《Unity Shader入门精要》读书笔记04

铁名_IronName Lv2

终于从上一章爬出来了。。。

Unity 版本为 2022.3

第 5 章 开始 Unity Shader 学习之旅

绝大部分代码写在SubShader->Pass中。

一个最简单的顶点/片元着色器

1
2
#prama vertex name1
#prama fragment name2

它们告诉Unity,哪个函数包含了顶点着色器的代码,哪个函数包含了片元着色器的代码。

vert 函数

1
2
3
4
float4 vert(float4 v : POSITION) : SV_POSITION {
retrun mul(UNITY_MATRIX_MVP,v);
// 在新版中应该用 UnityObjectToClipPos(v);
}

vert函数的输入 v 包含了这个顶点的位置,这是通过POSITION语义指定的。它的返回值是一个 float4 类型的变量,它是该顶点在裁剪空间中的位置,POSITION 和 SV_POSITION 都是CG/HLSL中的语义(semantics),它们是不可省略的,这些语义将告诉系统用户需要哪些输入值,以及用户的输出是什么。例如这里,POSITION将告诉Unity,把模型的顶点坐标填充到输入参数v中,SV_POSITION将告诉Unity,顶点着色器的输出是裁剪空间中的顶点坐标。

frag 函数

1
2
3
fixed4 frag() : SV_Target {
return fixed4(1.0, 1.0, 1.0, 1.0);
}

SV_Target : HLSL中的一个系统语义,它等同于告诉渲染器,把用户的输出颜色存储到一个渲染目标(rendertarget)中,这里将输出到默认的帧缓存中。

来自模型的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
// 使用一个结构体来定义顶点着色器的输入
// a表示应用(application),v表示顶点着色器(vertex shader),a2v的意思就是把数据从应用阶段传递到顶点着色器中。
struct a2v {
// POSITION: 用模型空间的顶点坐标
float4 vertex : POSITION;
// NORMAL: 用模型空间的法线方向
float3 normal : NORMAL;
// TEXCOORD0: 用模型空间的第一套纹理坐标
float4 texcoord : TEXCOORD0;
};
float4 vert(a2v v) : SV_POSITION {
return UnityObjectToClipPos(v.vertex);
}

结构体定义格式

1
2
3
4
5
6
struct StructName {
Type Name : Semantic;
Type Name : Semantic;
// .....
// 语义是不可以省略的
};

用结构体在 vert 和 frag 之间通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//...
struct v2f {
//SV_POSITION 语义告诉 Unity, pos 里包含了顶点在裁剪空间中的位置信息
float4 pos : SV_POSITION;
// COLOR0 语义可以用于存储颜色信息
fixed3 color : COLOR0;
};

v2f vert(a2v v) {
// 声明输出结构
v2f o;
o.pos = UnityObjectToClipPos(v);
// v.norma1包含了顶点的法线方向, 其分量范围在[-1.0, 1.0]
// 下面的代码把分量范围映射到了[0.0, 1.0]
// 存储到o.color中传递给片元着色器
o.color=v.normal*0.5+fixed3(0.5,0.5,0.5);
return o;
}

fixed4 frag(v2f i) : SV_Target {
// 将插值后的i.color显示到屏幕上
return fixed4(i.color, 1.0);
}
//...

使用属性Properties

1
2
3
4
5
6
7
8
9
10
11
Shader "Unity Shaders Book/Chapter 5/Simple Shader"{
Properties {
// 声明一个Color类型的属性
_Color ("Color Tint", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;

Unity内置文件和变量

内置的包含文件

1
2
3
4
5
CGPROGRAM
// ...
#include "UnityCG.cginc"
// ...
ENDCG

它们的位置:Unity的安装路径/Data/CGIncludes

文件名描述
UnityCG.cginc包含了最常使用的帮助函数、宏和结构体等
UnityShaderVariables.cginc在编译UnityShader时,会被自动包含进来。包含了许多内置的全局变量,如UNITY_MATRIX_MVP等
Lighting.cginc包含了各种内置的光照模型,如果编写的SurfaceShader的话,会自动包含进来
HLSLSupport.cginc在编译Unity Shader时,会被自动包含进来。声明了很多用于跨平台编译的宏和定义
  • 我们可以直接使用UnityCGcginc中预定义的结构体作为顶点着色器的输入和输出。
名称包含的变量
appdata_base顶点位置、顶点法线、第一组纹理坐标
appdata_tan顶点位置、顶点切线、顶点法线、第一组纹理坐标
appdata_full顶点位置、顶点切线、顶点法线、四组(或更多)纹理坐标
appdata_img顶点位置、第一组纹理坐标
v2f_img(用作输出)裁剪空间中的位置、纹理坐标
还有一些常用的帮助函数。还有很多宏。以后用到再说。

Unity 提供的 CG/HLSL 语义

系统数值语义(system-value semantics),以SV开头,代表系统数值(system-value)。

为了让我们的Shader有更好的跨平台性,对于这些有特殊含义的变量我们最好使用以SV开头的语义进行修饰。

Unity 支持的语义

从应用阶段传递模型数据到顶点着色器
(….省略,用到了再查,真的常用后面再整理)

Debug

1.假彩色图像(false-color image)——也就是把数据作为颜色输出
2.利用 Visual Studio——Graphics Debugger
3.帧调试器(Unity自带,Window->Analysis->Frame Debugger)可以用于查看渲染该帧时进行的各种渲染事件(event)。

渲染平台的差异

在OpenGL(OpenGLES也是)中,(0,0)点对应了屏幕的左下角,而在DirectX(Metal也是)中,(0,0)点对应了左上角。

  • 当我们要使用渲染到纹理技术,把屏幕图像渲染到张渲染纹理中时,如果不采取任何措施的话,就会出现纹理翻转的情况。
    除此之外还有语法和语义的差异。同样,以后遇到了再做记录。

关于Shader代码的建议

  • float的精度大于half, fixed, 所以优化时可以考虑降精度。
  • 如果要发布到DirectX平台上,就要使用更严格的语法。
  • 不同的ShaderTarget、不同的着色器阶段,我们可使用的临时寄存器和指令数目都是不同的。 对应指令#pragma target 2.0
  • 慎用分支和循环语句。GPU使用了不同于CPU的技术来实现分支语句,在最坏的情况下,我们花在一个分支语句的时间相当于运行了所有分支语句的时间。
    一个改善方法是,尽量把计算向流水线上端移动,例如把放在片元着色器中的计算放到顶点着色器中,或者直接在CPU中进行预计算,再把结果传递Shader。
  • 不要除以0。对那些除数可能为0的情况,强制截取到非0范围。
  • 标题: 《Unity Shader入门精要》读书笔记04
  • 作者: 铁名_IronName
  • 创建于 : 2026-01-14 15:03:57
  • 更新于 : 2026-01-15 15:26:30
  • 链接: https://blog.ironname.top/2026/01/14/《Unity-Shader入门精要》读书笔记04/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论