RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 585862
Accepted
justyx
justyx
Asked:2020-11-02 18:19:56 +0000 UTC2020-11-02 18:19:56 +0000 UTC 2020-11-02 18:19:56 +0000 UTC

unity3d 中的着色器

  • 772

冒着捡帽子的风险,我还是问了这个问题。

需要为Unity3d编写一个着色器,它看起来是最简单的,呈放射状出现的图像(如下面的 gif 中所示),但具有模糊和渐变透明效果。也就是说,填充图像的一段(例如 30 度)- 不清晰可见,但透明度平滑过渡(下图中的示例)。

同时,零透明度的图像有最大的模糊(虽然它在透明图像上不可见,并且在这个例子中不是很清楚,但是因为当使用着色器时它会应用于照片之间的过渡,一切将在那里清晰可见)并且随着单个像素的透明度降低,它们的模糊程度也会降低。因此,如果透明度为 100(不透明),则模糊 = 0。

在此处输入图像描述

在此处输入图像描述

看了好几篇文章,脑子里还是想不通。我读了高斯模糊是如何实现的(平均每列/行的精灵上最近像素的 rgb 值),我知道有必要为此附加透明度并根据另一个改变一个,但是,例如,如何做到这一切,直接在代码中,这里还附上了动画,我完全不明白(虽然我明白有一个属性,_TransVal有一个#pragma alpha负责透明度的参数)。

因为 我以前不必编写着色器,老实说,我在 Internet 上找到的那些文章并没有带来太多清晰度 - 特别是在实现问题上。非常了解着色器是什么、它们是什么以及为什么 - 我有它。

如果有人承诺使用我的任务示例逐步描述实施过程并进行解释,我将不胜感激:他们说我们为此需要这个,为此,这就是我们将如何改变模糊,这就是透明度,这就是我们添加动画的方式。

更新 我能够实现的目标:通过调整我在 delirium 中收集的误解的 sigma 和 cutoff 值(由于我缺乏编写着色器的经验),我需要的一切都会发生,除了一个 BUT,精灵在这一切的背后,它从何而来,如何摆脱它,我仍然无法理解。

在此处输入图像描述

http://g.recordit.co/rUaGYdynQ3.gif(大图)。

此材质(带着色器)应用于图像。图像和画布,它们被放置在由单独的相机渲染的transparentfx层上(该层在主相机上被禁用)。仍然需要了解如何在后面禁用(或从渲染中删除)这个精灵,并以最小的形式解决任务(填充边缘的透明度不再重要,至少要处理模糊)。

更新 2如果您将它设置在Image类型本身上filled并转动旋钮,任务就解决了fill amount。但是如何人为地关闭图像的渲染,只留下真实的渲染,仍然是不可理解的。

着色器输出:

在此处输入图像描述

更新 12 月 29 日 意识到我不太擅长像人一样写作,我尝试使用 Shader Forge,它似乎达到了预期的效果(必要的效果之一)。但结果很奇怪,在某种意义上,在单元的检查器窗口中显示了应有的效果,而在游戏场景中,可以说它表现得“简化”了。请参阅下面的 gif 以更好地理解。GIF1,检查器窗口。在此处输入图像描述 GIF2,游戏窗口在此处输入图像描述

unity3d
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Алексей Шиманский
    2020-12-30T01:06:35Z2020-12-30T01:06:35Z

    Ответ, часть 2.

    • Так как возможно это будет нагрузка на шейдер, то можно некоторые части также довыносить в управляющий скрипт. Оставить тут только применение значений. В итоге шейдер и скрипт могут быть такими:

      Shader "Custom/RadialFill_MoreScriptControl" {
          Properties {
              [PerRendererData]_MainTex ("MainTex", 2D) = "white" {}
              _Color ("Color", Color) = (1,1,1,1)
              _OpacityRotator ("Opacity Rotator", Range(-360, 360)) = -360 // два полных оборота
              _TextureRotator ("Texture Rotator", Range(0, 360)) = 360
              [MaterialToggle] _FillClockwise ("Fill Clockwise", int ) = 1
              [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
              [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0            
              [HideInInspector] _CutoffRightBottomLeftTop ("cRBLT", Float) = 1.0
              [HideInInspector] _OpRightBottomLeftTop ("oRBLT", Float) = 1.0  
              [HideInInspector] _OpVector ("OpVector", Vector) = (1, -1, 0, 0)
              [HideInInspector] _ReverseMaskCoords ("_ReverseMaskCoords", int) = 0 
          }
      
          SubShader {
              Tags {
                  "IgnoreProjector"="True"
                  "Queue"="Transparent"
                  "RenderType"="Transparent"
                  "CanUseSpriteAtlas"="True"
                  "PreviewType"="Plane"
              }
      
              Pass {
                  Name "FORWARD"
                  Tags {
                      "LightMode"="ForwardBase"
                  }
      
                  Blend One OneMinusSrcAlpha
                  Cull Off
                  ZWrite Off
      
                  CGPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
                  #define UNITY_PASS_FORWARDBASE
                  #pragma multi_compile _ PIXELSNAP_ON
      
                  #include "UnityCG.cginc"
                  #pragma multi_compile_fwdbase
                  #pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2 
                  #pragma target 3.0
      
                  static const float TAU = float(6.283185); // это 2 * PI, кто не знает
      
                  uniform sampler2D _MainTex; 
                  uniform float4 _MainTex_ST;
                  uniform float4 _Color;
                  uniform float _OpacityRotator;
                  uniform float _TextureRotator;
                  uniform fixed _FillClockwise;       
                  uniform fixed _CutoffRightBottomLeftTop;
                  uniform fixed _OpRightBottomLeftTop;
                  uniform float2 _OpVector;
                  uniform int _ReverseMaskCoords;
      
                  struct VertexInput {
                      float4 vertex : POSITION;
                      float3 normal : NORMAL;
                      float4 tangent : TANGENT;
                      float2 texcoord0 : TEXCOORD0;
                  };
      
                  struct VertexOutput {
                      float4 pos : SV_POSITION;
                      float2 uv0 : TEXCOORD0;
                      float4 posWorld : TEXCOORD1;
                      float3 normalDir : TEXCOORD2;
                      float3 tangentDir : TEXCOORD3;
                      float3 bitangentDir : TEXCOORD4;
                  };
      
                  // матрица вращения
                  float2x2 getMatrix(float angle) {                               
                      float r_cos = cos(angle);
                      float r_sin = sin(angle);                
                      return float2x2(r_cos, -r_sin, r_sin, r_cos);
                  }
      
                  // формирование маски
                  float2x2 getMask(float oAtan2MaskNormalized, float rotator, int isRotatorSubtract) {                            
                      float oAtan2MaskRotatable = isRotatorSubtract ? oAtan2MaskNormalized - rotator : rotator - oAtan2MaskNormalized;
                      return ceil(oAtan2MaskRotatable);
                  }
      
                  float getNormalizedAtanMask(float2 maskChannels, int reverseMaskCoords) {
                      float atan2var = reverseMaskCoords ? atan2(maskChannels.r, maskChannels.g) : atan2(maskChannels.g, maskChannels.r);
                      return (atan2var / TAU) + 0.5;
                  }
      
                  VertexOutput vert (VertexInput v) {
                      VertexOutput o = (VertexOutput)0;
                      o.uv0 = v.texcoord0;
                      o.normalDir = UnityObjectToWorldNormal(v.normal);
                      o.tangentDir = normalize(mul(_Object2World, float4(v.tangent.xyz, 0.0)).xyz);
                      o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
                      o.posWorld = mul(_Object2World, v.vertex);
                      o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                      #ifdef PIXELSNAP_ON
                          o.pos = UnityPixelSnap(o.pos);
                      #endif
      
                      return o;
                  }
      
                  float4 frag(VertexOutput i) : COLOR {
                      i.normalDir = normalize(i.normalDir);                
                      float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex));                
      
                      /*** Общее начало для opacity и cutoff, помогающее переключать вращение по/против часовой стрелки BEGIN ***/
                      // float2(1, -1) - по часовой, float2(1, 1) - против часовой
                      float2 clockCounterDirection = _FillClockwise ? float2(1, -1) : float2(1, 1); 
                      // по умолчанию "обрезание" начинается слева. 
                      // умножение на -1 для того, чтоб началось справа.....просто потому, что я так хочу =)
                      float2 CommonStartAndSwitcher = (-1 * (i.uv0 - 0.5)) * clockCounterDirection;
                      /*** Общее начало для opacity и cutoff с переключателем вращения по/против часовой стрелки END ***/
      
      
                      /*** Секция для cutoff ***/
                      float tRotatorNormalized = _TextureRotator / 360.0;                 
                      float cutoffRotator_ang = _CutoffRightBottomLeftTop * -TAU;
                      float2x2 cutoffRotationMatrix = getMatrix(cutoffRotator_ang); 
                      float2 cutoffRotator = mul(CommonStartAndSwitcher, cutoffRotationMatrix);
                      float whiteToBlackMask = getMask(getNormalizedAtanMask(cutoffRotator, 0), tRotatorNormalized, 1);
                      // Финальная маска
                      float finalMask = 1.0 - whiteToBlackMask;
                      clip(finalMask - 0.5);
      
      
                      /*** Секция для opacity ***/
                      float oRotatorNormalized = _OpacityRotator / 360.0;
                      float2 oVector = float2(_OpVector);
      
                      float oRotator_ang = _OpRightBottomLeftTop * (oRotatorNormalized * -TAU);               
                      float2x2 oRotationMatrix = getMatrix(oRotator_ang);             
                      float2 oRotator = mul(oVector * CommonStartAndSwitcher, oRotationMatrix);
                      float oWhiteToBlackMask = getMask(getNormalizedAtanMask(oRotator, _ReverseMaskCoords), oRotatorNormalized, 0);                                                                  
                      // Финальная прозрачность 
                      float oFinalMultiply = _MainTex_var.a * max(getNormalizedAtanMask(oRotator, _ReverseMaskCoords), ceil(oWhiteToBlackMask)); 
      
      
                      /*** Излучение (Emissive) ***/
                      // oFinalMultiply чтоб обрезать прозрачную область, где она обрезана в самой текстуре
                      float3 finalColor = _MainTex_var.rgb * _Color.rgb * oFinalMultiply;
                      // Конечный результат (цвет, обработанный маской и повернутый под углом альфа канал)
                      return fixed4(finalColor, oFinalMultiply);
                  }
      
                  ENDCG
              }       
          }
      
          FallBack "Diffuse"    
      }
      

      Скрипт будет таким:

      using UnityEngine;
      using System.Collections;
      
      public enum FillOrigin {
          Right,
          Bottom,
          Left,
          Top
      }
      
      
      public class RadialFill_MoreScriptControl : MonoBehaviour {
      
          public float cutoffStartAngle = 5.0f; // градусы 
          public float opacityStartAngle = -350.0f; // градусы,  -2 * PI + 10 (небольшой начальный угол)
          public float deltaAngle = 5f;
          public bool fillClockwise = true;
          public FillOrigin fillOrigin = FillOrigin.Right;
      
          private const float MAX_ANGLE = 360.0f;
          private Material material;
          private float _TextureRotator; // ссылка на переменную _TextureRotator в шейдере
          private float _OpacityRotator; // ссылка на переменную _TextureRotator в шейдере
      
          void Start () {
              material = GetComponent<SpriteRenderer>().material;
          }
      
      
          void Update () {            
              if (Input.GetMouseButtonDown(0)) //if (Input.GetKeyDown("f"))       
                  StartCoroutine(FillSprite());       
          }
      
      
          IEnumerator FillSprite() {      
              var cOffStart = cutoffStartAngle;
              var oStart = opacityStartAngle;
              material.SetFloat("_FillClockwise", fillClockwise ? 1 : 0);
              material.SetFloat("_TextureRotator", cOffStart);
              material.SetFloat("_OpacityRotator", oStart);
      
              SetCutoffData();
              SetOpacityData();
      
              _TextureRotator = cOffStart;
              _OpacityRotator = oStart;
      
              while(_OpacityRotator <= MAX_ANGLE) {           
                  if (_TextureRotator >= MAX_ANGLE) 
                      _TextureRotator = MAX_ANGLE;
                  if (_OpacityRotator >= MAX_ANGLE) 
                      _OpacityRotator = MAX_ANGLE;
      
                  material.SetFloat("_TextureRotator", _TextureRotator);
                  material.SetFloat("_OpacityRotator", _OpacityRotator);
      
                  _OpacityRotator += deltaAngle;
                  _TextureRotator += deltaAngle;
      
                  yield return null;
              }
      
              yield break;
          }
      
      
          private void SetCutoffData() {
              var cutoffRightBottomLeftTop = 1.0f;
              if (fillOrigin == FillOrigin.Bottom)
                  cutoffRightBottomLeftTop = fillClockwise ? 1.75f : 1.25f;
              else if (fillOrigin == FillOrigin.Left)
                  cutoffRightBottomLeftTop = 1.5f;
              else if (fillOrigin == FillOrigin.Top)
                  cutoffRightBottomLeftTop = fillClockwise ? 1.25f : 1.75f;
              cutoffRightBottomLeftTop += 0.001f;
      
              material.SetFloat("_CutoffRightBottomLeftTop", cutoffRightBottomLeftTop);
          }
      
      
          private void SetOpacityData() {
              Vector2 oVector = new Vector2(1, -1);
              var oRightBottomLeftTop = 1.0f;
              int reverseMaskCoords = (fillOrigin == FillOrigin.Top || fillOrigin == FillOrigin.Bottom) ? 1 : 0;
              if (fillOrigin == FillOrigin.Left)
                  oVector = new Vector2(-1, 1);
              else if (fillOrigin == FillOrigin.Top) {
                  oVector = fillClockwise ? new Vector2(-1, -1) : new Vector2(1, 1);
                  oRightBottomLeftTop = -1.0f;
              } else if (fillOrigin == FillOrigin.Bottom) {
                  oVector = fillClockwise ? new Vector2(1, 1) : new Vector2(-1, -1);
                  oRightBottomLeftTop = -1.0f;
              }
      
              material.SetInt("_ReverseMaskCoords", reverseMaskCoords);
              material.SetVector("_OpVector", oVector);
              material.SetFloat("_OpRightBottomLeftTop", oRightBottomLeftTop);
          }
      }
      

      в инспекторе управление такое:

      在此处输入图像描述


    И по поводу размытия...Так как мой ответ уже большой (из-за кода)...и уже вторая часть, то я приведу код шейдера Blur, который вы можете перенести в шейдеры выше. А также дописать управление размытием из скрипта по примеру выше.

    Shader "Custom/Blur" {
        Properties {
            _MainTex ("Texture", 2D) = "white" {}
            radius ("radius", Range(0, 80)) =0
            resolution ("resolution", float) = 800
        }
    
        SubShader {
            Tags { "RenderType"="Opaque" }
            LOD 100
    
            Pass {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag       
                #include "UnityCG.cginc"
    
                struct appdata {
                    float4 vertex : POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                struct v2f {
                    float2 uv : TEXCOORD0;
                    float4 vertex : SV_POSITION;
                };
    
                sampler2D _MainTex;
                float4 _MainTex_ST;
    
                uniform  float resolution = 800;
                uniform  float radius = 400;
                uniform  float2 dir = float2(0,1);
    
                v2f vert (appdata v) {
                    v2f o;
                    o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target {
                    float4 sum = float4(0.0, 0.0, 0.0, 0.0);
                    float2 tc = i.uv;
    
                    // радиус размытия в пикселях
                    float blur = radius/resolution/4; 
    
                    float hstep = 1; // размытие по горизонтали
                    float vstep = 0; // размытие по вертикали
    
    
                    sum += tex2D(_MainTex, float2(tc.x - 5.0 * blur * hstep, tc.y - 5.0 * blur * vstep)) * 0.0052111262;
                    sum += tex2D(_MainTex, float2(tc.x - 4.0 * blur * hstep, tc.y - 4.0 * blur * vstep)) * 0.0162162162;
                    sum += tex2D(_MainTex, float2(tc.x - 3.0 * blur * hstep, tc.y - 3.0 * blur * vstep)) * 0.0540540541;
                    sum += tex2D(_MainTex, float2(tc.x - 2.0 * blur * hstep, tc.y - 2.0 * blur * vstep)) * 0.1216216216;
                    sum += tex2D(_MainTex, float2(tc.x - 1.0 * blur * hstep, tc.y - 1.0 * blur * vstep)) * 0.1945945946;
    
                    sum += tex2D(_MainTex, float2(tc.x, tc.y)) * 0.2270270270;
    
                    sum += tex2D(_MainTex, float2(tc.x + 1.0 * blur * hstep, tc.y + 1.0 * blur * vstep)) * 0.1945945946;
                    sum += tex2D(_MainTex, float2(tc.x + 2.0 * blur * hstep, tc.y + 2.0 * blur * vstep)) * 0.1216216216;
                    sum += tex2D(_MainTex, float2(tc.x + 3.0 * blur * hstep, tc.y + 3.0 * blur * vstep)) * 0.0540540541;
                    sum += tex2D(_MainTex, float2(tc.x + 4.0 * blur * hstep, tc.y + 4.0 * blur * vstep)) * 0.0162162162;
                    sum += tex2D(_MainTex, float2(tc.x + 5.0 * blur * hstep, tc.y + 5.0 * blur * vstep)) * 0.0052111262;
    
                    return float4(sum.rgb, 1);
                }
    
                ENDCG
            }
        }
    }
    

    P.S. К сожалению шейдер RadialFill работает только для Sprite Mode → Single. Как сделать для мульти Multiple, я пока не знаю.

    P.P.S. Можно сделать еще улучшения:

    • Вынести функцию getMatrix (матрица вращения) также в скрипт.
    • Сделать маску не генерируемой внутри шейдера, а взять текстуру в виде картинки atan2 и применять уже её. Глядишь еще чуть снять загрузку с шейдера можно.

    PPPS要尝试至少在较小的层面上了解着色器,您可以使用Shader Forge资产- 用于编程着色器的可视化编辑器。在这种情况下,视觉效果非常好。

    在免费的中,目前搜索只显示uShader FREE - 但我没有尝试过,我不知道它有多好。


    上面着色器中使用的阅读链接:

    • 标准着色器预处理器宏
    • 使用多个着色器选项创建程序
    • #pragma 编译指令
    • 剔除和深度
    • 混合

    • Unity 中的着色器和效果。食谱书

    • Unity 5.x 着色器和效果手册
    • 4
  2. Best Answer
    KingPeas
    2020-11-09T13:08:44Z2020-11-09T13:08:44Z

    通过单个着色器来围栏是否值得这一切?会这样做:

    1. 要使用模糊图像,您需要另一个相机,通过 BLUR 后期效果,将在没有叠加图像的情况下写入 RenderTexture 屏幕(将其放在单独的图层中并关闭此相机中的该图层)。
    2. 接下来,我们制作您感兴趣的任何形式的表现形式的动画。使用 alpha 遮罩动画制作形状。
    3. 然后一切都很简单,对于动画形式,我们使用最简单的材质,根据材质上纹理的透明度,通过混合从主纹理或从 RenderTexture 中的模糊纹理绘制。
    • 2
  3. Алексей Шиманский
    2020-12-30T01:04:48Z2020-12-30T01:04:48Z

    哇,有趣的问题,甚至是奖励!

    在此处输入图像描述

    总的来说,无论我读了多少书,我都无法想象为什么要对透明度应用模糊效果。毕竟,如果 sprite 的某个部分是透明的,那么那里的模糊就不会可见和明显,而不透明的地方则没有模糊。或者我不明白什么,这意味着一般对整个精灵应用模糊,而不管现在可用的径向填充百分比是多少。如果是这样,那么

    1. 您可以按照另一个答案中的说明进行操作:从标准的 Unity 资产(幸运的是其中有很多)中获取应用于相机的模糊效果。添加另一个摄像机,在那里添加一个模糊脚本和一个着色器,然后在正确的时间打开该摄像机并更改模糊效果中的偏移量。
    2. 在着色器中根据需要实施))最后更多。

    (!!!)

    是的,站点管理员会原谅我,但我不会给出一个答案(由于代码量,而不是因为“水”)。所以答案将分为两部分。


    回答第 1 部分。

    最重要的是裁剪,填充透明度将归结为将采取面具的事实,一切都会在此基础上发生。掩码是通过三角函数以编程方式生成的atan2。在轴上它看起来像这样:

    阿坦2

    在二维坐标系中它看起来像这样:

    阿坦2 2D

    由于遮罩是由黑白色调组成的某个组件,其中黑色是完全没有纹理,而白色是完全可见的纹理,那么对于透明度,遮罩atan2将是从黑色到白色的过渡(见图上面),并且将应用附加功能进行裁剪,因此它只是黑色/白色,没有平滑过渡。

    atan2细胞

    我将尝试只发布一个着色器,其中包含对所做工作的评论。我不确定一切都会清楚,但我会尽力而为。

    Shader "Custom/RadialFill" {
        Properties {
            [PerRendererData]_MainTex ("MainTex", 2D) = "white" {}
            _Color ("Color", Color) = (1,1,1,1)
            _OpacityRotator ("Opacity Rotator", Range(-360, 360)) = -360 // два полных оборота
            _TextureRotator ("Texture Rotator", Range(0, 360)) = 360
            [MaterialToggle] _FillClockwise ("Fill Clockwise", Float ) = 1
            [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
            [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0    
            [KeywordEnum(Right, Bottom, Left, Top)] _Fill_Origin("Fill Origin", Int) = 0        
        }
    
        SubShader {
            // https://docs.unity3d.com/ru/current/Manual/SL-SubShaderTags.html
            Tags {      
                "IgnoreProjector"="True"
                "Queue"="Transparent"
                "RenderType"="Transparent"
                "CanUseSpriteAtlas"="True"
                "PreviewType"="Plane"
            }
    
            Pass {
                Name "FORWARD"
                Tags {
                    "LightMode"="ForwardBase" // https://docs.unity3d.com/Manual/SL-PassTags.html
                }
    
                Blend One OneMinusSrcAlpha  // https://docs.unity3d.com/ru/current/Manual/SL-Blend.html      
                ZWrite Off                  // https://docs.unity3d.com/ru/current/Manual/SL-CullAndDepth.html
    
                CGPROGRAM
                #pragma vertex vert     // vert - имя функции обработки вершин
                #pragma fragment frag   // frag - имя функции обработки пикселей             
                #pragma multi_compile _ PIXELSNAP_ON
    
                // как работает shader_feature: https://docs.unity3d.com/ru/530/Manual/SL-MultipleProgramVariants.html
                // он относится к свойству _Fill_Origin .... по сути - автоматически конвертируем его имя и значения в константы
                #pragma shader_feature _FILL_ORIGIN_RIGHT _FILL_ORIGIN_BOTTOM _FILL_ORIGIN_LEFT _FILL_ORIGIN_TOP
    
                #include "UnityCG.cginc"
    
                #pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2 
                #pragma target 3.0
                uniform sampler2D _MainTex; 
                uniform float4 _MainTex_ST;
                uniform float4 _Color;
                uniform float _OpacityRotator;
                uniform float _TextureRotator;
                uniform fixed _FillClockwise;
    
                static const float TAU = float(6.283185); // это 2 * PI, кто не знает
    
                struct VertexInput {
                    float4 vertex : POSITION;
                    float3 normal : NORMAL;
                    float4 tangent : TANGENT;
                    float2 texcoord0 : TEXCOORD0;
                };
    
                struct VertexOutput {
                    float4 pos : SV_POSITION;
                    float2 uv0 : TEXCOORD0;
                    float4 posWorld : TEXCOORD1;
                    float3 normalDir : TEXCOORD2;
                    float3 tangentDir : TEXCOORD3;
                    float3 bitangentDir : TEXCOORD4;
                };
    
                VertexOutput vert (VertexInput v) {
                    VertexOutput o = (VertexOutput)0;
                    o.uv0 = v.texcoord0;
                    o.normalDir = UnityObjectToWorldNormal(v.normal);
                    o.tangentDir = normalize(mul(_Object2World, float4(v.tangent.xyz, 0.0)).xyz);
                    o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
                    o.posWorld = mul(_Object2World, v.vertex);
                    o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                    #ifdef PIXELSNAP_ON
                        o.pos = UnityPixelSnap(o.pos);
                    #endif
    
                    return o;
                }
    
                float4 frag(VertexOutput i) : COLOR {
                    i.normalDir = normalize(i.normalDir);                
                    float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex));                
    
                    /*** Общее начало для opacity и cutoff, помогающее переключать вращение по/против часовой стрелки BEGIN ***/
                    // float2(1, -1) - по часовой, float2(1, 1) - против часовой
                    float2 clockCounterDirection = _FillClockwise ? float2(1, -1) : float2(1, 1); 
                    // по умолчанию "обрезание" начинается слева. 
                    // умножение на -1 для того, чтоб началось справа.....просто потому, что я так хочу =)
                    float2 CommonStartAndSwitcher = (-1 * (i.uv0 - 0.5)) * clockCounterDirection;
                    /*** Общее начало для opacity и cutoff с переключателем вращения по/против часовой стрелки END ***/
    
    
                    /*** Секция для cutoff ***/
                    float cutoffRightBottomLeftTop = 1.0; // изменение направления
    
                    // В зависимости от того, что выбрано в качестве старта вращения право/лево/верх/низ
                    // нужно будет провернуть и текстурку.
                    // +0.25 - 90 градусов, +0.5 - 180, +0.75 - 270
                    #if _FILL_ORIGIN_BOTTOM
                        cutoffRightBottomLeftTop = _FillClockwise ? 1.75 : 1.25;
                    #elif _FILL_ORIGIN_LEFT
                        cutoffRightBottomLeftTop = 1.5;
                    #elif _FILL_ORIGIN_TOP
                        cutoffRightBottomLeftTop = _FillClockwise ? 1.25 : 1.75;
                    #endif
                    cutoffRightBottomLeftTop += 0.001;
    
                    // Матрица вращения для cutoff
                    float cutoffRotator_ang = cutoffRightBottomLeftTop * -TAU;                
                    float cutoffRotator_cos = cos(cutoffRotator_ang);
                    float cutoffRotator_sin = sin(cutoffRotator_ang);                
                    float2x2 cutoffRotationMatrix = float2x2(cutoffRotator_cos, -cutoffRotator_sin, cutoffRotator_sin, cutoffRotator_cos);                      
    
                    float2 cutoffRotator = mul(CommonStartAndSwitcher, cutoffRotationMatrix);
    
                    // перевод из системы от 0 до 360 градусов в отсчет от 0 до 1
                    float tRotatorNormalized = _TextureRotator / 360.0;   
                    // Генерирование маски для отсечения пикселей и  отсечение пикселей по предоставленной маске
                    // 1. Для генерации нужны исхоные две координаты.... 
                    // rg, утрированно, представляют из себя x и y
                    float2 cutoffMaskSource = cutoffRotator.rg;             
                    // 2. Формируем начальную маску // в инете рисуночки глянуть как это выглядит =)
                    // Угол задается в радианах и принимает значения от -PI до PI, исключая -PI
                    float atan2Mask = atan2(cutoffMaskSource.g, cutoffMaskSource.r);
                    // 3. Добавляем пол оборота (до целого) и конвертируем в значение от 0 до 1,
                    // для дальнейшей удобной работы в единичном отрезке, т.к tRotatorNormalized меняется от 0 до 1
                    float atan2MaskNormalized = (atan2Mask / TAU) + 0.5;
                    // 4. Привязка маски к повороту. хз как объяснить               
                    float atan2MaskRotatable = atan2MaskNormalized - tRotatorNormalized;
                    // 5. Получаем карту заливки от белого к черному
                    // Белый - полностью видимый участок, Черный - обрезающиеся (не отображающиеся) пиксели
                    float whiteToBlackMask = ceil(atan2MaskRotatable);
                    // 6. Собираем финальную маску от чёрного к белому (т.к. нужно постепенное заполнение)
                    float finalMask = 1.0 - whiteToBlackMask;
                    clip(finalMask - 0.5);
    
    
                    /*** Секция для opacity ***/
                    // oVector меняется в зависимости от начала направления - лево/право/верх/низ
                    float2 oVector = float2(1, -1);
                    // изменение направления в зависимости от лево-право (1.0) или верх-низ (-1.0)
                    float oRightBottomLeftTop = 1.0;
    
                    // В зависимости от того, что выбрано в качестве старта вращения право/лево/верх/низ
                    // нужно будет провернуть и маску.
                    #if _FILL_ORIGIN_LEFT
                        oVector = float2(-1, 1);
                    #elif _FILL_ORIGIN_TOP
                        oVector = _FillClockwise ? float2(-1, -1) : float2(1, 1);
                        oRightBottomLeftTop = -1.0;
                    #elif _FILL_ORIGIN_BOTTOM
                        oVector = _FillClockwise ? float2(1, 1) : float2(-1, -1);
                        oRightBottomLeftTop = -1.0;
                    #endif
    
                    float oRotatorNormalized = _OpacityRotator / 360.0;
    
                    // Матрица вращения для opacity
                    float oRotator_ang = oRightBottomLeftTop * (oRotatorNormalized * -TAU);                
                    float oRotator_cos = cos(oRotator_ang);
                    float oRotator_sin = sin(oRotator_ang);                
                    float2x2 oRotationMatrix = float2x2(oRotator_cos, -oRotator_sin, oRotator_sin, oRotator_cos);               
    
                    float2 oRotator = mul(oVector * CommonStartAndSwitcher, oRotationMatrix);
    
                    // Как и у cutoff формируем маску
                    float2 oMask = oRotator.rg;
                    float2 oMaskHorizOrVert = atan2(oMask.g, oMask.r);
                    // при формировании маски по вертикали, нужно поменять x, y местами в функции
                    #if (_FILL_ORIGIN_TOP || _FILL_ORIGIN_BOTTOM)
                        oMaskHorizOrVert = atan2(oMask.r, oMask.g);
                    #endif
    
                    float oAtan2MaskNormalized = (oMaskHorizOrVert / TAU) + 0.5;
                    // oRotatorNormalized - oAtan2MaskNormalized для того, чтобы первый круг просто провернуться, а на втором
                    // начать обрезку как у cutoff, только начиная схвоста, но при этом продолжая вращаться.
                    // Если было бы  oAtan2MaskNormalized - oRotatorNormalized (как в примере с cutoff выше), то, т.к. значение oRotatorNormalized
                    // меняется с -1 до 1 (два полных круга),  получается что маска наложена на изображение 2 раза: 1 раз - прозрачность, 2 раз - она же
                    // поэтому увеличивается наложенность, белый цвет. В итоге при изменении с -1 до 1 ушла бы в начале белизна, а потом провернулась бы маска,
                    // и не обрезалась бы
                    float oAtan2MaskRotatable = oRotatorNormalized - oAtan2MaskNormalized;
                    float oWhiteToBlackMask = ceil(oAtan2MaskRotatable);                
                    // Финальная прозрачность 
                    float oFinalMultiply = _MainTex_var.a * max(oAtan2MaskNormalized, ceil(oWhiteToBlackMask)); 
    
                    /*** Излучение (Emissive) ***/
                    // oFinalMultiply чтоб обрезать прозрачную область, где она обрезана в самой текстуре
                    float3 finalColor = _MainTex_var.rgb * _Color.rgb * oFinalMultiply;
                    // Конечный результат (цвет, обработанный маской и повернутый под углом альфа канал)
                    return fixed4(finalColor, oFinalMultiply);
                }
    
                ENDCG
            }       
        }
    
        FallBack "Diffuse"    
    }
    

    要在正确的时间开始填充,当然需要下达命令。哪里可以给?没错 - 从剧本。它将位于下方:

    using UnityEngine;
    using System.Collections;
    
    
    public class RadialFill : MonoBehaviour {
    
        public float cutoffStartAngle = 5.0f; // градусы 
        public float opacityStartAngle = -350.0f; // градусы,  -2 * PI + 10 (небольшой начальный угол)
        public float deltaAngle = 5f;
    
        private const float MAX_ANGLE = 360.0f;
    
        private Material material;
        private float _TextureRotator; // ссылка на переменную _TextureRotator в шейдере
        private float _OpacityRotator; // ссылка на переменную _TextureRotator в шейдере
    
        void Start () {
            material = GetComponent<SpriteRenderer>().material;
        }
    
    
        void Update () {            
            if (Input.GetMouseButtonDown(0)) //if (Input.GetKeyDown("f"))       
                StartCoroutine(FillSprite());       
        }
    
    
        IEnumerator FillSprite() {      
            var cOffStart = cutoffStartAngle;
            var oStart = opacityStartAngle;
    
            material.SetFloat("_TextureRotator", cOffStart);
            material.SetFloat("_OpacityRotator", oStart);
            _TextureRotator = cOffStart;
            _OpacityRotator = oStart;
    
            while(_OpacityRotator <= MAX_ANGLE) {           
                if (_TextureRotator >= MAX_ANGLE) 
                    _TextureRotator = MAX_ANGLE;
                if (_OpacityRotator >= MAX_ANGLE) 
                    _OpacityRotator = MAX_ANGLE;
    
                material.SetFloat("_TextureRotator", _TextureRotator);
                material.SetFloat("_OpacityRotator", _OpacityRotator);
    
                _OpacityRotator += deltaAngle;
                _TextureRotator += deltaAngle;
    
                yield return null;
            }
    
            yield break;
        }
    }
    

    在哪里:

    cutoffStartAngle — начальный угол обрезки, opacityStartAngle — начальный угол прозрачности. Эти параметры для того, чтобы немного отрегулировать по вкусу площадь сектора, занимаемого прозрачностью. Замечу, что прозрачность изменяется от -360 до 360, потому что первый круг она проворачивается сама по себе, а второй круг — плавно "заходит" за текстуру.

    deltaAngle - дельта, на которую проворачиваются маски.

    Что скрипт делает? При нажатии нажатии на клавишу мыши он берет шейдер у спрайта (точнее с его материала), устанавливает изначальные углы, в цикле изменяет угол поворота и передает это значение в шейдер, чтобы он там уже у себя применил значения в frag.

    Выглядит в инспекторе так:

    RadialFillShader&脚本检查器

    • Opacity Rotator - вращение маски прозрачности
    • Texture Rotator - вращение маски обрезки
    • Fill Clockwise - по часовой стрелке или против
    • Fill Origin - с какой стороны начинать (справа/слева/сверху/снизу)

    Итог будет выглядеть примерно таким:

    在此处输入图像描述

    Увы на данной гифке не получается передать то, как это выглядит в правильности


    Улучшение

    • Так как у секции cutoff и opacity есть общие части, то их можно вынести в общие функции, как и во всех нормальных языках программирования.

      Shader "Custom/RadialFillCommonFunctions" {
          Properties {
              [PerRendererData]_MainTex ("MainTex", 2D) = "white" {}
              _Color ("Color", Color) = (1,1,1,1)
              _OpacityRotator ("Opacity Rotator", Range(-360, 360)) = -360 // два полных оборота
              _TextureRotator ("Texture Rotator", Range(0, 360)) = 360
              [MaterialToggle] _FillClockwise ("Fill Clockwise", Float ) = 1
              [HideInInspector]_Cutoff ("Alpha cutoff", Range(0,1)) = 0.5
              [MaterialToggle] PixelSnap ("Pixel snap", Float) = 0    
              [KeywordEnum(Right, Bottom, Left, Top)] _Fill_Origin("Fill Origin", Int) = 0        
          }
      
          SubShader {
              Tags {
                  "IgnoreProjector"="True"
                  "Queue"="Transparent"
                  "RenderType"="Transparent"
                  "CanUseSpriteAtlas"="True"
                  "PreviewType"="Plane"
              }
      
              Pass {
                  Name "FORWARD"
                  Tags {
                      "LightMode"="ForwardBase"
                  }
      
                  Blend One OneMinusSrcAlpha
                  Cull Off
                  ZWrite Off
      
                  CGPROGRAM
                  #pragma vertex vert
                  #pragma fragment frag
                  #define UNITY_PASS_FORWARDBASE
                  #pragma multi_compile _ PIXELSNAP_ON
      
                  #pragma shader_feature _FILL_ORIGIN_RIGHT _FILL_ORIGIN_BOTTOM _FILL_ORIGIN_LEFT _FILL_ORIGIN_TOP
      
                  #include "UnityCG.cginc"
                  #pragma multi_compile_fwdbase
                  #pragma exclude_renderers gles3 metal d3d11_9x xbox360 xboxone ps3 ps4 psp2 
                  #pragma target 3.0
      
                  static const float TAU = float(6.283185); // это 2 * PI, кто не знает
      
                  uniform sampler2D _MainTex; 
                  uniform float4 _MainTex_ST;
                  uniform float4 _Color;
                  uniform float _OpacityRotator;
                  uniform float _TextureRotator;
                  uniform fixed _FillClockwise;       
      
                  struct VertexInput {
                      float4 vertex : POSITION;
                      float3 normal : NORMAL;
                      float4 tangent : TANGENT;
                      float2 texcoord0 : TEXCOORD0;
                  };
      
                  struct VertexOutput {
                      float4 pos : SV_POSITION;
                      float2 uv0 : TEXCOORD0;
                      float4 posWorld : TEXCOORD1;
                      float3 normalDir : TEXCOORD2;
                      float3 tangentDir : TEXCOORD3;
                      float3 bitangentDir : TEXCOORD4;
                  };
      
                  // матрица вращения
                  float2x2 getMatrix(float angle) {                               
                      float r_cos = cos(angle);
                      float r_sin = sin(angle);                
                      return float2x2(r_cos, -r_sin, r_sin, r_cos);
                  }
      
                  // формирование маски
                  float2x2 getMask(float oAtan2MaskNormalized, float rotator, int isRotatorSubtract) {
                      //float atan2var = reverseMaskCoords ? atan2(maskChannels.r, maskChannels.g) : atan2(maskChannels.g, maskChannels.r);
                      //float oAtan2MaskNormalized = (atan2var / TAU) + 0.5;              
                      float oAtan2MaskRotatable = isRotatorSubtract ? oAtan2MaskNormalized - rotator : rotator - oAtan2MaskNormalized;
                      return ceil(oAtan2MaskRotatable);
                  }
      
                  float getNormalizedAtanMask(float2 maskChannels, int reverseMaskCoords) {
                      float atan2var = reverseMaskCoords ? atan2(maskChannels.r, maskChannels.g) : atan2(maskChannels.g, maskChannels.r);
                      return (atan2var / TAU) + 0.5;
                  }
      
                  VertexOutput vert (VertexInput v) {
                      VertexOutput o = (VertexOutput)0;
                      o.uv0 = v.texcoord0;
                      o.normalDir = UnityObjectToWorldNormal(v.normal);
                      o.tangentDir = normalize(mul(_Object2World, float4(v.tangent.xyz, 0.0)).xyz);
                      o.bitangentDir = normalize(cross(o.normalDir, o.tangentDir) * v.tangent.w);
                      o.posWorld = mul(_Object2World, v.vertex);
                      o.pos = mul(UNITY_MATRIX_MVP, v.vertex );
                      #ifdef PIXELSNAP_ON
                          o.pos = UnityPixelSnap(o.pos);
                      #endif
      
                      return o;
                  }
      
                  float4 frag(VertexOutput i) : COLOR {
                      i.normalDir = normalize(i.normalDir);                
                      float4 _MainTex_var = tex2D(_MainTex,TRANSFORM_TEX(i.uv0, _MainTex));                
      
                      /*** Общее начало для opacity и cutoff, помогающее переключать вращение по/против часовой стрелки BEGIN ***/
                      // float2(1, -1) - по часовой, float2(1, 1) - против часовой
                      float2 clockCounterDirection = _FillClockwise ? float2(1, -1) : float2(1, 1); 
                      // по умолчанию "обрезание" начинается слева. 
                      // умножение на -1 для того, чтоб началось справа.....просто потому, что я так хочу =)
                      float2 CommonStartAndSwitcher = (-1 * (i.uv0 - 0.5)) * clockCounterDirection;
                      /*** Общее начало для opacity и cutoff с переключателем вращения по/против часовой стрелки END ***/
      
      
                      /*** Секция для cutoff ***/
                      float tRotatorNormalized = _TextureRotator / 360.0; 
                      float cutoffRightBottomLeftTop = 1.0; // изменение направления
      
                      #if _FILL_ORIGIN_BOTTOM
                          cutoffRightBottomLeftTop = _FillClockwise ? 1.75 : 1.25;
                      #elif _FILL_ORIGIN_LEFT
                          cutoffRightBottomLeftTop = 1.5;
                      #elif _FILL_ORIGIN_TOP
                          cutoffRightBottomLeftTop = _FillClockwise ? 1.25 : 1.75;
                      #endif
                      cutoffRightBottomLeftTop += 0.001;
      
                      float cutoffRotator_ang = cutoffRightBottomLeftTop * -TAU;
                      float2x2 cutoffRotationMatrix = getMatrix(cutoffRotator_ang); 
                      float2 cutoffRotator = mul(CommonStartAndSwitcher, cutoffRotationMatrix);
                      float whiteToBlackMask = getMask(getNormalizedAtanMask(cutoffRotator, 0), tRotatorNormalized, 1);
                      // Финальная маска
                      float finalMask = 1.0 - whiteToBlackMask;
                      clip(finalMask - 0.5);
      
      
                      /*** Секция для opacity ***/
                      float oRotatorNormalized = _OpacityRotator / 360.0;
                      float2 oVector = float2(1, -1);
                      float oRightBottomLeftTop = 1.0;
                      int reverseMaskCoords = 0;
                      #if (_FILL_ORIGIN_TOP || _FILL_ORIGIN_BOTTOM)
                          reverseMaskCoords = 1;
                      #endif
      
                      #if _FILL_ORIGIN_LEFT
                          oVector = float2(-1, 1);
                      #elif _FILL_ORIGIN_TOP
                          oVector = _FillClockwise ? float2(-1, -1) : float2(1, 1);
                          oRightBottomLeftTop = -1.0;
                      #elif _FILL_ORIGIN_BOTTOM
                          oVector = _FillClockwise ? float2(1, 1) : float2(-1, -1);
                          oRightBottomLeftTop = -1.0;
                      #endif
      
                      float oRotator_ang = oRightBottomLeftTop * (oRotatorNormalized * -TAU);
                      float2x2 oRotationMatrix = getMatrix(oRotator_ang);             
                      float2 oRotator = mul(oVector * CommonStartAndSwitcher, oRotationMatrix);
                      float oWhiteToBlackMask = getMask(getNormalizedAtanMask(oRotator, reverseMaskCoords), oRotatorNormalized, 0);                                                                   
                      // Финальная прозрачность 
                      float oFinalMultiply = _MainTex_var.a * max(getNormalizedAtanMask(oRotator, reverseMaskCoords), ceil(oWhiteToBlackMask)); 
      
      
                      /*** Излучение (Emissive) ***/
                      // oFinalMultiply чтоб обрезать прозрачную область, где она обрезана в самой текстуре
                      float3 finalColor = _MainTex_var.rgb * _Color.rgb * oFinalMultiply;
                      // Конечный результат (цвет, обработанный маской и повернутый под углом альфа канал)
                      return fixed4(finalColor, oFinalMultiply);
                  }
      
                  ENDCG
              }       
          }
      
          FallBack "Diffuse"    
      }
      
    • 2

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5