Unity Shader 的渲染流程是一个从 3D 模型数据到屏幕像素的复杂转换过程,涉及多个处理阶段。理解这个流程对于创建高效且视觉出色的着色器至关重要。
阶段一:顶点着色器 处理每个顶点,进行坐标变换和数据计算
⇩
阶段二:曲面细分着色器(可选):增加模型细节,提升平滑度
⇩
阶段三:几何着色器(可选):生成新的几何图元
⇩
阶段四:光栅化阶段:将几何图元转换为像素(片元)
⇩
阶段五:片元着色器:计算每个像素的最终颜色
⇩
阶段六:输出合并阶段:处理像素的深度、模板和混合操作
一、顶点着色器(Vertex Shader)
1. 核心功能
坐标空间转换:将顶点从模型空间(Model Space)转换到裁剪空间(Clip Space)
数据准备:计算并传递后续阶段需要的数据(如纹理坐标、法线、世界坐标等)
顶点动画:实现骨骼动画、海浪起伏等顶点位置修改
2. 坐标空间转换
将顶点从模型空间(Model Space)转换到裁剪空间(Clip Space)
关键变换矩阵:
模型矩阵(Model Matrix):将顶点从模型空间转换到世界空间
视图矩阵(View Matrix):将顶点从世界空间转换到视图空间(相机空间)
投影矩阵(Projection Matrix):将顶点从视图空间转换到裁剪空间
MVP 矩阵:模型 - 视图 - 投影矩阵,一步完成从模型空间到裁剪空间的转换
模型顶点案例1:波浪变形效果
基于顶点的 XZ 坐标计算正弦波,使平面产生波浪效果。常用于模拟水面、旗帜或布料飘动。
Shader "Custom/WaveDeformation"
{
Properties
{
//基础纹理,提供模型的基本外观
_MainTex ("Texture", 2D) = "white" {}
//波浪振幅,控制波浪的高度(变形程度)
_Amplitude ("Wave Amplitude", Range(0, 5)) = 1
//波浪频率,控制波浪的密集程度
_Frequency ("Wave Frequency", Range(0, 10)) = 5
//波浪速度,控制波浪移动的快慢
_Speed ("Wave Speed", Range(0, 5)) = 2
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float _Amplitude;
float _Frequency;
float _Speed;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
// 计算波浪变形
float wave = sin(v.vertex.x * _Frequency + _Time.y * _Speed) *
sin(v.vertex.z * _Frequency + _Time.y * _Speed) *
_Amplitude;
// 应用变形到顶点位置
v.vertex.y += wave;
// 转换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
波浪变形计算
核心算法是使用两个正弦函数的乘积来生成波浪效果:
sin(v.vertex.x * _Frequency + _Time.y * _Speed)
:沿 X 轴的波浪分量
sin(v.vertex.z * _Frequency + _Time.y * _Speed)
:沿 Z 轴的波浪分量
_Amplitude
:控制波浪的高度(振幅)
时间变量
_Time.y
:Unity 内置的时间变量,表示从游戏开始经过的秒数
用于实现波浪的动画效果,使波浪随时间移动
顶点位置修改
将计算得到的波浪值添加到顶点的 Y 坐标上
这样模型表面就会沿 Y 轴产生波浪变形
波浪变形原理详解
这个 Shader 基于正弦函数叠加实现波浪变形:
数学原理:
正弦函数 sin(x)
生成周期性的波形
通过 sin(kx + ωt)
控制波形的频率(k)和速度(ω)
两个正弦函数相乘可以产生更复杂的波浪模式
算法特点:
在 X 和 Z 方向上都应用波浪变形,形成二维波浪
波浪高度由振幅(_Amplitude)控制
波浪密度由频率(_Frequency)控制
波浪移动速度由速度(_Speed)控制
效果特点:
简单高效,计算量小
波浪模式规则,适合模拟水面、旗帜等
依赖模型的顶点密度,低多边形模型可能效果不佳
参数调整与视觉效果
_Amplitude 波浪振幅
较小值(如 0.1):产生微小的波动
中间值(如 1):产生明显的波浪效果
较大值(如 5):产生夸张的变形,可能导致模型穿插
_Frequency 波浪频率
较小值(如 0.5):产生宽而平缓的波浪
中间值(如 5):产生中等密度的波浪
较大值(如 10):产生密集的小波浪
_Speed 波浪速度
接近 0 的值:波浪几乎不动
中间值(如 2):产生自然的波浪移动效果
较大值(如 5):产生快速流动的波浪
模型顶点案例2:顶点挤出 (Extrusion) 效果
沿顶点法线方向移动顶点位置,可用于创建膨胀、爆炸或 3D 文字描边效果。
Shader "Custom/VertexExtrusion"
{
Properties
{
//基础纹理,提供模型的基本外观
_MainTex ("Texture", 2D) = "white" {}
//挤出量,控制顶点沿法线方向移动的距离
_Amount ("Extrusion Amount", Range(0, 2)) = 0.5
//叠加颜色,用于调整最终输出颜色
_Color ("Color", Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float _Amount;
fixed4 _Color;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert (appdata v)
{
v2f o;
// 沿法线方向挤出顶点
v.vertex.xyz += v.normal * _Amount;
// 转换到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col *= _Color;
return col;
}
ENDCG
}
}
}
顶点挤出原理详解
这个 Shader 基于法线方向位移实现顶点挤出:
数学原理:
对于每个顶点 P
,其法线为 N
挤出后的新顶点位置为 P' = P + N * _Amount
算法特点:
简单高效,仅需一次向量乘法和加法
挤出效果与模型的法线分布密切相关
对所有顶点应用相同的挤出量,保持形状比例
效果特点:
模型整体向外或向内膨胀
边缘和角落处的挤出效果最明显
可用于创建轮廓、强调效果或变形动画
3. 计算并传递数据给片元着色器
在 Unity Shader 中,顶点着色器的一个重要职责是计算并传递数据给片元着色器。这些数据包括纹理坐标、法线方向、世界坐标等,是实现光照、纹理采样和各种特效的基础。
纹理坐标:可进行变换、缩放或使用多组纹理坐标实现纹理混合
法线变换:确保法线在不同坐标系间正确转换,是实现光照的关键
世界坐标:用于基于世界位置的效果,如地形纹理、全局雾效
切线空间:实现法线贴图、视差效果等需要在切线空间计算
屏幕坐标:用于后处理效果和屏幕空间特效
案例1:基于世界坐标的纹理映射
顶点着色器计算并传递世界空间坐标
片元着色器使用世界坐标的 XZ 分量作为纹理坐标
纹理会固定在世界空间中,物体移动时纹理不会变形
Shader "Custom/WorldSpaceTexture"
{
Properties
{
_MainTex ("Base Texture", 2D) = "white" {}
_Scale ("Texture Scale", Float) = 1
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
float _Scale;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0; // 世界空间位置
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 计算世界空间位置
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 使用世界坐标的XZ分量作为纹理坐标
float2 worldUV = i.worldPos.xz * _Scale;
// 采样纹理
fixed4 col = tex2D(_MainTex, worldUV);
return col;
}
ENDCG
}
}
}
案例2:基于屏幕坐标的后处理效果
顶点着色器计算并传递屏幕空间坐标
使用ComputeScreenPos
函数获取裁剪空间坐标
片元着色器归一化屏幕坐标并采样屏幕纹理
实现简单的屏幕后处理效果(灰度化)
Shader "Custom/ScreenSpaceEffect"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Intensity ("Effect Intensity", Range(0, 1)) = 0.5
}
SubShader
{
// 渲染类型为透明,在所有不透明物体后渲染
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float _Intensity;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 screenPos : TEXCOORD1; // 屏幕空间坐标
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
// 计算屏幕空间坐标
o.screenPos = ComputeScreenPos(o.pos);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 归一化屏幕坐标
float2 screenUV = i.screenPos.xy / i.screenPos.w;
// 采样主纹理
fixed4 col = tex2D(_MainTex, screenUV);
// 添加屏幕空间效果(这里实现简单的灰度化)
fixed gray = dot(col.rgb, fixed3(0.299, 0.587, 0.114));
col.rgb = lerp(col.rgb, gray, _Intensity);
return col;
}
ENDCG
}
}
}
二、曲面细分着色器(Tessellation Shader)
曲面细分着色器 (Tessellation Shader) 是 Unity Shader 中用于动态增加网格细节的高级特性。它允许在运行时细分几何体,在需要高细节的区域增加多边形数量,从而实现平滑的曲面效果。
1. 核心功能
增加模型细节:在运行时细分三角形,提升模型平滑度
实现细节层次(LOD):根据距离或重要性动态调整细分级别
节省内存:使用低多边形模型作为基础,通过细分生成高细节表面
2. 主要组件
外壳着色器(Hull Shader):
定义细分级别(每个边和内部的细分次数)
处理控制点(如贝塞尔曲线的控制点)
域着色器(Domain Shader):
根据细分级别和控制点计算新顶点的位置
将细分后的参数转换回 3D 空间
3. 应用场景
地形渲染:近处高细分,远处低细分
角色服装:裙子、头发等需要平滑变形的部位
程序化几何:生成复杂的有机形状
案例:
Shader "Custom/TessellationTerrain"
{
Properties
{
_MainTex ("Base Texture", 2D) = "white" {}
_HeightMap ("Height Map", 2D) = "white" {}
_Height ("Height", Range(0, 1)) = 0.5
_TessellationUniform ("Tessellation", Range(1, 64)) = 4
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma hull hull
#pragma domain domain
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _HeightMap;
float _Height;
float _TessellationUniform;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2h
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct h2d
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
// 顶点着色器
v2h vert (appdata v)
{
v2h o;
o.vertex = v.vertex;
o.normal = v.normal;
o.uv = v.uv;
return o;
}
// 外壳着色器 - 控制网格细分级别
[UNITY_domain("tri")]
[UNITY_outputcontrolpoints(3)]
[UNITY_outputtopology("triangle_cw")]
[UNITY_partitioning("fractional_odd")]
[UNITY_patchconstantfunc("PatchConstantFunction")]
h2d hull (InputPatch<v2h, 3> patch, uint id : SV_OutputControlPointID)
{
return patch[id];
}
// 补丁常量函数 - 计算细分级别
UnityTessellationFactors PatchConstantFunction(InputPatch<v2h, 3> patch)
{
UnityTessellationFactors o;
o.edge[0] = _TessellationUniform;
o.edge[1] = _TessellationUniform;
o.edge[2] = _TessellationUniform;
o.inside = _TessellationUniform;
return o;
}
// 域着色器 - 生成新顶点并应用高度图
[UNITY_domain("tri")]
v2f domain (UnityTessellationFactors tessFactors, OutputPatch<h2d, 3> patch, float3 bary : SV_DomainLocation)
{
// 插值顶点属性
float4 v = patch[0].vertex * bary.x + patch[1].vertex * bary.y + patch[2].vertex * bary.z;
float3 n = patch[0].normal * bary.x + patch[1].normal * bary.y + patch[2].normal * bary.z;
float2 uv = patch[0].uv * bary.x + patch[1].uv * bary.y + patch[2].uv * bary.z;
// 从高度图采样高度值
float height = tex2Dlod(_HeightMap, float4(uv, 0, 0)).r * _Height;
// 应用高度偏移
v.y += height;
v2f o;
o.pos = UnityObjectToClipPos(v);
o.uv = uv;
o.normal = UnityObjectToWorldNormal(n);
o.worldPos = mul(unity_ObjectToWorld, v).xyz;
return o;
}
// 片元着色器
fixed4 frag (v2f i) : SV_Target
{
// 简单的漫反射光照
fixed3 lightDir = _WorldSpaceLightPos0.xyz;
fixed3 normalDir = normalize(i.normal);
fixed diff = saturate(dot(normalDir, lightDir));
// 采样纹理
fixed4 col = tex2D(_MainTex, i.uv);
// 应用光照
col.rgb *= _LightColor0.rgb * diff;
col.rgb += col.rgb * UNITY_LIGHTMODEL_AMBIENT.rgb;
return col;
}
ENDCG
}
}
}
三、几何着色器(Geometry Shader)
几何着色器 (Geometry Shader) 是 Unity Shader 中可选的阶段,允许在运行时创建、修改或销毁几何体。它接收完整的图元 (如点、线、三角形) 作为输入,并输出一个或多个图元。
1. 核心功能
生成新的几何图元:可以根据输入的图元(点、线、三角形)生成更多图元
实现特殊效果:如粒子系统、草丛、爆炸碎片等
裁剪和剔除:根据条件丢弃或修改图元
2. 特点
输入灵活:可以处理点、线、三角形等多种图元
输出多样:可以输出任意数量的点、线或三角形
性能开销较大:需要谨慎使用,避免过度生成几何
3. 示例应用
法线可视化:为每个顶点生成一条表示法线的线段
广告牌效果:将三角形转换为始终面向相机的四边形
程序化网格:根据单个点生成复杂的几何结构
案例:
Shader "Custom/GeometryShaderParticles"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Color", Color) = (1,1,1,1)
_ParticleSize ("Particle Size", Range(0.1, 10)) = 2
_Speed ("Speed", Range(0.1, 10)) = 5
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
fixed4 _Color;
float _ParticleSize;
float _Speed;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2g
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
struct g2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 color : COLOR;
};
// 顶点着色器
v2g vert (appdata v)
{
v2g o;
o.pos = v.vertex;
o.uv = v.uv;
// 为每个粒子分配随机颜色和速度方向
float randX = frac(sin(dot(v.vertex.xy, float2(12.9898, 78.233))) * 43758.5453);
float randY = frac(sin(dot(v.vertex.yz, float2(45.5432, 12.9898))) * 43758.5453);
float randZ = frac(sin(dot(v.vertex.zx, float2(78.233, 45.5432))) * 43758.5453);
o.color = _Color;
o.color.a = 1.0; // 初始不透明
// 使用随机值作为速度方向
o.pos.xyz += float3(randX, randY, randZ) * _Time.y * _Speed;
return o;
}
// 几何着色器
[maxvertexcount(4)]
void geom(point v2g input[1], inout TriangleStream<g2f> triStream)
{
float4 pos = input[0].pos;
float2 uv = input[0].uv;
float4 color = input[0].color;
// 粒子生命周期衰减(随时间变淡)
color.a = 1.0 - (_Time.y * 0.2);
if (color.a <= 0) return; // 粒子消失
// 计算粒子在屏幕空间中的大小
float4 posCS = UnityObjectToClipPos(pos);
float2 screenSize = _ScreenParams.xy;
// 粒子在屏幕空间中的半宽高
float halfWidth = _ParticleSize / screenSize.x;
float halfHeight = _ParticleSize / screenSize.y;
// 创建四边形(两个三角形)
g2f v[4];
v[0].pos = posCS + float4(-halfWidth, -halfHeight, 0, 0);
v[1].pos = posCS + float4(-halfWidth, halfHeight, 0, 0);
v[2].pos = posCS + float4( halfWidth, -halfHeight, 0, 0);
v[3].pos = posCS + float4( halfWidth, halfHeight, 0, 0);
v[0].uv = float2(0, 0);
v[1].uv = float2(0, 1);
v[2].uv = float2(1, 0);
v[3].uv = float2(1, 1);
v[0].color = v[1].color = v[2].color = v[3].color = color;
// 输出两个三角形组成四边形
triStream.Append(v[0]);
triStream.Append(v[1]);
triStream.Append(v[2]);
triStream.Append(v[2]);
triStream.Append(v[1]);
triStream.Append(v[3]);
}
// 片元着色器
fixed4 frag (g2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv) * i.color;
return col;
}
ENDCG
}
}
}
四、光栅化阶段(Rasterization)
1. 核心功能
将几何图元(三角形)转换为屏幕上的像素(片元)
对顶点属性(如 UV、法线、颜色)进行插值计算,为每个片元提供数据
2. 主要步骤
三角形设置:计算三角形边界,确定需要处理的像素范围
三角形遍历:检查每个像素是否被三角形覆盖,生成片元
属性插值:使用重心坐标对顶点属性进行线性插值
3. 关键技术
重心坐标(Barycentric Coordinates):
每个像素在三角形中的位置可以用三个顶点的权重(α, β, γ)表示,满足 α+β+γ=1。
插值公式:V = α・V₁ + β・V₂ + γ・V₃
透视校正:
自动处理透视效果,确保纹理正确显示(非齐次坐标的插值更复杂)
五、片元着色器(Fragment Shader)
1. 核心功能
计算每个像素的最终颜色值
实现光照、纹理采样、颜色混合、特效等复杂效果
通过clip
函数或透明度混合控制像素可见性
2. 关键操作
纹理采样:使用tex2D
、texCUBE
等函数从纹理获取颜色
光照计算:实现漫反射、高光反射、PBR 等光照模型
颜色混合:通过lerp
等函数混合多种颜色来源
透明度控制:使用clip
或Blend
指令控制透明度
3. 纹理采样
TRANSFORM_TEX 函数
输入的 UV 坐标根据纹理的缩放 (Tiling) 和偏移 (Offset) 参数进行变换,得到最终用于采样纹理的 UV 坐标。
单纹理采样
fixed4 frag (v2f i) : SV_Target {
// 变换UV坐标
float2 uv = TRANSFORM_TEX(i.uv, _MainTex);
// 采样纹理
fixed4 col = tex2D(_MainTex, uv);
return col;
}
多纹理情况
Properties {
_MainTex ("Base Texture", 2D) = "white" {}
_DetailTex ("Detail Texture", 2D) = "white" {}
}
fixed4 frag (v2f i) : SV_Target {
// 分别变换两个纹理的UV坐标
float2 mainUV = TRANSFORM_TEX(i.uv, _MainTex);
float2 detailUV = TRANSFORM_TEX(i.uv2, _DetailTex);
// 采样纹理
fixed4 mainCol = tex2D(_MainTex, mainUV);
fixed4 detailCol = tex2D(_DetailTex, detailUV);
return mainCol * detailCol;
}
案例1:法线贴图与 Phong 光照
使用法线贴图模拟表面细节,无需增加网格复杂度
在切线空间计算光照,确保法线正确应用于任何网格方向
Blinn-Phong 模型提供更精确的高光反射效果
Shader "Custom/NormalMapPhong"
{
Properties
{
_MainTex ("Base Texture", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_Specular ("Specular", Color) = (1,1,1,1)
_Glossiness ("Glossiness", Range(0, 1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;
sampler2D _BumpMap;
float4 _MainTex_ST;
fixed4 _Specular;
half _Glossiness;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 tangent : TEXCOORD1;
float3 binormal : TEXCOORD2;
float3 normal : TEXCOORD3;
float3 worldPos : TEXCOORD4;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// 计算世界空间位置和法线
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.normal = UnityObjectToWorldNormal(v.normal);
// 计算切线空间矩阵
o.tangent = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal = cross(o.normal, o.tangent) * v.tangent.w;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 构建TBN矩阵(切线-副法线-法线)
float3x3 TBN = float3x3(i.tangent, i.binormal, i.normal);
// 从法线贴图采样并解码
fixed3 normalTS = UnpackNormal(tex2D(_BumpMap, i.uv));
// 将法线从切线空间转换到世界空间
fixed3 normalWS = normalize(mul(normalTS, TBN));
// 计算光照方向和视线方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
// 漫反射计算
fixed diff = saturate(dot(normalWS, lightDir));
fixed3 diffuse = _LightColor0.rgb * diff;
// 高光计算(Blinn-Phong模型)
fixed3 halfDir = normalize(lightDir + viewDir);
fixed spec = pow(saturate(dot(normalWS, halfDir)), _Glossiness * 100);
fixed3 specular = _LightColor0.rgb * spec * _Specular.rgb;
// 采样主纹理
fixed4 texCol = tex2D(_MainTex, i.uv);
// 最终颜色 = 纹理颜色 * (漫反射 + 环境光) + 高光
fixed4 finalCol = texCol * (diffuse + UNITY_LIGHTMODEL_AMBIENT.rgb) + fixed4(specular, 0);
return finalCol;
}
ENDCG
}
}
}
案例2:菲涅尔反射与透明效果
实现了透明物体的折射和反射效果
菲涅尔效应使物体边缘反射更强,模拟真实世界的光学现象
折射指数控制光线弯曲程度,不同材质有不同的折射指数
Shader "Custom/FresnelRefraction"
{
Properties
{
_MainTex ("Base Texture", 2D) = "white" {}
_Cubemap ("Environment Cubemap", CUBE) = "" {}
_FresnelPower ("Fresnel Power", Range(0.1, 10)) = 5
_RefractionIndex ("Refraction Index", Range(0.1, 3)) = 1.5
_Opacity ("Opacity", Range(0, 1)) = 0.8
}
SubShader
{
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
samplerCUBE _Cubemap;
float _FresnelPower;
float _RefractionIndex;
float _Opacity;
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal : TEXCOORD1;
float3 worldPos : TEXCOORD2;
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// 计算世界空间位置和法线
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 归一化法线和视线方向
fixed3 normal = normalize(i.normal);
fixed3 viewDir = normalize(_WorldSpaceCameraPos - i.worldPos);
// 计算菲涅尔因子(控制反射强度)
float fresnel = pow(1 - dot(normal, viewDir), _FresnelPower);
// 计算折射向量
fixed3 refractDir = refract(-viewDir, normal, 1.0 / _RefractionIndex);
// 计算反射向量
fixed3 reflectDir = reflect(-viewDir, normal);
// 从立方体贴图采样折射和反射颜色
fixed4 refractCol = texCUBE(_Cubemap, refractDir);
fixed4 reflectCol = texCUBE(_Cubemap, reflectDir);
// 基于菲涅尔因子混合反射和折射
fixed4 envCol = lerp(refractCol, reflectCol, fresnel);
// 采样基础纹理
fixed4 baseCol = tex2D(_MainTex, i.uv);
// 最终颜色 = 基础颜色与环境颜色混合
fixed4 finalCol = lerp(baseCol, envCol, _Opacity);
finalCol.a = _Opacity;
return finalCol;
}
ENDCG
}
}
}
案例3: 基于深度的雾效
基于深度纹理实现场景雾效
远处物体被雾逐渐遮挡,增强场景深度感
可调整雾的开始位置、结束位置和密度
Shader "Custom/DepthFog"
{
Properties
{
_MainTex ("Base Texture", 2D) = "white" {}
_FogColor ("Fog Color", Color) = (0.5, 0.6, 0.7, 1)
_FogDensity ("Fog Density", Range(0, 1)) = 0.5
_FogStart ("Fog Start", Float) = 0
_FogEnd ("Fog End", Float) = 100
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
sampler2D _CameraDepthTexture;
float4 _MainTex_ST;
fixed4 _FogColor;
float _FogDensity;
float _FogStart;
float _FogEnd;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 screenPos : TEXCOORD1; // 屏幕空间坐标
float eyeDepth : TEXCOORD2; // 相机空间深度
};
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.screenPos = ComputeScreenPos(o.pos);
// 计算相机空间深度
float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex);
o.eyeDepth = -viewPos.z;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 采样基础纹理
fixed4 col = tex2D(_MainTex, i.uv);
// 从深度纹理获取场景深度
float sceneZ = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.screenPos)));
// 计算雾因子(基于相机空间深度)
float fogFactor = saturate((sceneZ - _FogStart) / (_FogEnd - _FogStart));
// 应用雾效
col.rgb = lerp(col.rgb, _FogColor.rgb, fogFactor * _FogDensity);
return col;
}
ENDCG
}
}
}
六、输出合并阶段(Output Merger)
1. 核心功能
处理片元着色器输出的颜色,决定最终写入帧缓冲区的像素值
执行深度测试、模板测试和混合操作
2. 主要步骤
深度测试(Depth Test):
比较片元的深度值与深度缓冲区的值
根据ZTest
设置(如ZTest LEqual
)决定是否丢弃片元
模板测试(Stencil Test):
比较片元的模板值与模板缓冲区的值
根据Stencil
设置决定是否丢弃片元或修改模板值
混合操作(Blend):
如果启用混合(Blend SrcAlpha OneMinusSrcAlpha
),将片元颜色与帧缓冲区中的颜色
混合
常见混合模式:透明度混合、加法混合、乘法混合等
写入帧缓冲区:
将通过所有测试的片元颜色写入颜色缓冲区
更新深度缓冲区和模板缓冲区(如果允许)