初面网初面网

Unity Shader

渲染管线

名称解释
Unity的内置渲染适用于所有平台,非常可靠,容易使用。但是不可定制,也非最有效
通用渲染管线(URP)非常适合移动、Web和VR项目,因为它针对性能进行了高度优化。配置有点复杂,但比内置渲染管道更具可定制性,可以产生不错的图形
高清渲染管线(HDRP)旨在为具有强大处理能力的高端平台(如游戏机或游戏计算机)生成高质量图形。配置非常复杂,适合具有大量图形经验的人使用

URP和HDRP都是Scriptable Render Pipelines(SRP)。SRP是高度可定制的。

渲染流水线

应用阶段(由CPU负责)

1、场景数据加载到显存中

渲染所需数据从硬盘加载到内存中,网格纹理等数据被加载到显存中(一般加载到显存后内存中的数据就会被移除)

2、设置渲染状态

处理用于定义场景中的网格如何渲染,例如,使用哪个顶点着色器,哪个纹理等。

该阶段还会剔除不可见内容。

3、调用Draw Call

是一个命令,由CPU发起,GPU接收。该命令仅仅会指向一个需要被渲染的图元列表,而不会包含任何材质信息

应用阶段会输出渲染图元到下个阶段

几何阶段(GPU负责)

把顶点坐标变换到屏幕空间中,再交给光栅器处理

该阶段会输出屏幕空间的顶点信息到下阶段

光栅阶段(GPU负责)

使用上阶段传递的数据,产生屏幕上的像素并最终渲染出图像

GPU流水线

几何阶段和光栅化阶段中,开发者只能对某些步骤进行操作。例如:

几何阶段中:顶点着色器、曲面细分着色器、几何着色器、裁剪(仅可配置)

光栅化阶段中:片元着色器、逐片元操作(仅可配置)

整个流程为:

顶点数据->顶点着色器->曲面细分着色器->几何着色器->裁剪->屏幕映射->(后面是光栅化阶段)三角形设置->三角形遍历->片元着色器->逐片元操作->屏幕图像

顶点着色器、裁切、屏幕映射

顶点着色器,顾名思义,此处是对顶点的操作。输入的每个顶点都会调用一次顶点着色器,主要完成顶点坐标变换以及逐顶点光照,输出后续所需数据。

裁切,超出摄像机部分的裁剪掉不作渲染处理。

屏幕映射,将三维空间的数据映射到二维平面。OpenGL的原点在左下角,坐标系朝右上角延伸。DX原点在左上角,朝右下角延伸。

三角形设置、三角形遍历

顶点数据处理完以后进入到光栅化阶段。此处的内容是以三角形为基本单位,大部分处理都与三角形有关。

首先就是根据坐标点,确定一个三角形,然后根据三角形的覆盖范围,在像素上生成片元。

逐片元操作

在DX中,这个阶段叫做输出合并阶段

在这个阶段会对每个片元的可见性进行设置,这往往需要经过深度测试、模板测试等测试来对最终结果进行确定。当确定了结果以后,就把该片元的颜色值和储存在颜色缓冲区的色彩进行合并。

shader分类

Unity中使用ShaderLab编写Shader。

shader分成三类:

类型名英文名说明
表面着色器Surface Shader表面着色器相当于封装了顶点片元着色器
顶点/片元着色器Vertex/Fragment Shader更底层的着色器,更灵活
固定函数着色器(已弃用)Fixed Function Shader

基本结构

Shader "文件路径/文件名"
{
  Properties{}
  SubShader{
    Pass{}
  }
  FallBack "Diffuse"
}

Properties里声明和定义属性。变量名一般会以下划线开头。

SubShader中书写方法。可以有多个SubShader。真正意义上的Shader代码都会出现在这里。在这里还可以写表面着色器,顶点/片元着色器,固定函数着色器

FallBack后跟的是默认的shader解决方法,仅当显卡不支持SubShader中的所有方法时生效

Properties 属性

Properties是property的复数形式,但同样都表达“属性”的含义。

以下是支持的属性:

属性类型默认值的定义语法例子
Intnumber_Int("Int",Int) = 2
Floatnumber_Float("Float",Float) = 2.8
Range(min,max)number_Range("Range",Range(0.0,5.0)) = 2.5
Color(number,number,number,number)_Color("Color",Color) = (1,1,1,1))
Vector(number,number,number,number)_Vector("Vector",Vector) = (2,3,6,1)
2D"defaulttexture"{}_2D("2D",2D) = "" {}
Cube"defaulttexture"{}_Cube("Cube",Cube) = "white"{}
3D"defaulttexture"{}_3D("3D",3D) = "black"{}

Subshader

状态

SubShader{
  // 可选的——标签
  [Tags]

  // 可选的——状态
  [RenderSetup]

  Pass{}

  // Other Passes

}

标签和状态在Pass中也可以声明,但是Pass过多会造成渲染性能下降,且有的标签是SubShader特有的。 此外,在Subshader中设置的状态会应用到所有的Pass。

  • 常见的状态设置
状态名称设置指令解释
CullCull Back / Front / Off设置剔除模式:剔除背面/正面/关闭剔除
ZTestZTest Less Greater / LEqual / GEqual / Equal / NotEqual / Always设置深度测试时使用的函数
ZWriteZWrite On/ Off开启/关闭深度写入
BlendBlend SrcFactor DstFactor开启并设置混合模式
LOD

用例:

SubShader{
  Tags{"RenderType" = "Opaque"}
  // 可选的——状态
  Cull off
  ZWrite off
  LOD 100

  Pass{}
}

标签

标签是一个键值对,键、值都是字符串类型。这些键值对连接着SubShader和渲染引擎,告诉Unity如何渲染某个对象。

结构:Tags{"TagName1" = "Value1"}

  • 常见标签类型
标签类型说明例子
Queue控制渲染顺序Tags{"Queue"="Transparent"}
RenderType对着色器进行分类、替换Tags{"RenderType"="Opaque"}
DisableBatching指明是否使用Unity的批处理功能,是否进行合批Tags{"DisableBatching"="True"}
ForceNoShadowCasting使用该SubShader的物体是否会投影Tags{"ForceNoShadowCasting"="True"}
IgnoreProjector控制物体是否受Projector的影响,通常用于透明物体Tags{"IgnoreProjector"="True"}
CanUseSpriteAtlas用于Sprites的时候需设为False,通常用于UITags{"CanUseSpriteAtlas"="False"}
PreviewType指明材质面板如何预览该材质Tags{"PreviewType"="Plane"}

可以在pass里也写tag。如果外面写了,那么使用的是外面的。

Pass语义块

Pass {
  [Name]    // Name "MyPassName"
  [Tags]
  [RenderSetup]
  // Other code
}

可以使用Name使用其他Unity Shader中的Pass
UsePass "MyShader/MYPASSNAME"   // Pass名会被Unity转换为全大写

// 还有个GrabPass,负责抓取屏幕并将结果存储在一张纹理中用于后续Pass处理
  • Pass标签
标签类型说明例子
LightMode定义该Pass在Unity的渲染流水线中的角色Tags{"LightMode"="ForwardBase"}
RequireOptions指定当满足某些条件时才渲染该Pass,其值为空格分隔的字符串Tags{"RequireOptions"="SoftVegetation"}

注意:不建议写太多pass,会影响性能

Fallback

这是一个shader的最终解决方案,即所有SubShader都失效时的方案。

Fallback中内置一个阴影Pass。如果没有写阴影相关的内容,那么,为了Shader可以正确展示,要设置合适的Fallback。

自定义材质

可以使用CustomEditor语义扩展编辑界面,还可以使用Category语义对Unity Shader中的命令进行分组。

各种着色器

表面着色器

Unity在处理表面着色器的时候,会将其转换为对应的顶点/片元着色器(所以一定程度上这两个着色器可视作同一种)。

示例:

Shader "Custom/Simple Surface Shader" {
  SubShader {
    Tags{"RenderType"="Opaque"}
    CGPROGRAM
    #pragma surface surf Lambert
    struct Input{
      float4 color: COLOR;
    };

    void surf(Input IN, inout SurfaceOutput o){
      o.Albedo = 1;
    }

    ENDCG
  }
  Fallback "Diffuse"
}

CGPROGRAM和ENDCG之间的代码是使用Cg/HLSL编写的,所以我们需要把Cg/HLSL语言嵌套在ShaderLab语言中。 但这里的Cg/HLSL是Unity封装过的,有些函数未实现,且细节上有所不同。

顶点/片元着色器

示例:

Shader "Custom/Simple VertexFragment Shader" {
  SubShader {
    Pass {    // 注意代码写在Pass里
      CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag

        float4 vert(float4 v: POSITION):SV_POSITION {
          return mul (UNITY_MATRIX_MVP, v);
        }

        fixed4 frag(): SV_Target {
          return fixed4(1.0,0.0,0.0,1.0);
        }
      ENDCG
    }
  }
}

写在Pass里的原因是,我们需要自己定义每个Pass,灵活控制Shader。

固定函数着色器

对于老旧设备需要使用该着色器。自Unity 5.2始,该种着色器会编译为顶点/片元着色器。

示例:

Shader "Tutorial/Basic" {
  Properties {
    _Color ("Main Color", Color) = (1,0.5,0.5,1)
  }
  SubShader {
    Pass {
      Material {
        Diffuse [_Color]
      }
      Lighting on
    }
  }
}

着色器适用场景说明

表面着色器,适用于与光源打交道(性能消耗较大)

顶点/片元着色器,适用于多自定义的渲染效果,以及光源少的场合

如果您坚持想用GLSL写着色器,可以将着色器嵌套到GLSLPROGRAM和ENDGLSL之间。当然,这也意味着只有支持GL的平台能够使用。

坐标空间(坐标系空间)

有四种坐标空间:物体,世界,摄像机,投影成像

1、物体空间:即3D物体自己的坐标空间

2、世界空间:即游戏世界空间

3、摄像机空间:即以摄像机为原点的坐标系空间

4、投影成像:即3D坐标转换到屏幕时,屏幕上的二位坐标空间

坐标系之间的转换

这部分的转换需要背诵常用的转换API

API说明
transform.localToWorldMatrix局部转世界的矩阵
transform.worldToLocalMatrix世界坐标转局部坐标矩阵

可通过MultiplyPoint, MultiplyPoint3x4, MultiplyVector来进行坐标变换

Shader中也有坐标变换

|左乘_World2Object矩阵|世界坐标转局部坐标| |左乘_Object2World|局部转世界|

以下是一些矩阵名

说明
UNITY_MATRIX_MV基本变换矩阵 x 摄像机矩阵
UNITY_MATRIX_MVP基本变换矩阵 x 摄像机矩阵 x 投影矩阵
UNITY_MATRIX_V摄像机矩阵
UNITY_MATRIX_P投影矩阵
UNITY_MATRIX_VP摄像机矩阵 x 投影矩阵
UNITY_MATRIX_T_MV(基本变换矩阵*摄像机矩阵)的转置矩阵
UNITY_MATRIX_IT_MV(基本变换矩阵*摄像机矩阵)的逆转置矩阵
UNITY_MATRIX_TEXTURE0纹理变化矩阵

常用语义

说明
POSITION位置
TANGENT切线
NORMAL法线
TEXCOORD0第一套纹理
TEXCOORD1第二套纹理
TEXCOORD2第三套纹理
TEXCOORD3第四套纹理
COLOR颜色

结构体和语义

下面是结构体的格式:

struct name{
  类型 名字;
  // 尽量不使用函数
  返回值 函数名称(参数){
    // 如果成员函数里面使用该结构体的数据成员,
    // 该数据成员一定要定义在函数前
  }
}

基本类型表达式

  • 提供float/half/double浮点类型数值
  • 定点数fixed高效处理某些小数
  • 用int表示整数
  • bool表示逻辑类型
  • sampler*,纹理对象的句柄,sampler/1D/2D/3D/CUBE/RECT
  • 内置向量数据类型:float4(float,float,float,float),长度不能超过4
  • 内置矩阵数据类型:float1x1,float2x3,float4x3,float4x4.最多不能超过4x4
  • 数组类型:float a[10].指10个float;float4 b[10],指10个float4
  • 语义绑定:float4 a: POSITION,返回值也可以语义绑定

附录A:标准内置函数

函数名解释
abs(num)获取绝对值
cross(a,b)向量叉积
determinant(M)矩阵的行列式
dot(a,b)向量点积
floor(x)向下取整
lerp(a,b,f)a,b两点间的线性插值
log2(x)以2为底的x的对数
mul(m,n)矩阵运算。可以是三种情况:矩阵x矩阵,矩阵x向量,向量x矩阵
power(x,y)x的y次方
radians(x)度转弧度
reflect(v,n)v关于法线n的反射向量
round(x)靠近取整
tex2D(sample,x)二位纹理查找
tex3Dproj(sample,x)投影三维纹理查找
texCUBE立方体贴图纹理查找

如果想使用Unity自带函数,需要引入库。引入语句为:

#include "UnityCG.cginc" Unity->Edit->Data->CGIncludes;

附录B:float4 fixed4 _Time

  • 内置向量float4(x,y,z,w)。假设有float4 a;访问单独成员a.x,a.y,a.z,a.w

  • 内置向量fixed4(r,g,b,a)。假设有fixed4 c;访问单独成员c.r,c.g,c.b,c.a

  • 内置向量float3(x,y,z)

  • 内置向量fixed3(r,g,b)

  • 内置向量float2(x,y)

  • _Time

    。假设为t,可获取4个分量:t/20,t,t*2,t*3

  • _SinTime: 时间的正弦值。假设为t,4个分量:t/8,t/4,t/2,t

  • _CosTime: 时间的余弦值。假设为t,4个分量:t/8,t/4,t/2,t

  • unity_DeltaTime: 时间增量。假设为dt, 4个分量:dt,1/dt,smoothDt,1/smoothDt。其中,smoothDt是平滑时间,防止时间间隔起伏太大。

更新于 2025/2/26