您现在的位置是:首页 >技术交流 >庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)>网站首页技术交流

庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)>

Allen7474 2024-08-29 12:01:03
简介庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)>

庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)

大纲:

目录

庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)

大纲:

正文:

一、走马灯:

总览:

细则公式原理:(1-3步骤)

二、极坐标:

三、分享:


正文:

一、走马灯:

0、前置 小知识点:

①、名词认识,符号认识: 

Sequence = 序列

PolarCoord = Polar(极地的) + Coord(坐标)<Coordinated的简写>。

θ = 拼音,拼写,(XiTa) 代表含义,<温度,角度>。

参考连接:θ 百度百科

②、Subshader { } 的含义,不同LOD级别的回退。

通常手机游戏开发中,对不同机型做适配,会根据 高 中 低 配,写不同的优化程度的内容。

通常在 Subshader中写 LOD 500 ,LOD 100。

Subshader {

LOD 500

}

③、Subshader下的Tags,作用与整个shader。

④、Subshader下的Pass,可以有多个,也可以有不同名字不同LightMode,不同的混合模式(Blend One OneMinusSrcAlpha)

不同的名字有什么作用和好处呢?

例如A Pass写完了一个效果,然后里面内容完全不想改,可以通过UsePass(名字)索引到。

LightMode是什么意思呢?

在手机环境下用的都是"LightMode"="ForwardBase”,但在用URP 或HDRP时,是可以自己声明一个LightMode的,你的相机在渲染场景的时候,他会有一个模式,这个模式下只会渲染这个模式下的shader,例如,十二课写人物shader中,为什么你没有写关于阴影的事情,只是把他的.cginc包含进来,就有投影了呢,因为回退FallBack的shader里面,会有一个Pass,他的LightMode 是 ShaderCast.,就是产生阴影。

实际上就是,指定某个Tags,渲染阴影,而其他的Tags则不渲染阴影,和PPV机制类似。

特效的shader是不需要Fallback的 (因为特效不需要阴影),

人物是需要保留的 (因为人物需要阴影)。

1、案例展示:

双通道,双Pass实现。

2、实现思路:

①、双通道,双Pass:

复制AB的Pass 代码在第一层,(基础着色),

修改Pass下的Name为"FORWARD_AB"

复制AD的Pass代码在第二层,(序列帧着色)

修改Pass下的Name为"FORWARD_AD"

中间输出测试看一下,可以看到,普通的AB和 AB+AD的相比,AB+AD更亮一些。

那我们需要让AD更亮的,输出鬼火的序列。

②、声明参数:

声明一个序列帧图像,

_Sequence ("序列帧", 2d) = "gray"{}

声明一个行数,一个列数,因为序列帧贴图的行列是不同的,所以这里要让他们可变。

_RowCount ("行数", int) = 1

_ColCount ("列数", int) = 1

声明一个编号,用来记录 格子中每个图的变量。

_SequId ("序号",int) = 0

注意图像的 循环模式为Repeat。

③、实现AD的法线方向的挤出效果:

所以要在AD输入结构中 ,追加法线

float3 normal : Normal; //法线声明,挤出用。

在顶点shader中,用对象本地空间做挤出,那么本地空间顶点是谁呢,就是,v.vertex;

然后让他沿顶点方向挤出一点距离怎么操作呢?

他的位置.xyz + 他自己的法线方向 * 挤出长度;

v.vertex.xyz += v.normal * 0.01;

(这里注意v.vertex4维的,v.normal3维的,所以v.vertex需要加上.xyz)

小知识:

这里也可以做成描边,现在是渲染正面,这里改成渲染反面就可以实现描边Cull Front.

描边的原理,也是挤出的思路~;

④、换算 序列UV, 实现序列图滚动。

视频里 庄佬 口头讲了很多,但是最终可以用一幅图来表达清楚他前期想要追加的所有概念。

行序号idU 0-4 = _RowCunt行数4

列序号idV 0-4 = _ColCunt列数4

_SequId序号:共16个

stepU (U轴步幅的长度1cm)

stepV (V轴步幅的长度1cm)

总览:

1、知道了行序号,和列序号,就可以确定序号 范围.

例如 序号9 ==id [V2] [U1]

//求idU 和 idV ,显示uv位置. (floor为程序中取整)。

float idV = floor( _SequId / _ColCount); //求 id 在 idV的行数(商)

float idU = _Sequence - idV * _ColCount; //求id在 idU的列数(余)

//求U轴,和 V轴 的步幅

float stepU = 1 / _ColCount; //U轴一步幅 长度

float stepV = 1 / _RowCount; //V轴一步幅 长度

2、划定初始位置 + 偏移

idU 是我跳了几格(4), stepU步幅 是每格跳的长度(1cm)

idU * stepU = U轴向 的偏移 步数

4(偏移次数) * 1cm(偏移长度) = 4总偏移距离

公式:总偏移距离(因为要整除,准确对应图像位置) = 初始UV + float2(idU * stepU , idV * stepV);

//划定初始UV位置 先缩小,在移动到初始位置;

float2 initUV = o.uv * float2(stepuU,stepV); //UV缩小

float2 initUV = o.uv * float2(stepuU,stepV) + float2(0,stepU * (_RowCount-1)); //UV位移

//将计算好的 UV 位置 大小,偏移,赋值给 o.uv;

o.uv = initUV - float2(idU * stepU , idV * stepV);


细则公式原理:(1-3步骤)

1、代码中:获取 商数 (V轴列数) < 这里假设要拿到序号9>

求商 → idV = floor(序号 / 列数); 9/4=2(商) ... 1(余)

_SequId(序号数) / _ColCunt(列数) = idV (商数)

9 (序号) / 4(列数) = 2列 (商数) ...1(余数)

这里正好,把序号在队列中的位置表述出来9[U2][V1]

行号,和列号 都是从0开始算的.

float idV = floor(_SequId(序号总数) / _ColCunt(列数) );

floor的使用是代码使用中整除,取整的意思。


2、 获取 余数 (U轴行数)

求余 → idU = 序号 - 商 * 列数; 9 - 2*4 = 1 (余数)

float idU = _SequId - idV * _ColCunt;

这里取余的话就是 走公式就可以了。


3、获取步幅距离:

stepU(U轴步幅 距离) = 1(整张UV) / _ColCunt(4列数);

stepV(V轴步幅 距离) = 1(整张UV) / _RowCunt(3行数);

4、获取初始UV:

(小知识点:在shader中除法比较消耗内存,通常会在Edit编辑界面提前除好,在进入计算环节)

先缩小UV范围:根据前面计算好的U轴(stepU),和V轴(stepV) 步幅长度限定出范围。

initUV = o.uv * float2 (stepU,stepV);

然后设置初始位置:

因为程序运行贴图的机制,贴图原点通常在左下角,而不是左上角,所以这里需要将初始UV上移2格,也就是V轴上移2格。

initUV = o.uv * float2 (stepU,stepV) + float2(0.0 , -stepV * (_RowCunt - 1 ));

这里开始没想明白为什么要 3(行数) -1 ,原因是,缩小后的UV本身也算一个单位所以要减去本身一个单位变成上移2个单位就正好是指定的位置了.

5、将上面计算出的 UV大小,和UV初始位置,赋给 o.uv;

o.uv = initUV - float2(idU * stepU , idV * stepV);

(注意:视频中,+ 号- 号,会影响 UV是上滚动,还是下滚动)

6、替换外部输入为随时间滚动:

先删除全局中的序号的变量(_SequId)

顶点shader中,声明一个局部 _SequId变量,并和_Time.x相乘,进行滚动,这里的_Speed范围给在(range(-10,10)),这样可控制,正向滚动,和负向滚动。

float _SequId = floor(_Time.y * _Speed);

视频中引用了下图的贴图进行滚动测试,这里在删除全局_SequId变量前记得先测试滚动顺序,以免发生视频中的问题。

7、回顾总结:

8、代码实现:

下列代码中庄懂老师改变了代码的结构,做了些优化,可能与上述的思路不太一致,可以做变式训练。

Shader "Unlit/Sc018_Sequence02"
{
    Properties
    {
        _MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
        _Opacity ("透明度", range(0, 1)) = 0.5

        _Sequence   ("序列帧", 2d) = "gray"{}
        _RowCount   ("行数", int) = 1
        _ColCount   ("列数", int) = 1
        _Speed       ("速度",Range(-10,10)) = 5
    }
    SubShader
    {
        Tags { 
                "Queue"="Transparent"
                "RenderType"="Transparent"
                "ForceNoShadowCasting"="True"
                "IgnorProjector"="True"
            }

         Pass {
            Name "FORWARD_AB"
            Tags {

            }

            Blend One OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma target 3.0
            UNITY_INSTANCING_BUFFER_START( Props )
               // UNITY_DEFINE_INSTANCED_PROP( float4, _Color)
            UNITY_INSTANCING_BUFFER_END( Props )
            
            uniform sampler2D _MainTex; 
            uniform half _Opacity;

            //输入结构
            struct VertexInput
            {
                float4 vertex : POSITION;       // 顶点位置 总是必要
                float2 uv : TEXCOORD0;          // UV信息 采样贴图用
            };
            //顶点输出结构
            struct VertexOutput 
            {
                 float4 pos : SV_POSITION;       // 顶点位置 总是必要
                float2 uv : TEXCOORD0;          // UV信息 采样贴图用
            };
            //输出结构>>>顶点shader>>>输出结构
            VertexOutput vert (VertexInput v) 
            {
                 VertexOutput o = (VertexOutput)0;
                    o.pos = UnityObjectToClipPos( v.vertex);    // 顶点位置 OS>CS
                    o.uv = v.uv;       // UV信息 支持TilingOffset
                return o ;
            }
            //色彩输出结构
            float4 frag(VertexOutput i) : COLOR 
            {
                half4 var_MainTex = tex2D(_MainTex, i.uv);      // 采样贴图 RGB颜色 A透贴
                half3 finalRGB = var_MainTex.rgb;
                half opacity = var_MainTex.a * _Opacity;
                return half4(finalRGB * opacity ,opacity );                // 返回值
            }
            ENDCG
        }

        Pass {
            Name "FORWARD_AD"
            Tags {
                "LightMode"="ForwardBase"
            }

            Blend One One           //更浅了
            

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma target 3.0
            UNITY_INSTANCING_BUFFER_START( Props )
               // UNITY_DEFINE_INSTANCED_PROP( float4, _Color)
            UNITY_INSTANCING_BUFFER_END( Props )
            

            uniform sampler2D _Sequence ; uniform float4 _Sequence_ST;
            uniform float _RowCount ;
            uniform float _ColCount ;
            uniform float _Speed   ;

            //输入结构
            struct VertexInput
            {
                float4 vertex : POSITION;
                float2 uv0 : TEXCOORD0;
                float3 normal : NORMAL;  //挤出用
            };
            //顶点输出结构
            struct VertexOutput 
            {
                float4 pos : SV_POSITION;
                float2 uv0 : TEXCOORD0;
            };
            //输出结构>>>顶点shader>>>输出结构
            VertexOutput vert (VertexInput v) 
            {
                v.vertex.xyz += v.normal *0.01;   //对象顶点 + 对象法线方向 * 长度= 挤出的边
                VertexOutput o = (VertexOutput)0;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv0 = TRANSFORM_TEX(v.uv0,_Sequence);

                //time驱动 uv 滚动
                float id = floor(_Time.y * _Speed);

                //获得idU 和 idV ,来求得 显示uv的位置。
                float idV = floor( id / _ColCount);//求 id 在 idV的行数(商)
                float idU = id - idV * _ColCount;//求id在 idU的列数(余) 

                //求U轴,和 V轴 的步幅
                float stepU = 1 / _ColCount;    //U轴一步幅 长度
                float stepV = 1 / _RowCount;    //V轴一步幅 长度

                //划定初始UV位置   先缩小,在移动;
                float2 initUV = o.uv0 * float2(stepU,stepV)+float2(0,stepV *(_RowCount-1));//UV缩小+位移
                
                //将 大小,位置 偏移 后的 initUV 赋值给 o.uv0;
                o.uv0 = initUV + float2(idU * stepU , -idV * stepV);

                return o ;
            }
            //色彩输出结构
            float4 frag(VertexOutput i) : COLOR 
            {
                float4 var_SequenceTex = tex2D(_Sequence,i.uv0);
                float3 finalRGB = var_SequenceTex.rgb ;
                float opacity = var_SequenceTex.a ;
                return float4(finalRGB*opacity,opacity);//输出最终颜色
            }
            ENDCG
        }


    }
}

9、核心代码:

 //输出结构>>>顶点shader>>>输出结构
            VertexOutput vert (VertexInput v) 
            {
                v.vertex.xyz += v.normal *0.01;   //对象顶点 + 对象法线方向 * 长度= 挤出的边
                VertexOutput o = (VertexOutput)0;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv0 = TRANSFORM_TEX(v.uv0,_Sequence);

                //time驱动 uv 滚动
                float id = floor(_Time.y * _Speed);

                //获得idU 和 idV ,来求得 显示uv的位置。
                float idV = floor( id / _ColCount);//求 id 在 idV的行数(商)
                float idU = id - idV * _ColCount;//求id在 idU的列数(余) 

                //求U轴,和 V轴 的步幅
                float stepU = 1 / _ColCount;    //U轴一步幅 长度
                float stepV = 1 / _RowCount;    //V轴一步幅 长度

                //划定初始UV位置   先缩小,在移动;
                float2 initUV = o.uv0 * float2(stepU,stepV)+float2(0,stepV *(_RowCount-1));//UV缩小+位移
                
                //将 大小,位置 偏移 后的 initUV 赋值给 o.uv0;
                o.uv0 = initUV + float2(idU * stepU , -idV * stepV);

                return o ;
            }

二、极坐标:

1、案例展示:

2、实现思路:

1、顶点shader下:传递 UV 和顶点色彩。

传递UV+传递顶点色

那为什么在顶点shader中只传 了 这两个就变成极坐标了呢?

那是因为我们在像素shader中做了极坐标的处理,那么为什么是在像素shader中做处理,而不是在顶点shader下做处理呢?

因为像素shader的UV是 在顶点shader中拿出UV做线性插值的, 线性插值,在笛卡尔坐标(横平竖直)UV是满足的,但是极坐标并不是笛卡尔坐标,是不能做线性插值的,所以你的极坐标的换算,只能在像素shader下去做。

2、极坐标(高中知识),两个量,确定一个位置(角度 距中心长度)。

θ(theta,角度) = Y / X ;

极坐标角度的正切值 = Y / X ,相当于 U / V的值,


过程:

1、换算UV中心点:

如何换算 UV呢,首先减去0.5,这是什么意思呢?

就相当于UV坐标的原点往上挪0.5,0.5到图片中心位置(因为默认图片中心位置都在左下角),

i.uv = i.uv - float2(0.5,0.5); //原点中心位置 右上挪到中间。

2、极坐标(高中知识),两个量,确定一个位置(角度 距中心长度)。

                                                关于 三角函数 三个角的求解 公式

1、角度: θ(theta,角度) = Y / X

极坐标角度的正切值(atan2) = Y / X ,相当于 U / V的值(也就是 Tanθ 直角 ∟)

知道正切值 怎么换算 角度呢?

θ(theta角度) = atan2(i.uv.y , i.uv.x); // 笛卡尔坐标 换算成 极坐标 ;

(角度距离)

float theta = atan2(i.uv.y , i.uv.x);

1.1、remap纠正 值域 -1 到 1 为 正值 0 到 1:

theta = theta / 3.1415926 * 0.5 +0.5;

得出角度,角度θ的值域是 -Π,到 Π,那么现在要把值域换算成采样贴图的区间,需要把值域从,-Π,到拍转移到 0 到 1。

2、长度获取:

//距离中心点,任意一点 的长度。然后+上计时器 * 3 的速度。

float r = length(i.uv) + frac(_Time.x * 3);

3、获取 角度 和 长度 后,传递给 i.uv;

i.uv = float2 (theta , r); //作为坐标,传递给uv

这个时候就完成了 极坐标 到 笛卡尔坐标(横平竖直)的映射。

4、i.color 渐变 与 主贴图 混合为 主图渐变效果。

3、代码实现:

Shader "Unlit/Sc018_PolarCoord01"
{
    Properties
    {
        _MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
        _Opacity ("透明度", range(0, 1)) = 0.5
        [HDR]_Color("色彩",color)=(1,1,1,1)
    }
    SubShader
    {
        Tags { 
                "Queue"="Transparent"
            }
        LOD 100

         Pass {
            Name "FORWARD"
            Tags {

            }

            Blend One OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "UnityCG.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma target 3.0
            UNITY_INSTANCING_BUFFER_START( Props )
               // UNITY_DEFINE_INSTANCED_PROP( float4, _Color)
            UNITY_INSTANCING_BUFFER_END( Props )
            
             uniform sampler2D _MainTex; uniform float4 _MainTex_ST;
            uniform half _Opacity;
            uniform float3 _Color;

            //输入结构
            struct VertexInput
            {
                 float4 vertex : POSITION;       // 顶点位置 总是必要
                float2 uv : TEXCOORD0;          // UV信息 采样贴图用
                float4 color : COLOR;           //顶点色声明
            };
            //顶点输出结构
            struct VertexOutput 
            {
                 float4 pos : SV_POSITION;       // 顶点位置 总是必要
                float2 uv : TEXCOORD0;          // UV信息 采样贴图用
                float4 color : COLOR;           //顶点色(因为像素中也用到了,所以输出也要有)
            };
            //输出结构>>>顶点shader>>>输出结构
            VertexOutput vert (VertexInput v) 
            {
                 VertexOutput o = (VertexOutput)0;
                    o.pos = UnityObjectToClipPos( v.vertex);    // 顶点位置 OS>CS
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);       // UV信息 支持TilingOffset
                    o.color = v.color;
                return o ;
            }
            //色彩输出结构
            float4 frag(VertexOutput i) : COLOR 
            {
                i.uv = i.uv - float2(0.5,0.5);          //原点中心位置 右上挪到中间。
                float theta = atan2(i.uv.y,i.uv.x);     //笛卡尔坐标  换算成  极坐标.
                theta = theta / 3.1415926  * 0.5 +0.5;  //remap纠正 值域 -1 到 1  为 正值 0 到 1:
                float r = length(i.uv) + frac(_Time.x * 3);   //距离中心点,任意一点 的长度。然后+上计时器 * 3 的速度。
                i.uv = float2(theta,r);                 //获取 角度 和 长度 后,传递给 i.uv;


                half4 var_MainTex = tex2D(_MainTex, i.uv);      // 采样贴图 RGB颜色 A透贴
                half3 finalRGB = (1-var_MainTex.rgb) * _Color;  // 给贴图增加控制色
                half opacity = (1-var_MainTex.r) * _Opacity * i.color.r;    //i.color为渐变,渐变和贴图相乘
                return half4(finalRGB * opacity ,opacity );                // 返回值
                //return float4 ((1-var_MainTex.rgb)*i.color.r,0);
                //return  float4 (i.color.rgb * _Color,0);
            }
            ENDCG
        }
    }
}

4、核心代码

 //色彩输出结构
            float4 frag(VertexOutput i) : COLOR 
            {
                i.uv = i.uv - float2(0.5,0.5);          //原点中心位置 右上挪到中间。
                float theta = atan2(i.uv.y,i.uv.x);     //笛卡尔坐标  换算成  极坐标.
                theta = theta / 3.1415926  * 0.5 +0.5;  //remap纠正 值域 -1 到 1  为 正值 0 到 1:
                float r = length(i.uv) + frac(_Time.x * 3);   //距离中心点,任意一点 的长度。然后+上计时器 * 3 的速度。
                i.uv = float2(theta,r);                 //获取 角度 和 长度 后,传递给 i.uv;


                half4 var_MainTex = tex2D(_MainTex, i.uv);      // 采样贴图 RGB颜色 A透贴
                half3 finalRGB = (1-var_MainTex.rgb) * _Color;  // 给贴图增加控制色
                half opacity = (1-var_MainTex.r) * _Opacity * i.color.r;    //i.color为渐变,渐变和贴图相乘
                return half4(finalRGB * opacity ,opacity );                // 返回值
            }

三、分享:

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。