带光照效果的旗帜Shader教程
在Unity中,你可以创建自定义着色器来赋予物体独特的外观和行为。下面是一个用于创建旗帜效果并带有光照效果的着色器示例,让我们逐步了解它的结构和功能。
Shader 文件结构
Shader "Custom/Flag with lighting"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Ambient ("Ambient", Range(0., 1.)) = 0.2
[Header(Waves)]
_WaveSpeed("Speed", float) = 0.0
_WaveStrength("Strength", Range(0.0, 1.0)) = 0.0
}
SubShader
{
// ... SubShader 内容 ...
}
}
Properties
_MainTex
: 用作主纹理的贴图。_Ambient
: 环境光的强度,范围在 0 到 1 之间。_WaveSpeed
: 波动速度。_WaveStrength
: 波动强度。
SubShader 和 Pass
SubShader
{
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
Cull Off
Pass
{
CGPROGRAM
// ... 着色器内容 ...
ENDCG
}
}
着色器主体
结构体定义
struct v2f {
float4 pos : SV_POSITION;
float4 vertex : TEXCOORD1;
float2 uv : TEXCOORD0;
};
计算光照效果
fixed4 _LightColor0;
float _Ambient;
fixed3 diffuseLambert(float3 normal) {
float diffuse = max(_Ambient, dot(normalize(normal), _WorldSpaceLightPos0.xyz));
return _LightColor0.rgb * diffuse;
}
顶点变形函数
movement
函数是这个着色器中的一个关键部分,它负责在顶点着色器中对顶点位置进行变形。让我们逐步解释 movement
函数中的逻辑:
float4 movement(float4 pos, float2 uv) {
float sinOff = (pos.x + pos.y + pos.z) * _WaveStrength;
float t = _Time.y * _WaveSpeed;
float fx = uv.x;
float fy = uv.x * uv.y;
pos.x += sin(t * 1.45 + sinOff) * fx * 0.5;
pos.y = sin(t * 3.12 + sinOff) * fx * 0.5 - fy * 0.9;
pos.z -= sin(t * 2.2 + sinOff) * fx * 0.2;
return pos;
}
每行代码的解释:
-
float sinOff = (pos.x + pos.y + pos.z) * _WaveStrength;
:- 这行代码根据顶点在 x、y、z 轴上的位置和
_WaveStrength
参数计算一个偏移量。这个偏移量用于产生波动效果。
- 这行代码根据顶点在 x、y、z 轴上的位置和
-
float t = _Time.y * _WaveSpeed;
:_Time.y
表示自场景启动后的时间,_WaveSpeed
是控制波速的参数。这行代码计算了一个与时间和波速相关的值t
。
-
float fx = uv.x;
和float fy = uv.x * uv.y;
:- 这两行代码根据顶点的纹理坐标
uv
计算出fx
和fy
,用于在后续计算中对顶点位置进行修改。
- 这两行代码根据顶点的纹理坐标
-
pos.x += sin(t * 1.45 + sinOff) * fx * 0.5;
:- 根据时间和之前计算的偏移量,以及纹理坐标
fx
,在 x 轴方向上对顶点位置进行了一个正弦函数的变换。
- 根据时间和之前计算的偏移量,以及纹理坐标
-
pos.y = sin(t * 3.12 + sinOff) * fx * 0.5 - fy * 0.9;
:- 同样根据时间、偏移量和纹理坐标
fx
和fy
,在 y 轴方向上对顶点位置进行了正弦函数的变换。
- 同样根据时间、偏移量和纹理坐标
-
pos.z -= sin(t * 2.2 + sinOff) * fx * 0.2;
:- 在 z 轴方向上,根据时间、偏移量和纹理坐标
fx
对顶点位置进行正弦函数的变换。
- 在 z 轴方向上,根据时间、偏移量和纹理坐标
movement
函数是一个用于在顶点着色器中对顶点位置进行波动和变形的函数。它利用了时间、顶点位置、纹理坐标等参数来产生动态效果,从而在着色器中模拟出旗帜飘动的效果。
顶点着色器
v2f vert(appdata_base v) {
// ... 顶点着色器逻辑 ...
}
片段着色器
fixed4 frag(v2f i) : SV_Target {
// ... 片段着色器逻辑 ...
}
代码整体汇总
- Properties 部分定义了着色器的属性,如贴图、光照参数等。
- SubShader 和 Pass 定义了着色器的渲染方式和光照模式。
- 结构体定义(
v2f
)描述了顶点着色器输出到片段着色器的数据结构。 - 计算光照效果的部分(
diffuseLambert
)根据法线和光照位置计算漫反射光照。 - 顶点变形函数(
movement
)对顶点进行波动或变形操作。 - 顶点着色器(
vert
)用于转换输入顶点并执行其他操作。 - 片段着色器(
frag
)定义了如何为每个片段(像素)生成最终的颜色输出。
完整shader
Shader "Custom/Flag with lighting"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Ambient ("Ambient", Range(0., 1.)) = 0.2
[Header(Waves)]
_WaveSpeed("Speed", float) = 0.0
_WaveStrength("Strength", Range(0.0, 1.0)) = 0.0
}
SubShader
{
Tags { "RenderType"="Opaque" "LightMode"="ForwardBase" }
Cull Off
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f {
float4 pos : SV_POSITION;
float4 vertex : TEXCOORD1;
float2 uv : TEXCOORD0;
};
fixed4 _LightColor0;
float _Ambient;
// Compute the diffuse light
fixed3 diffuseLambert(float3 normal)
{
float diffuse = max(_Ambient, dot(normalize(normal), _WorldSpaceLightPos0.xyz));
return _LightColor0.rgb * diffuse;
}
float _WaveStrength;
float _WaveSpeed;
// Deformation
float4 movement(float4 pos, float2 uv) {
float sinOff = (pos.x + pos.y + pos.z) * _WaveStrength;
float t = _Time.y * _WaveSpeed;
float fx = uv.x;
float fy = uv.x * uv.y;
pos.x += sin(t * 1.45 + sinOff) * fx * 0.5;
pos.y = sin(t * 3.12 + sinOff) * fx * 0.5 - fy * 0.9;
pos.z -= sin(t * 2.2 + sinOff) * fx * 0.2;
return pos;
}
v2f vert(appdata_base v) {
v2f o;
o.vertex = v.vertex;
o.pos = mul(UNITY_MATRIX_MVP, movement(v.vertex, v.texcoord));
o.uv = v.texcoord;
return o;
}
sampler2D _MainTex;
fixed4 frag(v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv);
// Compute the new normal;
float3 pos0 = movement(float4(i.vertex.x, i.vertex.y, i.vertex.z, i.vertex.w), i.uv).xyz;
float3 pos1 = movement(float4(i.vertex.x + 0.01, i.vertex.y, i.vertex.z, i.vertex.w), i.uv).xyz;
float3 pos2 = movement(float4(i.vertex.x, i.vertex.y, i.vertex.z + 00.1, i.vertex.w), i.uv).xyz;
// Normal in model space
float3 normal = cross(normalize(pos2 - pos0), normalize(pos1 - pos0));
// Normal in world space
float3 worldNormal = mul(normal, (float3x3) unity_WorldToObject);
col.rgb *= diffuseLambert(worldNormal);
return col;
}
ENDCG
}
}
}