庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)
大纲:
目录
庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)
正文:
一、走马灯:
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.vertex是4维的,v.normal是3维的,所以,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(stepU,stepV); //UV缩小
float2 initUV = o.uv * float2(stepU,stepV) + float2(0,stepU * (_RowCount-1)); //UV位移,
-1是因为序号是从0开始计数。
//将计算好的 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) 步幅长度限定出范围。
float2 initUV = o.uv * float2 (stepU,stepV);
然后设置初始位置:
因为程序运行贴图的机制,贴图原点通常在左下角,而不是左上角,所以这里需要将初始UV上移2格,也就是V轴上移2格。
float2 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;
Π的含义:1Π = 180°. 2Π=360°
Π/6=30º = 180 / 6 = 30º
得出角度,角度θ的值域是 -Π,到 Π,那么现在要把值域换算成采样贴图的区间,需要把值域从,-Π,到拍转移到 0 到 1。
如果没有这段代码会出现如下图状态的接缝问题:
中间测试输出可在 像素shader 中 用下列代码 观察:
return float4(i.color.rgb,opacity);
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 ); // 返回值
}