第一章:Shader入门——从零开始认识着色器

大家好,欢迎来到《Unity着色器开发:从基础到进阶》的第一章。

说实话,我刚开始学Shader的时候,也是一头雾水。什么顶点、片段、渲染管线……听着就头大。但别担心,这一章我会用最直白的方式,带你搞懂Shader到底是什么、它在渲染管线里扮演什么角色、以及Unity里的Shader长什么样。

1.1 什么是Shader?

Shader,中文叫「着色器」。说白了,它就是一段运行在GPU上的小程序。

它的任务很简单:告诉GPU怎么给每个像素上色

你想想看,游戏里一个3D模型,从一堆顶点数据变成屏幕上五彩斑斓的画面,中间经历了什么?嗯,就是Shader在干活。

我个人习惯把Shader比作「画家」。模型是画布,纹理是颜料,而Shader就是那个拿着画笔的人。它决定了哪里该亮、哪里该暗、哪里该反射、哪里该透明。

核心要点:Shader = 运行在GPU上的、控制渲染效果的代码。

我在项目中遇到过不少新手,以为Shader是某种神秘的黑魔法。其实不是。它就是一种工具,跟C#脚本一样,只不过它跑在GPU上,而且更贴近图形硬件。

1.2 Shader在渲染管线中的位置

要理解Shader,必须先理解渲染管线。

渲染管线,说白了就是「从3D数据到2D屏幕」的流水线。它分很多阶段,但Shader主要参与其中两个关键阶段:

阶段 Shader类型 干什么的
顶点处理 顶点着色器(Vertex Shader) 处理每个顶点的位置、法线、UV等
片段处理 片段着色器(Fragment Shader) 计算每个像素的最终颜色

整个流程大致是这样的:

  1. 输入装配:把模型的顶点数据送进GPU
  2. 顶点着色器:对每个顶点做变换(比如把模型从局部空间转到屏幕空间)
  3. 光栅化:把顶点连成三角形,再拆成一个个像素
  4. 片段着色器:对每个像素计算颜色
  5. 输出合并:处理深度测试、透明度混合等,最终输出到屏幕

你看,Shader就插在第二步和第四步。这两个阶段是我们可以编程控制的。其他阶段?嗯,硬件帮你搞定了,你改不了。

小提示:Unity里你写的Shader代码,主要就是顶点着色器和片段着色器。其他阶段Unity帮你封装好了。

1.3 Unity Shader的基本结构

好,现在咱们来看看Unity里的Shader到底长什么样。

Unity的Shader文件,后缀是.shader。它用一种叫ShaderLab的语言来写。ShaderLab是Unity自己封装的一套标记语言,里面可以嵌套HLSL(或CG)代码。

一个最基本的Unity Shader结构是这样的:

Shader "Custom/MyFirstShader"
{
    Properties
    {
        _MainTex ("纹理", 2D) = "white" {}
        _Color ("颜色", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" }

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _Color;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                fixed4 col = tex2D(_MainTex, i.uv) * _Color;
                return col;
            }
            ENDCG
        }
    }
}

看着有点长?别怕,我拆开给你讲。

1.3.1 Shader名称

第一行:Shader "Custom/MyFirstShader"

这个字符串就是Shader在Unity材质面板里显示的名字。斜杠表示层级,比如"Custom/..."会出现在Custom菜单下。

1.3.2 Properties(属性块)

Properties定义了这个Shader暴露给编辑器的参数。比如纹理、颜色、数值等。

格式很简单:变量名 ("显示名称", 类型) = 默认值

我个人习惯把常用的属性放在前面,比如纹理、颜色、光滑度。这样美术同事用起来也方便。

1.3.3 SubShader(子着色器)

一个Shader可以包含多个SubShader。Unity会按顺序尝试,选第一个能跑在当前硬件上的。

SubShader里可以设置Tags(标签),比如RenderTypeQueue等。这些标签告诉Unity这个Shader该怎么渲染。

注意:Tags写错了,可能会导致渲染顺序出问题。我曾经因为忘了写Queue标签,透明物体渲染得乱七八糟……嗯,排查了半天。

1.3.4 Pass(通道)

每个SubShader里可以有一个或多个Pass。每个Pass代表一次完整的渲染过程。

最简单的Shader只有一个Pass。复杂一点的,比如多Pass可以做轮廓描边、阴影等效果。

1.3.5 CGPROGRAM ... ENDCG

这就是真正的着色器代码了。用HLSL/CG语言写的。

里面包含:

  • #pragma指令:指定顶点和片段着色器的入口函数名
  • 结构体:定义输入输出数据的格式
  • 顶点着色器函数:处理顶点变换
  • 片段着色器函数:计算像素颜色

你看,其实没那么复杂。顶点着色器把顶点坐标从模型空间转到裁剪空间,片段着色器采样纹理并乘以颜色,就完事了。

避坑指南:我曾经在写顶点着色器时忘了调用UnityObjectToClipPos,结果模型全跑到屏幕外面去了。记住:顶点着色器必须把坐标转换到裁剪空间,否则GPU不知道这个顶点该画在哪。

1.4 小结

这一章我们聊了:

  • Shader是运行在GPU上的小程序,控制渲染效果
  • 它在渲染管线中主要负责顶点处理和片段处理
  • Unity Shader由ShaderLab编写,包含Properties、SubShader、Pass等部分

下一章,我们会真正动手写第一个Shader。到时候你会发现,原来写Shader跟写C#脚本一样,有套路可循。

嗯,今天就到这里。有什么问题,欢迎随时交流。


公众号:蓝海资料掘金营,微信deep3321