53.动态效果-顶点动画-顶点动画的注意事项-阴影
53.1 知识点
知识回顾:如何让物体投射阴影
对应知识点:Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影
物体向其它物体投射阴影的关键点是:
实现 LightMode 为 ShadowCaster 的 Pass:
物体需要实现 LightMode(灯光模式)为 ShadowCaster(阴影投射)的 Pass(渲染通道),这样该物体才能参与到光源的阴影映射纹理计算中。编译指令、内置文件和关键宏:
编译指令:
#pragma multi_compile_shadowcaster
该指令告诉 Unity 编译器生成多个着色器变体,用于支持不同类型的阴影(如 SM,SSSM 等等),确保着色器能够在所有可能的阴影投射模式下正确渲染。内置文件:
#include "UnityCG.cginc"
其中包含了关键的阴影计算相关宏。三个关键宏:
V2F_SHADOW_CASTER:
顶点到片元着色器阴影投射结构体数据宏。它定义了一些标准的成员变量,用于在阴影投射路径中传递顶点数据到片元着色器,主要在结构体中使用。TRANSFER_SHADOW_CASTER_NORMALOFFSET:
转移阴影投射器法线偏移宏。用于在顶点着色器中计算和传递阴影投射所需的变量,主要做了以下几项工作:- 将对象空间的顶点位置转换为裁剪空间的位置;
- 考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时;
- 传递顶点的投影空间位置,用于后续的阴影计算。
SHADOW_CASTER_FRAGMENT:
阴影投射片元宏,将深度值写入到阴影映射纹理中,主要在片元着色器中使用。
在 Shader 中实现代码
- 由于投射阴影相关的代码较为通用,因此建议直接通过 FallBack 调用 Unity 中默认 Shader 中的相关代码,而无需自行实现。
知识点回顾:透明度混合物体投射阴影
对应知识点:Lesson19_阴影_透明物体阴影_透明度混合物体阴影
由于透明度混合需要关闭深度写入,而阴影相关的处理需要用到深度值参与计算。因此,Unity 中从性能角度考虑(计算半透明物体的阴影效果相对复杂),所有的内置半透明 Shader 都不会产生阴影效果(如 Transparent/VertexLit)。
透明混合 Shader 想要投射阴影时:
- 不管在 FallBack 中写入哪种自带的半透明混合 Shader,都不会有投射阴影的效果,因为深度不会写入。
透明混合 Shader 想要接受阴影时:
- Unity 内置的阴影接收计算相关宏不会计算处理透明混合 Shader。
- 设置混合因子为半透明效果(Blend SrcAlpha OneMinusSrcAlpha)的 Shader 时,因透明混合物体的深度值和遮挡关系无法直接用传统的深度缓冲和阴影贴图来处理。
结论:
- Unity 中不会直接为透明度混合 Shader 处理阴影。
强制投射:
- 在 FallBack 中设置一个非透明 Shader(例如 VertexLit、Diffuse 等),并使用其中的灯光模式设置为阴影投射的渲染通道,参与阴影映射纹理的计算,将该物体当成实体物体处理。
顶点动画物体投射阴影
我们可以为有顶点动画的物体使用 LightMode 为 ShadowCaster 的 Pass,这样它便能投射阴影。但是如果直接使用内置的这种 Pass(通过 FallBack 寻找到的默认 Shader),投射的阴影会是不正确的,因为默认 Pass 中并不会使用新的顶点位置来投射阴影,而是按照模型原来的顶点位置来计算阴影。
- 举例:
- 新建一个 Shader,复制
Lesson49_动态效果_顶点动画_滚动的2D河流_具体实现
中的流动的 2D 河流的 Shader 代码。 - 为其加上一个不透明的 FallBack Shader,如
VertexLit
。 - 在 Mesh Renderer 中开启双面投射阴影。
- 新建一个 Shader,复制
Shader "Unlit/Lesson53_2DWater"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
//波动幅度
_WaveAmplitude("WaveAmplitude", Float) = 1
//波动频率
_WaveFrequency("WaveFrequency", Float) = 1
//波长的倒数
_InvWaveLength("InvWaveLength", Float) = 1
//纹理变化速度
_Speed("Speed", Float) = 1
}
SubShader
{
// 透明 Shader 相关渲染标签 + 关闭批处理标签
Tags
{
// 设置渲染类型为透明 因为可能会在水中悬浮
"RenderType"="Transparent"
// 设置渲染队列顺序为透明队列
"Queue"="Transparent"
// 忽略投影器
"IgnoreProjector"="True"
// 关闭批处理
"DisableBatching"="True"
}
Pass
{
// 关闭深度写入
ZWrite Off
// 设置混合模式为源颜色的透明度作为混合因子,目标颜色的(1 - 源颜色透明度)作为混合因子
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 2D 纹理采样器
sampler2D _MainTex;
// 纹理的缩放和平移信息
float4 _MainTex_ST;
// 颜色值,用于调整纹理颜色
float4 _Color;
// 波浪的振幅
float _WaveAmplitude;
// 波浪的频率
float _WaveFrequency;
// 波浪的倒数波长
float _InvWaveLength;
// 纹理移动速度
float _Speed;
struct v2f
{
// 纹理坐标,用于传递给片元着色器进行纹理采样
float2 uv : TEXCOORD0;
// 顶点在裁剪空间中的位置
float4 vertex : SV_POSITION;
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 模型空间下的偏移位置
float4 offset;
// 让它在模型空间的 x 轴方向进行偏移 因为x是长的横的 基于预制体定的
// 河流公式:某轴位置偏移量 = sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
offset.x = sin(_Time.y * _WaveFrequency + appdata_base.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
// 将顶点从模型空间转换到裁剪空间,并加上偏移量
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex + offset);
// 计算纹理坐标,结合纹理的缩放和平移信息以及模型的原始纹理坐标
v2f.uv = appdata_base.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
// 根据时间和速度在垂直方向上移动纹理坐标
v2f.uv += float2(0, _Time.y * _Speed);
return v2f;
}
// 片元着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 对纹理进行采样
fixed4 color = tex2D(_MainTex, v2f.uv);
// 将采样得到的颜色的 RGB 分量乘以指定的颜色值,实现颜色调整
color.rgb *= _Color.rgb;
return color;
}
ENDCG
}
}
Fallback "VertexLit"
}
这时,我们使用该 Shader 投射出来的阴影是没有经过顶点动画变化的模型阴影,阴影效果明显不对。
让顶点动画物体投射正确的阴影
主要步骤
我们需要自定义一个 LightMode 为 ShadowCaster 的 Pass,在顶点着色器函数中进行顶点相关的计算:
- 创建
Lesson53_NoteOnVertexAnimationShadow
,复制Lesson53_2DWater
中的代码。 - 复制
Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影
中的Lesson15_OpaqueObjectCastShadows
的阴影投影 Pass。 - 在该 Pass 中加入波形频率、波长的倒数、波形幅度属性的映射。(直接复制前面的代码)
- 在该 Pass 中的顶点着色器函数中加入顶点的偏移计算,直接对模型空间中顶点进行偏移,不进行裁剪坐标空间变换和 UV 相关计算。
创建Lesson53_NoteOnVertexAnimationShadow,复制刚刚的Lesson53_2DWater
Shader "Unlit/Lesson53_NoteOnVertexAnimationShadow"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
//波动幅度
_WaveAmplitude("WaveAmplitude", Float) = 1
//波动频率
_WaveFrequency("WaveFrequency", Float) = 1
//波长的倒数
_InvWaveLength("InvWaveLength", Float) = 1
//纹理变化速度
_Speed("Speed", Float) = 1
}
SubShader
{
// 透明 Shader 相关渲染标签 + 关闭批处理标签
Tags
{
// 设置渲染类型为透明 因为可能会在水中悬浮
"RenderType"="Transparent"
// 设置渲染队列顺序为透明队列
"Queue"="Transparent"
// 忽略投影器
"IgnoreProjector"="True"
// 关闭批处理
"DisableBatching"="True"
}
Pass
{
// 关闭深度写入
ZWrite Off
// 设置混合模式为源颜色的透明度作为混合因子,目标颜色的(1 - 源颜色透明度)作为混合因子
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 2D 纹理采样器
sampler2D _MainTex;
// 纹理的缩放和平移信息
float4 _MainTex_ST;
// 颜色值,用于调整纹理颜色
float4 _Color;
// 波浪的振幅
float _WaveAmplitude;
// 波浪的频率
float _WaveFrequency;
// 波浪的倒数波长
float _InvWaveLength;
// 纹理移动速度
float _Speed;
struct v2f
{
// 纹理坐标,用于传递给片元着色器进行纹理采样
float2 uv : TEXCOORD0;
// 顶点在裁剪空间中的位置
float4 vertex : SV_POSITION;
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 模型空间下的偏移位置
float4 offset;
// 让它在模型空间的 x 轴方向进行偏移 因为x是长的横的 基于预制体定的
// 河流公式:某轴位置偏移量 = sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
offset.x = sin(_Time.y * _WaveFrequency + appdata_base.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
// 将顶点从模型空间转换到裁剪空间,并加上偏移量
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex + offset);
// 计算纹理坐标,结合纹理的缩放和平移信息以及模型的原始纹理坐标
v2f.uv = appdata_base.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
// 根据时间和速度在垂直方向上移动纹理坐标
v2f.uv += float2(0, _Time.y * _Speed);
return v2f;
}
// 片元着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 对纹理进行采样
fixed4 color = tex2D(_MainTex, v2f.uv);
// 将采样得到的颜色的 RGB 分量乘以指定的颜色值,实现颜色调整
color.rgb *= _Color.rgb;
return color;
}
ENDCG
}
}
Fallback "VertexLit"
}
复制Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影 中的 Lesson15_OpaqueObjectCastShadows 的 阴影投影 Pass,这是我们自己实现的阴影Pass,主要是用来计算阴影投射的
//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
// 其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f v2f;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
float4 frag(v2f v2f):SV_Target
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
ENDCG
}
在阴影Pass中加入 波形频率、波长的倒数、波形幅度 属性的映射
// 波浪的振幅
float _WaveAmplitude;
// 波浪的频率
float _WaveFrequency;
// 波浪的倒数波长
float _InvWaveLength;
在阴影Pass中的顶点着色器函数中 加入顶点的偏移计算(直接复制前面的代码),注意我们直接对模型空间中顶点进行偏移,不用进行裁剪坐标空间变换以及UV相关计算,修改转换部分
v2f vert(appdata_base v)
{
v2f v2f;
// 模型空间下的偏移位置
float4 offset;
// 让它在模型空间的 x 轴方向进行偏移 因为x是长的横的 基于预制体定的
// 河流公式:某轴位置偏移量 = sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
offset.x = sin(_Time.y * _WaveFrequency + v.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
// 由于v2f结构体相关的转换会在TRANSFER_SHADOW_CASTER_NORMALOFFSET宏中处理
// 注意我们不需要自己调UnityObjectToClipPos 注释
// 我们只需要设置好模型空间下的顶点 让TRANSFER_SHADOW_CASTER_NORMALOFFSET使用即可
// 不需要自己调UnityObjectToClipPos转换到裁剪空间
// // 将顶点从模型空间转换到裁剪空间,并加上偏移量
// v2f.vertex = UnityObjectToClipPos(v.vertex + offset);
//设置好模型空间下的顶点 让TRANSFER_SHADOW_CASTER_NORMALOFFSET使用 无需自己转换
v.vertex = v.vertex + offset;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
创建材质赋值,可以看到阴影会随着顶点的移动正常投射了
总结
为了让带有顶点动画的对象正确地投射阴影,我们需要自定义阴影投射的 Pass(渲染通道),并在其中加入顶点变换计算。
53.2 知识点代码
Lesson53_动态效果_顶点动画_顶点动画的注意事项_阴影.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Lesson53_动态效果_顶点动画_顶点动画的注意事项_阴影 : MonoBehaviour
{
void Start()
{
#region 知识回顾 如何让物体投射阴影
//对应知识点 Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影
//物体向其它物体投射阴影的关键点是:
//1. 需要实现 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道)
// 这样该物体才能参与到光源的阴影映射纹理计算中
//2. 一个编译指令,一个内置文件,三个关键宏
// 编译指令:
// #pragma multi_compile_shadowcaster
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 内置文件:
// #include "UnityCG.cginc"
// 其中包含了关键的阴影计算相关的宏
// 三个关键宏:
// 2-1.V2F_SHADOW_CASTER
// 顶点到片元着色器阴影投射结构体数据宏
// 这个宏定义了一些标准的成员变量
// 这些变量用于在阴影投射路径中传递顶点数据到片元着色器
// 我们主要在结构体中使用
// 2-2.TRANSFER_SHADOW_CASTER_NORMALOFFSET
// 转移阴影投射器法线偏移宏
// 用于在顶点着色器中计算和传递阴影投射所需的变量
// 主要做了
// 2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
// 2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
// 2-2-3.传递顶点的投影空间位置,用于后续的阴影计算
// 我们主要在顶点着色器中使用
// 2-3.SHADOW_CASTER_FRAGMENT
// 阴影投射片元宏
// 将深度值写入到阴影映射纹理中
// 我们主要在片元着色器中使用
//3.利用这些内容在Shader中实现代码
//由于投射阴影相关的代码较为通用
//因此建议大家不用自己去实现相关Shader代码
//直接通过FallBack调用Unity中默认Shader中的相关代码即可
#endregion
#region 知识点回顾 透明度混合物体投射阴影
//对应知识点 Lesson19_阴影_透明物体阴影_透明度混合物体阴影
//由于透明度混合需要关闭深度写入
//而阴影相关的处理需要用到深度值参与计算
//因此Unity中从性能方面考虑(要计算半透明物体的的阴影表现效果是相对复杂的)
//所有的内置半透明Shader都不会产生阴影效果(比如 Transparent/VertexLit)
//因此
//2-1.透明混合Shader想要 投射阴影时
// 不管你在FallBack中写入哪种自带的半透明混合Shader
// 都不会有投射阴影的效果,因为深度不会写入
//2-2.透明混合Shader想要 接受阴影时
// Unity内置关于阴影接收计算的相关宏
// 不会计算处理 透明混合Shader
// 混合因子 设置为半透明效果(Blend SrcAlpha OneMinusSrcAlpha)的Shader
// 因为透明混合物体的深度值和遮挡关系无法直接用传统的深度缓冲和阴影贴图来处理
//结论:
//Unity中不会直接为透明度混合Shader处理阴影
//强制投射:
//在FallBack中设置一个非透明Shader,比如VertexLit、Diffuse等
//用其中的灯光模式设置为阴影投射的渲染通道来参与阴影映射纹理的计算
//把该物体当成一个实体物体处理
#endregion
#region 知识点一 顶点动画物体投射阴影
//我们可以为有顶点动画的物体 使用 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道)
//这样它便能投射阴影
//但是如果我们直接使用内置的这种Pass(默认Shader中的,通过FallBack寻找到的)
//投射的阴影会是不正确的,因为默认Pass当中并不会使用新的顶点位置来投射
//而是按照模型原来的顶点位置来计算阴影的
//举例:
//1.新建一个Shader,复制 Lesson49_动态效果_顶点动画_滚动的2D河流_具体实现 流动的2D河流的Shader代码 Lesson49_2DWater
//2.为其加上一个不透明的FallBackShader 比如VertexLit
//3.在Mesh Renderer中开启双面投射阴影
//这时我们使用该Shader投射出来的阴影是没有经过顶点动画变化的模型阴影 阴影想效果是明显不对的
#endregion
#region 知识点二 让顶点动画物体投射正确的阴影
//我们需要自定义一个LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道)
//在顶点着色器函数中进行顶点相关的计算
//1.创建Lesson53_NoteOnVertexAnimationShadow,复制刚刚的Lesson53_2DWater
//复制Lesson15_阴影_不透明物体阴影_让不透明物体投射阴影 中的 Lesson15_OpaqueObjectCastShadows 的 阴影投影 Pass
//阴影投影 Pass主要是用来计算阴影映射纹理
//2.在该Pass中加入 波形频率、波长的倒数、波形幅度 属性的映射
//3.在该Pass中的顶点着色器函数中 加入顶点的偏移计算(直接复制前面的代码)
//4.直接对模型空间中顶点进行偏移,不用进行裁剪坐标空间变换以及UV相关计算
#endregion
#region 总结
//想要让带有顶点动画的对象产生正确的阴影
//我们需要自定义 投射阴影的Pass(渲染通道)
//在其中加入对顶点的变换计算即可
#endregion
}
}
Lesson53_2DWater.shader
Shader "Unlit/Lesson53_2DWater"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
//波动幅度
_WaveAmplitude("WaveAmplitude", Float) = 1
//波动频率
_WaveFrequency("WaveFrequency", Float) = 1
//波长的倒数
_InvWaveLength("InvWaveLength", Float) = 1
//纹理变化速度
_Speed("Speed", Float) = 1
}
SubShader
{
// 透明 Shader 相关渲染标签 + 关闭批处理标签
Tags
{
// 设置渲染类型为透明 因为可能会在水中悬浮
"RenderType"="Transparent"
// 设置渲染队列顺序为透明队列
"Queue"="Transparent"
// 忽略投影器
"IgnoreProjector"="True"
// 关闭批处理
"DisableBatching"="True"
}
Pass
{
// 关闭深度写入
ZWrite Off
// 设置混合模式为源颜色的透明度作为混合因子,目标颜色的(1 - 源颜色透明度)作为混合因子
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 2D 纹理采样器
sampler2D _MainTex;
// 纹理的缩放和平移信息
float4 _MainTex_ST;
// 颜色值,用于调整纹理颜色
float4 _Color;
// 波浪的振幅
float _WaveAmplitude;
// 波浪的频率
float _WaveFrequency;
// 波浪的倒数波长
float _InvWaveLength;
// 纹理移动速度
float _Speed;
struct v2f
{
// 纹理坐标,用于传递给片元着色器进行纹理采样
float2 uv : TEXCOORD0;
// 顶点在裁剪空间中的位置
float4 vertex : SV_POSITION;
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 模型空间下的偏移位置
float4 offset;
// 让它在模型空间的 x 轴方向进行偏移 因为x是长的横的 基于预制体定的
// 河流公式:某轴位置偏移量 = sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
offset.x = sin(_Time.y * _WaveFrequency + appdata_base.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
// 将顶点从模型空间转换到裁剪空间,并加上偏移量
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex + offset);
// 计算纹理坐标,结合纹理的缩放和平移信息以及模型的原始纹理坐标
v2f.uv = appdata_base.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
// 根据时间和速度在垂直方向上移动纹理坐标
v2f.uv += float2(0, _Time.y * _Speed);
return v2f;
}
// 片元着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 对纹理进行采样
fixed4 color = tex2D(_MainTex, v2f.uv);
// 将采样得到的颜色的 RGB 分量乘以指定的颜色值,实现颜色调整
color.rgb *= _Color.rgb;
return color;
}
ENDCG
}
}
Fallback "VertexLit"
}
Lesson53_NoteOnVertexAnimationShadow.shader
Shader "Unlit/Lesson53_NoteOnVertexAnimationShadow"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("Color", Color) = (1,1,1,1)
//波动幅度
_WaveAmplitude("WaveAmplitude", Float) = 1
//波动频率
_WaveFrequency("WaveFrequency", Float) = 1
//波长的倒数
_InvWaveLength("InvWaveLength", Float) = 1
//纹理变化速度
_Speed("Speed", Float) = 1
}
SubShader
{
// 透明 Shader 相关渲染标签 + 关闭批处理标签
Tags
{
// 设置渲染类型为透明 因为可能会在水中悬浮
"RenderType"="Transparent"
// 设置渲染队列顺序为透明队列
"Queue"="Transparent"
// 忽略投影器
"IgnoreProjector"="True"
// 关闭批处理
"DisableBatching"="True"
}
Pass
{
// 关闭深度写入
ZWrite Off
// 设置混合模式为源颜色的透明度作为混合因子,目标颜色的(1 - 源颜色透明度)作为混合因子
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
// 2D 纹理采样器
sampler2D _MainTex;
// 纹理的缩放和平移信息
float4 _MainTex_ST;
// 颜色值,用于调整纹理颜色
float4 _Color;
// 波浪的振幅
float _WaveAmplitude;
// 波浪的频率
float _WaveFrequency;
// 波浪的倒数波长
float _InvWaveLength;
// 纹理移动速度
float _Speed;
struct v2f
{
// 纹理坐标,用于传递给片元着色器进行纹理采样
float2 uv : TEXCOORD0;
// 顶点在裁剪空间中的位置
float4 vertex : SV_POSITION;
};
// 顶点着色器函数
v2f vert(appdata_base appdata_base)
{
v2f v2f;
// 模型空间下的偏移位置
float4 offset;
// 让它在模型空间的 x 轴方向进行偏移 因为x是长的横的 基于预制体定的
// 河流公式:某轴位置偏移量 = sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
offset.x = sin(_Time.y * _WaveFrequency + appdata_base.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
// 将顶点从模型空间转换到裁剪空间,并加上偏移量
v2f.vertex = UnityObjectToClipPos(appdata_base.vertex + offset);
// 计算纹理坐标,结合纹理的缩放和平移信息以及模型的原始纹理坐标
v2f.uv = appdata_base.texcoord * _MainTex_ST.xy + _MainTex_ST.zw;
// 根据时间和速度在垂直方向上移动纹理坐标
v2f.uv += float2(0, _Time.y * _Speed);
return v2f;
}
// 片元着色器函数
fixed4 frag(v2f v2f) : SV_Target
{
// 对纹理进行采样
fixed4 color = tex2D(_MainTex, v2f.uv);
// 将采样得到的颜色的 RGB 分量乘以指定的颜色值,实现颜色调整
color.rgb *= _Color.rgb;
return color;
}
ENDCG
}
//阴影投影 Pass 主要是用来计算阴影映射纹理
Pass
{
Tags
{
"LightMode" = "ShadowCaster"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
// 比如有些低端移动设备不支持
#pragma multi_compile_shadowcaster
// 其中包含了关键的阴影计算相关的宏
#include "UnityCG.cginc"
// 波浪的振幅
float _WaveAmplitude;
// 波浪的频率
float _WaveFrequency;
// 波浪的倒数波长
float _InvWaveLength;
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f v2f;
// 模型空间下的偏移位置
float4 offset;
// 让它在模型空间的 x 轴方向进行偏移 因为x是长的横的 基于预制体定的
// 河流公式:某轴位置偏移量 = sin(_Time.y * 波动频率 + 顶点某轴坐标 * 波长的倒数) * 波动幅度
offset.x = sin(_Time.y * _WaveFrequency + v.vertex.z * _InvWaveLength) * _WaveAmplitude;
offset.yzw = float3(0, 0, 0);
// 由于v2f结构体相关的转换会在TRANSFER_SHADOW_CASTER_NORMALOFFSET宏中处理
// 注意我们不需要自己调UnityObjectToClipPos 注释
// 我们只需要设置好模型空间下的顶点 让TRANSFER_SHADOW_CASTER_NORMALOFFSET使用即可
// 不需要自己调UnityObjectToClipPos转换到裁剪空间
// // 将顶点从模型空间转换到裁剪空间,并加上偏移量
// v2f.vertex = UnityObjectToClipPos(v.vertex + offset);
//设置好模型空间下的顶点 让TRANSFER_SHADOW_CASTER_NORMALOFFSET使用 无需自己转换
v.vertex = v.vertex + offset;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置(光源视角下的坐标),用于后续的阴影计算
//我们主要在顶点着色器中使用
//这个宏会自动使用我们传进来的v 注意v这个参数不可以改名 否则识别不了
TRANSFER_SHADOW_CASTER_NORMALOFFSET(v2f);
return v2f;
}
float4 frag(v2f v2f):SV_Target
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(v2f);
}
ENDCG
}
}
Fallback "VertexLit"
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com