1、代码实现:
1.1、声明移动范围,移动速度。
_MoveRange ("移动范围", range(0.0, 3.0)) = 1.0
_MoveSpeed ("移动速度", range(0.0, 3.0)) = 1.0
顶点shader中对,顶点位置做预处理xyz,用到的方法需要在外部声明。
// 声明常量 Π 的2 倍 =6.283185
#define TWO_PI 6.283185
这里的 TOW_PI为常量,就是类似Π = 3.1415926这些公认的数值.
这里可以用 中间测试环节,测试 顶点动画方法:
vertex.y顶点的Y轴 加一个 移动范围,可以对顶点的移动范围做控制。
vertex.y += _MoveSpeed;
ok现在我们想让他自己上下起伏,就用到了正弦运动sin
vertex.y += sin(_Time.z) * _MoveSpeed;
往期课程中,不同机型长时间运行,会出现混乱的效果,所以这里要取余frac(),但是取余后会出现顶点一抖一抖的状态,这是为什么呢?
这是因为曲线在运动到0.8的时候就取余了,然而我们需要他走完一个完整的三元函数波的进程。
而上图中一个完整的曲线进程长度是多少呢?,是2倍的Π(3.1415926...)。
所以这里就引出了我们需要 规定一个常量了。
// 顶点动画方法
void Translation (inout float3 vertex) {
vertex.y += _MoveRange * sin(frac(_Time.z * _MoveSpeed) * TWO_PI);
}
2、核心代码分析:
回顾:
首先 时间 * 时间流动的速度 这样可以控制 动画快慢,
然后为了保证浮点精度对结果取余(frac)
然后让时间的流动,0-1的区间,但我们外面用的是一个sin函数,所以0-1是不行的,要让里面的值是6.283185. 所以要 *2Π,然后用周期性阔上,这样就开始做周期性波动。 得到周期性的波动后,我们想要控制他的波动范围,所以我们乘了一个范围的变量。这样我们就得到一个上下起伏的偏移值,是偏移值,不是Y轴的数值,我们要把偏移的数值,加到原有的Y轴上去,就完成了平移顶点动画的过程。
示例代码:
Shader "AP01/L19/Translation" {
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
_MoveRange ("移动范围", range(0.0, 3.0)) = 1.0
_MoveSpeed ("移动速度", range(0.0, 3.0)) = 1.0
}
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 float4 _MainTex_ST;
uniform half _Opacity;
uniform float _MoveRange;
uniform float _MoveSpeed;
// 输入结构
struct VertexInput {
float4 vertex : POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输出结构
struct VertexOutput {
float4 pos : SV_POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 声明常量
#define TWO_PI 6.283185
// 顶点动画方法
void Translation (inout float3 vertex) {
vertex.y += _MoveRange * sin(frac(_Time.z * _MoveSpeed) * TWO_PI);
}
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
Translation(v.vertex.xyz);
o.pos = UnityObjectToClipPos(v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 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
}
}
}
1、代码实现:
1、AB为模板,改名,并追加面板参数。
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
_ScaleRange ("缩放范围", range(0.0, 0.5)) = 0.2
_ScaleSpeed ("缩放速度", range(0.0, 3.0)) = 1.0
和顶点位置变化相似,只是前缀变成了缩放。
2、和顶点shader一样,在做一般操作之前对顶点信息做一个修改。
和上个案例相比,一个是名字变了,另一个是 内容变了,因为对平移的修改是 偏移 + 到Y轴上去,
缩放,一般是用 乘 ,我们算出他 缩放的比例,例如缩放原先大小的 120%,或者70%,我们把这样的假设区间,做一个周期性的变化,再把它乘上去,平移是( + ), 缩放是( * ) ,这个是两者不同的地方。
// 顶点动画方法
void Scaling (inout float3 vertex) {
vertex.y *= _ScaleRange * sin(frac(_Time.z * _ScaleSpeed) * TWO_PI);
}
//输入结构:
Scaling(v.vertex.xyz);
这个时候可以输出测试看下效果:
这是为什么呢,因为代码平移中,只对Y轴做了操作,现在缩放要对XYZ都做操作,(xyz都做操作就不需要.xyz了,也可以省略)这时,修改xyz后,我们可以继续输出看下。
vertex.xyz *= _ScaleRange * sin(frac(_Time.z * _ScaleSpeed) * TWO_PI);
这个时候我们发现,现在虽然有了缩放,但是他取了负向的值,我们不想让他有负的,不想让他在0的缩放比例上来回浮动,因为在0的区间上,他可能会取正数,也可能取负数,我们希望他在原有大小基础上做浮动,在100%的大小上做浮动,那么这个改变也很简单,我们有个基础值,100%那就1嘛,在加上波动值,这个事情就完事儿了。
vertex.xyz *= 1.0 + _ScaleRange * sin(frac(_Time.z * _ScaleSpeed) * TWO_PI);
2、核心代码分析:
1、代码实现:
1、AB做为模板。
2、声明旋转参数:
_RotateRange ("旋转范围", range(0.0, 45.0)) = 20.0
_RotateSpeed ("旋转速度", range(0.0, 3.0)) = 1.0
3、声明常量2Π;
4、顶点动画方法:
这里的 思路是分两步走,计算偏移量的,归到计算偏移量,应用偏移量的,归到应用偏移量。
那些部分是 计算偏移量呢?首先我们要知道对旋转来说,偏移量意味着什么?
对平移来说,他的偏移量是距离,
对旋转来说,他的偏移量是角度。
//计算偏移量:
所以我们要把偏移的角度计算出来,他的偏移量还是动态的需要加上_Time.
角度 = 旋转范围 * 正弦(取余*(Time.z * 旋转速度) * 2Π);
角度= 旋转范围 * -1到1的动态数值。
float angleY = _RotateRange * sin(frac(_Time.z * _RotateSpeed) * TWO_PI);
应用偏移量:
角度转成弧度,是为了方便后面三角函数的运算,因为三角函数取的参是 弧度,不是角度。
float radY = radians(angleY); //角度转弧度
下面的是计算 sin 和 cos 值,会用sincos()这个方法把sinY,cosY两个参数一起求出来。
float sinY, cosY = 0;
sincos(radY, sinY, cosY); // 通过radY弧度求出(sinY ,和 cosY)参数值
//sincos(radY,输入Y,输出Y)
Sincos()API文档解释:
sincos - Win32 apps | Microsoft Learn
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY);
// 顶点动画方法
void Rotation (inout float3 vertex) {
//此为计算偏移量的代码
float angleY = _RotateRange * sin(frac(_Time.z * _RotateSpeed) * TWO_PI);
//以下为应用偏移量的代码
float radY = radians(angleY); //角度转弧度
float sinY, cosY = 0;
sincos(radY, sinY, cosY);
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY
);
2、核心代码分析:
ok那么上述的内容,为什么能让顶点做旋转呢?
我们详细讲一下原理:
前置知识:就是向量的 加减法 。
假设有一个向量a 和 b, a 是从A 指到B 的向量
b是从A指到D的向量
那a + b 和 a - b 分别是什么呢?
今天的知识,只需要知道 a + b 和 a - b就可以了。
A+B 相当于 把 A B 平移到 D C的位置,这样就围成一个平行四边形。
那a+b等于什么呢?
a+b等于 A指向C的这条 向量。 相当于两个向量求和的话,就是把他拼成平行四边形,平行四边形对角一连线,就是他们的和,他们另外的一个对角,就是他们的差。
现在我们的模型是沿Y轴旋转,沿Y轴旋转,相当于Y轴是没有变化的,变化的只有 x,z轴。
然后旋转一个θ(角度)后,就旋转到红色标注的点上了,我们已知原始点 是x,z,旋转θ角度之后,问,红色的这个坐标点是多少?
如果我们知道这个坐标值,跟旋转角θ之间的对应关系,方程列出来,那么我们的代码就可以写了。
我们第一步怎么来咧?
先把黑色的这个点,理解为一个向量,从原点指向黑点的一个向量,这个向量的值,也是他的坐标值。
然后把这个向量值根据x轴,y轴。原始的向量,等于 x 和 y相加。
这两个向量,也同样旋转Θ角度之后,就变成了红色的向量,那么,红色的xy相加就等于红色原点的位置,因为我们可以视为,整个坐标系旋转了一个θ角,然后就变成了下图的样子了,之前是个长方形,旋转之后也是个长方形,我们现在要 求 红色原点的位置,那我们知道 红色的 x轴和红色的y轴就可以了。那么红色x,y怎么求?
首先看红色x轴上,x轴的长度,虽然红色线现在是斜线,但是点的横向移动是不改变他的长度的。这里引入三角函数的一些知识。cos = 邻边 / 斜边。 求那个角,就按照下图 套公式即可。
那么 红色的x轴的位置就是,(x*cosθ , x * sinθ)
红色的z轴的位置就是,(-z * sinθ , z * cosθ)
现在 两个红色分量加在一起 就求出 红点位置了。
( x * cosθ - z * sinθ , x * sinθ + z * cos θ )
示例代码:
Shader "AP01/L19/Rotation" {
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
_RotateRange ("旋转范围", range(0.0, 45.0)) = 20.0
_RotateSpeed ("旋转速度", range(0.0, 3.0)) = 1.0
}
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 float4 _MainTex_ST;
uniform half _Opacity;
uniform float _RotateRange;
uniform float _RotateSpeed;
// 输入结构
struct VertexInput {
float4 vertex : POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 输出结构
struct VertexOutput {
float4 pos : SV_POSITION; // 顶点位置 总是必要
float2 uv : TEXCOORD0; // UV信息 采样贴图用
};
// 声明常量
#define TWO_PI 6.283185
// 顶点动画方法
void Rotation (inout float3 vertex) {
float angleY = _RotateRange * sin(frac(_Time.z * _RotateSpeed) * TWO_PI);
float radY = radians(angleY);
float sinY, cosY = 0;
sincos(radY, sinY, cosY);
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY
);
}
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
Rotation(v.vertex.xyz);
o.pos = UnityObjectToClipPos(v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 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
}
}
}
1、代码实现:
对模型做一个处理,去maya或者blende做定点色刷制。
这里没刷蓝色,是因为,黑色部分 实际上是 红色通道的反向。就可以少占用一个通道。
maya中顶点颜色绘制位置: 网格显示>>绘制顶点颜色工具
PS:注意 这里顶点色 添加中,色彩一定要是 纯色,否则会出现问题。
1、参数追加:
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
_ScaleParams ("天使圈缩放 X:强度 Y:速度 Z:校正", vector) = (0.2, 1.0, 4.5, 0.0)
_SwingXParams ("X轴扭动 X:强度 Y:速度 Z:波长", vector) = (1.0, 3.0, 1.0, 0.0)
_SwingZParams ("Z轴扭动 X:强度 Y:速度 Z:波长", vector) = (1.0, 3.0, 1.0, 0.0)
_SwingYParams ("Y轴起伏 X:强度 Y:速度 Z:滞后", vector) = (1.0, 3.0, 0.3, 0.0)
_ShakeYParams ("Y轴摇头 X:强度 Y:速度 Z:滞后", vector) = (20.0, 3.0, 0.3, 0.0)
2、输入输出结构 追加顶点色:
struct vertexInput{
float4 vertex : POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
}
struct vertexOutplut {
float4 vertex : POSITION;
float4 color : COLOR;
float4 uv : TEXCOORD0;
}
因为我们为模型刷 了 顶点色彩做动画遮罩,所以需要在输入结构声明 。至于输出结构中也声明定点色,是因为案例中 的光环 做了闪烁效果处理,所以输出结构,我们依然输出了顶点色。
这里的定点色闪烁也是 用了 参数传入,在传出的方式来实现的。
//顶点动画方法:
float scale = _ScaleParams.x * color.g * sim(frac (_Time.z * _ScaleParams.y) * TWO_PI );
vertex .xyz * = 1 + scale;
vertex.y -=_ScaleParams.z * scale;
这里的公式,也很眼熟, X轴摆动=x轴范围*(速度*正弦运动 + )*2Π;
float swingX = _SwingXParms.x * sin((_Time.z * _SwingXParams.y + vertex.z * _SwingXParams.z) * TWO_PI);
这里如果不加小尾巴的会直接整体横向平移,而不是摆动。
那么这里的小尾巴_SwingXParams.y + vertex.z是什么意思呢?
小尾巴和什么相关,和顶点信息的Y坐标相关,也就是和模型的高度是相关的,且模型Y轴上不同顶点的高度也是不一样的,除非是个饼,所以在不同的高度,顶点信息也是不一样的。
这个时候Y轴顶点不一样,再把它作用在sin的参数里面,最终得出的sin值也是不一样的。
不一样如何体现呢?
他会沿Y轴方向做 正弦波,也就是从上到下 的S形。
float swingZ = _SwingXParms.x * sin((_Time.z * _SwingXParams.y + vertex.y * _SwingXParams.z) * TWO_PI);
vertex.xz+=float2 (swingX,swingZ) * color.r; //xz轴位移 * 红遮罩 = 红区域摆动。
//计算偏移量:计算弧度
弧度 =弧度 * ( 1-color.g红色反向遮罩 ) sin(frac(时间 *速度 - 旋转滞后 )*2Π);
float radY = radians(_ShakeYParams.x) * (1.0 - color.r) *
sin(frac(_Time.z * _ShakeYParams.y - color.g * _ShakeYParams.z) * TWO_PI);
这里的小尾巴是什么意思呢?实际上是滞后效果。
那么怎么滞后原理是什么呢?
通过原本的旋转时间,在 减去 红色遮罩区域中的一个延迟时间。
就是时间 减去了一个值.
_Time.z * _ShakeYParams.y - color.g * _ShakeYParams.z;
//应用偏移量:应用弧度
float sinY, cosY = 0;
sincos(radY, sinY, cosY);
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY
);
//计算偏移量
Y起伏参数=起伏范围*sin(frac(时间*速度 - 滞后)*2Π);
float swingY = _SwingYParams.x *
sin(frac(_Time.z * _SwingYParams.y - color.g * _SwingYParams.z) * TWO_PI);
//应用偏移量
vertex.y += swingY;
需要想好在像素shader中应用 顶点色 。(跳转到: 输出结构>>像素下)
那么为了配合像素shader的应用使用,我们要怎么构造从顶点输出的RGB呢?
首先 声明一个亮度,对天使圈之外的亮度,他恒定保持为1, 在天使圈的范围亮度是提高的,也是带闪烁的。
亮度基础值是1对吧,因为身子是占的绝大多数,天使圈占的是小部分,然后他应该加上某个东西,因为天使圈是要提亮的也就是 1+color.g 乘 亮度倍数和 闪烁(scale是1到-1的波动值)
lightness = 亮度1 + 红遮罩 * 1 + scale * 2 ;
float lightness = 1.0 + color.g * 1.0 + scale * 2.0;
color = float3(lightness, lightness, lightness);
来到输入输出结构
输入结构下:
AnimGhost(v.vertex.xyz,v.color.rgb); //修改了v.vertex.xyz顶点位置,和定点色v.color.rbg
输出结构>>像素下:
首先我们用顶点色干嘛呢?我们用途是说把天使圈提亮,并闪烁。
天使圈在像素shader的最终输出是什么呢?
是finalRGB把,所以让 var_MainTex.rgb * i.color.RGB就完事了。
float3 finalRGB = var_MainTex.rgb * i.color.rgb; //定点色的亮度,传给 finalRGB的像素颜色中。
2、核心代码分析:
示例代码:
Shader "AP01/L19/AnimGhost" {
Properties {
_MainTex ("RGB:颜色 A:透贴", 2d) = "gray"{}
_Opacity ("透明度", range(0, 1)) = 0.5
_ScaleParams ("天使圈缩放 X:强度 Y:速度 Z:校正", vector) = (0.2, 1.0, 4.5, 0.0)
_SwingXParams ("X轴扭动 X:强度 Y:速度 Z:波长", vector) = (1.0, 3.0, 1.0, 0.0)
_SwingZParams ("Z轴扭动 X:强度 Y:速度 Z:波长", vector) = (1.0, 3.0, 1.0, 0.0)
_SwingYParams ("Y轴起伏 X:强度 Y:速度 Z:滞后", vector) = (1.0, 3.0, 0.3, 0.0)
_ShakeYParams ("Y轴摇头 X:强度 Y:速度 Z:滞后", vector) = (20.0, 3.0, 0.3, 0.0)
}
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 float4 _MainTex_ST;
uniform half _Opacity;
uniform float4 _ScaleParams;
uniform float3 _SwingXParams;
uniform float3 _SwingZParams;
uniform float3 _SwingYParams;
uniform float3 _ShakeYParams;
// 输入结构
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;
};
// 声明常量
#define TWO_PI 6.283185
// 顶点动画方法
void AnimGhost (inout float3 vertex, inout float3 color) {
// 缩放天使圈
float scale = _ScaleParams.x * color.g * sin(frac(_Time.z * _ScaleParams.y) * TWO_PI);
vertex.xyz *= 1.0 + scale;
vertex.y -= _ScaleParams.z * scale;
// 幽灵摆动
float swingX = _SwingXParams.x * sin(frac(_Time.z * _SwingXParams.y + vertex.y * _SwingXParams.z) * TWO_PI);
float swingZ = _SwingZParams.x * sin(frac(_Time.z * _SwingZParams.y + vertex.y * _SwingZParams.z) * TWO_PI);
vertex.xz += float2(swingX, swingZ) * color.r;
// 幽灵摇头
float radY = radians(_ShakeYParams.x) * (1.0 - color.r) * sin(frac(_Time.z * _ShakeYParams.y - color.g * _ShakeYParams.z) * TWO_PI);
float sinY, cosY = 0;
sincos(radY, sinY, cosY);
vertex.xz = float2(
vertex.x * cosY - vertex.z * sinY,
vertex.x * sinY + vertex.z * cosY
);
// 幽灵起伏
float swingY = _SwingYParams.x * sin(frac(_Time.z * _SwingYParams.y - color.g * _SwingYParams.z) * TWO_PI);
vertex.y += swingY;
// 处理顶点色
float lightness = 1.0 + color.g * 1.0 + scale * 2.0;
color = float3(lightness, lightness, lightness);
}
// 输入结构>>>顶点Shader>>>输出结构
VertexOutput vert (VertexInput v) {
VertexOutput o = (VertexOutput)0;
AnimGhost(v.vertex.xyz, v.color.rgb);
o.pos = UnityObjectToClipPos(v.vertex); // 顶点位置 OS>CS
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // UV信息 支持TilingOffset
o.color = v.color;
return o;
}
// 输出结构>>>像素
half4 frag(VertexOutput i) : COLOR {
half4 var_MainTex = tex2D(_MainTex, i.uv); // 采样贴图 RGB颜色 A透贴
half3 finalRGB = var_MainTex.rgb * i.color.rgb;
half opacity = var_MainTex.a * _Opacity;
return half4(finalRGB * opacity, opacity); // 返回值
}
ENDCG
}
}
}