Unity Shader 渲染流程深度解析

        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. 关键操作

        纹理采样:使用tex2DtexCUBE等函数从纹理获取颜色

        光照计算:实现漫反射、高光反射、PBR 等光照模型

        颜色混合:通过lerp等函数混合多种颜色来源

        透明度控制:使用clipBlend指令控制透明度

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),将片元颜色与帧缓冲区中的颜色

                混合

                常见混合模式:透明度混合、加法混合、乘法混合等

        写入帧缓冲区

                将通过所有测试的片元颜色写入颜色缓冲区

                更新深度缓冲区和模板缓冲区(如果允许)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值