庄懂的TA笔记(十八)<特效:走马灯(序列帧) + 极坐标(UV转中心点)
大纲:
一、走马灯:序列帧
双通道,双Pass
二、极坐标:
三、分享:
正文:
一、走马灯:
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]
2、初始位置偏移
idU 是我跳了几格(4), stepU步幅 是每格跳的长度(1cm)
idU * stepU = U轴向 的偏移 步数
4(偏移次数) * 1cm(偏移长度) = 4总偏移距离
公式:总偏移距离(因为要整除,准确对应图像位置) = 初始UV + float2(idU * stepU , idV * stepV);
3、代码中:获取 商数 (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的使用是代码使用中整除,取整的意思。
4、 获取 余数 (U轴行数)
求余 → idU = 序号 - 商 * 列数; 9 - 2*4 = 1 (余数)
float idU = _SequId - idV * _ColCunt;
这里取余的话就是 走公式就可以了。
5、获取步幅距离:
stepU(U轴步幅 距离) = 1(整张UV) / _ColCunt(4列数);
stepV(V轴步幅 距离) = 1(整张UV) / _RowCunt(3行数);
6、获取初始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个单位,就正好是指定的位置了.
7、将上面计算出的 UV大小,和UV初始位置,赋给 o.uv;
o.uv = initUV - float2(idU * stepU , idV * stepV);
(注意:视频中,+ 号 和 - 号,会影响 UV是上滚动,还是下滚动)
8、替换外部输入为随时间滚动:
先删除全局中的序号的变量(_SequId)
在顶点shader中,声明一个局部 _SequId变量,并和_Time.x相乘,进行滚动,这里的_Speed范围给在(range(-10,10)),这样可控制,正向滚动,和负向滚动。
float _SequId = floor(_Time.y * _Speed);
视频中引用了下图的贴图进行滚动测试,这里在删除全局_SequId变量前记得先测试滚动顺序,以免发生视频中的问题。
9、回顾总结:
3、代码实现:
下列代码中庄懂老师改变了代码的结构,做了些优化,可能与上述的思路不太一致,可以做变式训练。
Shader "AP01/L18/Sequence" {
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
_Sequence ("序列帧", 2d) = "gray"{}
_RowCount ("行数", int) = 1
_ColCount ("列数", int) = 1
_Speed ("速度", range(0.0, 15.0)) = 1
}
SubShader {
Tags {
"Queue"="Transparent" // 调整渲染顺序
"RenderType"="Transparent" // 对应改为Cutout
"ForceNoShadowCasting"="True" // 关闭阴影投射
"IgnoreProjector"="True" // 不响应投射器
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
Blend One OneMinusSrcAlpha // 修改混合方式One/SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
// 输入参数
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;
}
// 输出结构>>>像素
half4 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"
Tags {
"LightMode"="ForwardBase"
}
Blend One One // 混合方式
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
// 输入参数
uniform sampler2D _Sequence; uniform float4 _Sequence_ST;
uniform half _Opacity;
uniform half _RowCount;
uniform half _ColCount;
uniform half _Speed;
// 输入结构
struct VertexInput {
float4 vertex : POSITION; // 顶点位置 总是必要
float3 normal : NORMAL;
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输出结构
struct VertexOutput {
float4 pos : SV_POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
v.vertex.xyz += v.normal * 0.03; // 顶点位置法向挤出
o.pos = UnityObjectToClipPos(v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _Sequence); // 前置UV ST操作
float id = floor(_Time.z * _Speed); // 计算序列id
float idV = floor(id / _ColCount); // 计算V轴id
float idU = id - idV * _ColCount; // 计算U轴id
float stepU = 1.0 / _ColCount; // 计算U轴步幅
float stepV = 1.0 / _RowCount; // 计算V轴步幅
float2 initUV = o.uv * float2(stepU, stepV) + float2(0.0, stepV * (_ColCount - 1.0)); // 计算初始UV
o.uv = initUV + float2(idU * stepU, idV * stepV); // 计算序列帧UV
return o;
}
// 输出结构>>>像素
half4 frag(VertexOutput i) : COLOR {
half4 var_Sequence = tex2D(_Sequence, i.uv); // 采样贴图 RGB颜色 A透贴
half3 finalRGB = var_Sequence.rgb;
half opacity = var_Sequence.a * _Opacity;
return half4(finalRGB * opacity, opacity); // 返回值
}
ENDCG
}
}
}
①、面板参数定义:
②、输出结构:
③、顶点shader输入输出:
4、核心代码:
二、极坐标:
1、案例展示:
2、实现思路:
使用原理:
顶点shader下:传递 UV 和顶点瑟。
传递UV+传递顶点色
那为什么在顶点shader中只传 了 这两个就变成极坐标了呢?
那是因为我们在像素shader中做了极坐标的处理,那么为什么是在像素shader中做处理,而不是在顶点shader下做处理呢?
因为像素shader的UV是 在顶点shader中拿出UV做线性插值的, 线性插值,在笛卡尔坐标(横平竖直)UV是满足的,但是极坐标并不是笛卡尔坐标,是不能做线性插值的,所以你的极坐标的换算,只能在像素shader下去做。
过程:
换算UV中心点:
如何换算 UV呢,首先减去0.5,这是什么意思呢?
就相当于UV坐标的原点,往上挪0.5,0.5到图片中心位置(因为默认图片中心位置都在左下角),
实现极坐标中心定位,以图片中间滚动效果。
极坐标(高中知识),两个量,确定一个位置(角度 距中心长度)。
θ(theta,角度) = Y / X
极坐标角度的正切值 = Y / X ,相当于 U / V的值,
知道正切值 怎么换算 角度呢?
θ(theta角度) = atan2(i.uv.y , i.uv.x); // 笛卡尔坐标 换算成 极坐标
角度 ,距离
得出角度,角度的值域是 -拍,到 拍,那么现在要把值域换算成采样贴图的区间,需要把值域从,-拍,到拍转移到 0 到 1。
3、代码实现:
Shader "AP01/L18/PolarCoord" {
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
[HDR] _Color ("混合颜色", color) = (1.0, 1.0, 1.0, 1.0)
_Opacity ("透明度", range(0, 1)) = 0.5
}
SubShader {
Tags {
"Queue"="Transparent" // 调整渲染顺序
"RenderType"="Transparent" // 对应改为Cutout
"ForceNoShadowCasting"="True" // 关闭阴影投射
"IgnoreProjector"="True" // 不响应投射器
}
Pass {
Name "FORWARD"
Tags {
"LightMode"="ForwardBase"
}
Blend One OneMinusSrcAlpha // 修改混合方式
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#pragma multi_compile_fwdbase_fullshadows
#pragma target 3.0
// 输入参数
uniform sampler2D _MainTex;
uniform half _Opacity;
uniform half3 _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 = v.uv; // UV信息 支持TilingOffset
o.color = v.color;
return o;
}
// 直角坐标转极坐标方法
float2 RectToPolar(float2 uv, float2 centerUV) {
uv = uv - centerUV;
float theta = atan2(uv.y, uv.x); // atan()值域[-π/2, π/2]一般不用; atan2()值域[-π, π]
float r = length(uv);
return float2(theta, r);
}
// 输出结构>>>像素
half4 frag(VertexOutput i) : COLOR {
// 直角坐标转极坐标
float2 thetaR = RectToPolar(i.uv, float2(0.5, 0.5));
// 极坐标转纹理采样UV
float2 polarUV = float2(
thetaR.x / 3.141593 * 0.5 + 0.5, // θ映射到[0, 1]
thetaR.y + frac(_Time.x * 3.0) // r随时间流动
);
// 采样MainTex
half4 var_MainTex = tex2D(_MainTex, polarUV);
// 处理最终输出
half3 finalRGB = (1 - var_MainTex.rgb) * _Color;
half opacity = (1 - var_MainTex.r) * _Opacity * i.color.r;
// 返回值
return half4(finalRGB * opacity, opacity);
}
ENDCG
}
}
}
4、核心代码
5、代码示例: