Unity Shader编程完全入门指南:从零到实战 C# 实战案例

Unity Shader编程完全入门指南:从零到实战 C#


提示:内容纯个人编写,欢迎评论点赞。


1. Shader基础概念

1.1 什么是Shader?

Shader(着色器)是一类运行在GPU上的特殊程序,用于控制图形渲染管线的各个阶段。它决定了模型的顶点如何变换到屏幕空间,以及每个像素如何着色

graph LR
A[顶点数据] --> B[顶点着色器]
B --> C[图元装配]
C --> D[几何着色器]
D --> E[光栅化]
E --> F[片元着色器]
F --> G[帧缓冲输出]

1.2 Shader在游戏开发中的作用

  • 材质外观控制:金属、木质、皮肤等材质的视觉效果
  • 特效实现:水波、火焰、全息投影等特殊效果
  • 性能优化:通过减少draw call提升渲染效率
  • 艺术风格:卡通渲染、像素风等独特视觉风格

1.3 Unity中的Shader类型

在这里插入图片描述

2. ShaderLab语法入门

2.1 基本结构

Shader "Custom/ExampleShader"
{
    Properties
    {
        // 属性声明
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        Pass
        {
            // 渲染指令
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // CG代码
            ENDCG
        }
    }
    FallBack "Diffuse"
}

2.2 Properties属性块

Properties
{
    _Color ("Main Color", Color) = (1,1,1,1)
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _Glossiness ("Smoothness", Range(0,1)) = 0.5
    _Metallic ("Metallic", Range(0,1)) = 0.0
    _BumpMap ("Normal Map", 2D) = "bump" {}
}

2.3 SubShader与Pass

  • SubShader:针对不同显卡配置的备选方案
  • Pass:一次完整的渲染流程,一个SubShader可包含多个Pass

2.4 常用渲染指令

Cull Back // 背面剔除
ZWrite On // 深度写入
Blend SrcAlpha OneMinusSrcAlpha // 透明混合
LOD 200 // 细节级别

3. 表面着色器(Surface Shader)实战

3.1 表面着色器结构

Shader "Custom/SurfaceExample"
{
    Properties { /* 属性声明 */ }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        
        CGPROGRAM
        #pragma surface surf Standard
        
        struct Input
        {
            float2 uv_MainTex;
        };
        
        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 表面着色逻辑
        }
        ENDCG
    }
}

3.2 实现漫反射光照

void surf (Input IN, inout SurfaceOutputStandard o)
{
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
    o.Albedo = c.rgb;
    o.Alpha = c.a;
    o.Metallic = _Metallic;
    o.Smoothness = _Glossiness;
}

3.3 添加纹理贴图

Properties {
    _MainTex ("Albedo (RGB)", 2D) = "white" {}
    _NormalMap ("Normal Map", 2D) = "bump" {}
}

struct Input {
    float2 uv_MainTex;
    float2 uv_NormalMap;
};

void surf (Input IN, inout SurfaceOutputStandard o) {
    fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    o.Albedo = c.rgb;
    o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
}

4. 顶点/片元着色器(Vertex/Fragment Shader)实战

4.1 基本结构

Shader "Custom/VFExample"
{
    Properties { _Color("Color", Color) = (1,0,0,1) }
    SubShader
    {
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            struct appdata
            {
                float4 vertex : POSITION;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
            };
            
            v2f vert (appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                return _Color;
            }
            ENDCG
        }
    }
}

4.2 实现顶点动画

v2f vert (appdata v)
{
    v2f o;
    
    // 正弦波动效果
    float wave = sin(_Time.y * 5 + v.vertex.x * 10) * 0.1;
    v.vertex.y += wave;
    
    o.pos = UnityObjectToClipPos(v.vertex);
    return o;
}
初始状态:   波动状态:
  ---        /\/\/\
  ---       /\/\/\/\
  ---      /\/\/\/\/\

4.3 片元着色特效

fixed4 frag (v2f i) : SV_Target
{
    // 创建UV动画
    float2 uv = i.uv + float2(_Time.x * 0.1, _Time.y * 0.2);
    
    // 生成噪点效果
    float noise = frac(sin(dot(uv, float2(12.9898,78.233))) * 43758.5453);
    
    // 混合颜色
    fixed4 col = tex2D(_MainTex, uv);
    col.rgb += noise * 0.1;
    
    // 添加边缘光
    float rim = 1.0 - saturate(dot(i.normal, i.viewDir));
    col.rgb += pow(rim, 5) * _RimColor;
    
    return col;
}

5. 常见问题与解决方案

5.1 性能优化

  1. 减少数学运算:
  • 避免使用sin、cos等复杂函数
  • 使用mad指令优化计算(a*b+c)
  1. 纹理采样优化:
// 避免多次采样相同纹理
fixed4 col = tex2D(_MainTex, uv);
fixed4 normal = UnpackNormal(tex2D(_NormalMap, uv));

// 使用纹理合并(RGB存储不同数据)
  1. 条件语句优化:
// 避免分支语句
float value = a > b ? 1.0 : 0.0; // 不推荐
float value = saturate(sign(a - b)); // 推荐`在这里插入代码片`

5.2 平台兼容性问题

// 1. 精度问题
#ifdef GL_ES
precision mediump float;
#endif

// 2. 坐标系差异
float4 clipPos = mul(UNITY_MATRIX_VP, mul(UNITY_MATRIX_M, v.vertex));

// 3. 特性支持检查
#if defined(SHADER_API_D3D11) || defined(SHADER_API_GLES3)
    // 使用高级特性
#else
    // 回退方案
#endif

5.3 调试技巧

  1. 颜色输出调试法:
// 可视化法线方向
return float4(i.normal * 0.5 + 0.5, 1.0);

// 显示UV坐标
return float4(i.uv, 0, 1);
  1. Unity帧调试器:
  • Window > Analysis > Frame Debugger
  • 逐步查看渲染过程
  1. RenderDoc工具:
  • 捕获帧数据
  • 分析渲染管线每个阶段的状态

6. 实战案例:水波特效

6.1 效果分析

  • 水面波动:正弦波叠加
  • 折射效果:法线扰动+屏幕纹理采样
  • 高光反射:菲涅尔反射

6.2 数学原理

水波函数:

y = A \times sin(\frac{2\pi}{T} \times t + \frac{2\pi}{\lambda} \times x)

其中:

A = 振幅
T = 周期
λ = 波长

6.3 完整代码实现

Shader "Custom/WaterShader"
{
    Properties
    {
        _Color ("Water Color", Color) = (0.2, 0.6, 1, 0.8)
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _NormalMap ("Normal Map", 2D) = "bump" {}
        _WaveSpeed ("Wave Speed", Float) = 1.0
        _WaveHeight ("Wave Height", Float) = 0.1
        _WaveFrequency ("Wave Frequency", Float) = 1.0
        _RefractionIntensity ("Refraction", Range(0,1)) = 0.1
        _Specular ("Specular", Range(0,1)) = 0.5
    }
    
    SubShader
    {
        Tags { "Queue"="Transparent" "RenderType"="Transparent" }
        LOD 300
        
       
        
        GrabPass { "_RefractionTex" }
        
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 grabPos : TEXCOORD0;
                float2 uv : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
                float3 normal : NORMAL;
            };
            
            sampler2D _MainTex;
            sampler2D _NormalMap;
            sampler2D _RefractionTex;
            float4 _Color;
            float _WaveSpeed;
            float _WaveHeight;
            float _WaveFrequency;
            float _RefractionIntensity;
            float _Specular;
            
            v2f vert (appdata v)
            {
                v2f o;
                
                // 顶点动画 - 正弦波
                float wave = sin(_Time.y * _WaveSpeed + v.vertex.x * _WaveFrequency);
                v.vertex.y += wave * _WaveHeight;
                
                o.pos = UnityObjectToClipPos(v.vertex);
                o.grabPos = ComputeGrabScreenPos(o.pos);
                o.uv = v.uv;
                o.viewDir = normalize(WorldSpaceViewDir(v.vertex));
                o.normal = UnityObjectToWorldNormal(v.normal);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // 法线贴图扰动
                float3 normal = UnpackNormal(tex2D(_NormalMap, i.uv));
                
                // 折射效果
                float2 refraction = normal.xy * _RefractionIntensity;
                i.grabPos.xy += refraction;
                fixed4 refrCol = tex2Dproj(_RefractionTex, i.grabPos);
                
                // 菲涅尔反射
                float fresnel = 1.0 - saturate(dot(i.normal, i.viewDir));
                float specular = SPECULAR_STRENGTH * pow(fresnel, _Specular);
                
                // 最终颜色混合
                fixed4 baseCol = tex2D(_MainTex, i.uv) * _Color;
                fixed4 finalCol = lerp(refrCol, baseCol, 0.7);
                finalCol.rgb += specular;
                
                return finalCol;
            }
            ENDCG
        }
    }
    FallBack "Transparent/Diffuse"
}

水波特效效果:

初始水面:   波动效果:     折射效果:
  ~~~~        ~~~~~~        ~~~~~~
  ~~~~        ~/\/\/~       ~/~~\/~
  ~~~~        /~~~~~~       /~~~~~~

7. 实战案例:卡通渲染

7.1 效果分析

  • 硬朗的明暗分界:离散化光照计算
  • 黑色描边:边缘检测或背面挤出
  • 色块化着色:减少颜色过渡

7.2 边缘检测实现

// 背面挤出法实现描边
Pass
{
    Cull Front // 渲染背面
    
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    
    struct appdata
    {
        float4 vertex : POSITION;
        float3 normal : NORMAL;
    };
    
    struct v2f
    {
        float4 pos : SV_POSITION;
    };
    
    float _OutlineWidth;
    
    v2f vert (appdata v)
    {
        v2f o;
        // 沿法线方向挤出
        float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
        float2 offset = TransformViewToProjection(normal.xy);
        o.pos = UnityObjectToClipPos(v.vertex);
        o.pos.xy += offset * _OutlineWidth;
        return o;
    }
    
    fixed4 frag (v2f i) : SV_Target
    {
        return fixed4(0,0,0,1); // 黑色描边
    }
    ENDCG
}

7.3 色块化处理

fixed4 frag (v2f i) : SV_Target
{
    // 计算光照
    float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
    float diffuse = max(0, dot(i.normal, lightDir));
    
    // 离散化处理
    float ramp = floor(diffuse * _RampSteps) / _RampSteps;
    
    // 采样色块纹理
    fixed3 rampCol = tex2D(_RampTex, float2(ramp, 0.5)).rgb;
    
    // 添加高光
    float3 viewDir = normalize(i.viewDir);
    float3 halfDir = normalize(lightDir + viewDir);
    float specular = pow(max(0, dot(i.normal, halfDir)), _SpecularPower);
    specular = step(_SpecularThreshold, specular);
    
    fixed4 col = tex2D(_MainTex, i.uv);
    col.rgb *= rampCol + specular * _SpecularColor;
    
    return col;
}

8. 进阶学习资源

8.1 官方文档

Unity Shader Reference
Surface Shader Examples
Shader Variants

8.2 推荐书目

  • 《Unity Shader入门精要》- 冯乐乐
  • 《Real-Time Rendering》- Tomas Akenine-Möller
  • 《GPU Gems》系列 - NVIDIA

8.3 社区资源

Unity官方论坛Shader版块
CG/CGIN标准函数库

  • 希望本文能帮助你在Unity开发中更加得心应手!如果有任何问题,请在评论区留言讨论。
  • 点赞收藏加关注哦~ 蟹蟹
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

与火星的孩子对话

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值