第四章 开始Unity Shader学习之旅(2)
1. 强大的援手:Unity提供的内置文件和变量
上面,我们讲述了如何在Unity中编写一个基本的顶点/片元着色器的过程。顶点/片元着色器的复杂之处在于,很多事情都要我们“亲力亲为”,例如我们需要自己转换法线方向,自己处理光照、阴影等。为了方便开发者的编码过程,Unity提供了很多内置文件,这些文件包含了很多提前预定义的函数、变量和宏等。如果读者在学习其它人编写的UnityShader代码时,遇到了一些从未见到的变量、函数,而又无法找到对应的声明和定义,那么很有可能就是这些代码使用了Unity内置文件提供的函数和变量。
下面我们给出这些文件和变量的预览。
1.1 内置的包含文件
包含文件(include file),是类似于c++中头文件的一种文件。在Unity中,它们的文件后缀是.cginc。在编写Shader时,我们可以使用#include指令把这些文件包含进来,这样我们就可以使用Unity为我们提供的一些非常有用的变量和帮助函数,例如:
CGPROGRAM
//......
#include "UnityCG.cgnic"
//......
ENDCG
那么,这些文件在哪里呢?我们可以在官方网站(http://unity3d.com/cn/get-unity/download/archive)上选择下载->内置着色器,来直接下载这些文件,下图显示了由官网压缩包得到的文件。

从图中可以看出,从官网下载的文件中包含了多个文件夹。其中,CGIncludes文件夹中包含了所有的内置文件;DefaultResources文件夹中包含了一些内置组件或功能所需要的Unity Shader,例如一些GUI元素使用的Shader;DefaultResourcesExtra则包含了所有Unity中内置的Unity Shader;Editor文件夹目前只包含了一个脚本文件,它用于定义Unity5引入的StandardShader所使用的的材质面板。这些文件都是非常好的参考资料,在我们想要学习内置着色器的实现或是寻找内置函数的实现时,都可以在这里找到内部实现。但在本节中,我们只关注CGIncludes文件夹下的相关文件。
我们也可以从Unity的应用程序中直接找到CGIncludes文件夹。它的位置是:Unity的安装路径/Data/CGIncludes.。
下表给出了CGIncludes中主要包含的文件以及它们的主要用处。

可以看出,有一些文件即便是我们没有使用#include指令,它们也是会被自动包含进来的,例如UnityShaderVariables.cginc。因此,在前面的例子中,我们可以直接使用UNITY_MATRIX_MVP变量来进行顶点变换。除了上表列出的包含文件外,Unity5引入了许多重要的包含文件,如UnityStandardBRDF.cginc、UnityStandardCore.cginc等,这些包含文件用于实现基于物理的渲染,我们会在后面说道。
UnityCG.cginc是我们最常接触的一个包含文件,在后面的学习中,我们将使用很多文件提供的结构体和函数,为我们的编写提供方便。例如我们可以直接使用UnityCg.cginc中预定义的结构体作为顶点着色器的输入和输出。下表给出了一些结构体的名称和包含的变量。

强烈建议读者找到UnityCG.cginc文件并查看上述结构体的声明,这样的过程可以帮助我们快速的理解Unity中一些内置变量的工作原理。
除了结构体外,UnityCG.cginc也提供了一些常用的帮助函数。下表给出了一些函数名和它们的描述。

我们建议读者在UnityCG.cginc文件找到这些函数的定义,并尝试理解它们。一些函数我们完全可以自己实现,例如 UnityObjectToWorldDir和 UnityWorldToObjectDir,这两个函数实际上就是对方向矢量进行了一次坐标空间变换。而UnityCG.cginc文件可以帮我们提高代码的复用率。UnityCG.cginc还包含了很多宏,在后面的学习中,我们就会遇到它们。
1.2 内置的变量
我们在以前给出了一些用于坐标变换和摄像机参数的内置变量。除此之外,Unity还提供了用于访问时间、光照、雾效和环境光等目的的变量。这些内置变量大多位于UnityShaderVariables.cginc中,与光照有关的内置变量还会位于Lighting.cginc、AutoLight.cginc等文件中。当我们在后面的学习中遇到这些变量时再进行详细的讲解。
2. Unity提供的Cg/HLSL语义
读者在平时的Shader学习中可能经常看到,在顶点着色器和片元着色器的输入输出变量后还有一个冒号以及一个全部大写的名称,例如SV_POSITION、POSITION、COLOR0。这些大写的名字是什么呢?它们有什么用呢?
2.1 什么是语义
实际上,这些是Cg/HLSL提供的语义(semantics)。语义实际上就是一个赋给Shader输入和输出的字符串,这个字符串表达了这个参数的含义。通俗地讲,这些语义可以让Shader知道从哪里读取数据,并把数据输出到哪里,它们在Cg/HLSL的Shader流水线中是不可或缺的。需要注意的是,Unity并没有支持所有的语义。
通常情况下,这写输入输出并不需要有特别的意义,也就是说,我们可以自行决定这些变量的用途。例如在上面的代码中,顶点着色器的输出结构体中我们用Color0语义去描述color变量。color变量本身存储了什么,Shader流水线并不关心。
而Unity为了方便对模型数据的传输,对一些语义进行了特别含义的规定。例如,在顶点着色器中的输入结构体a2v用TEXCOORD0来描述texcoord,Unity会识别TEXCOORD0的语义,以把模型的第一组纹理坐标填充到texcoord中。需要注意的是,即便语义的名称一样,如果出现的位置不同,含义也不同。例如TEXCOORD0即可用于描述顶点着色器的输入结构体a2v,也可用于描述输出结构体v2f。但在输入结构体a2v中,TEXCOORD0有特别的含义,即把模型的第一组纹理坐标存储在该变量中,而在输出结构体v2f中,TEXCOORD0修饰的变量含义就可以由我们来决定。
在DirectX 10以后,有了一种新的语义类型,就是系统数值语义(system-value semantics)。这类语义是以SV开头的,SV代表的含义就是系统数值(system-value)。这些语义在渲染流水线中有特殊的含义。例如在上面的代码中,我们使用SV_POSITION语义去修饰顶点着色器的输出变量pos,那么就表示pos包含了可用于光栅化的变换后的顶点坐标(即齐次裁剪空间中的坐标)。用这些语义描述的变量是不可以随便赋值的,因为流水线需要它们来完成特定的目的,例如渲染引擎会把用SV_POSITION修饰的变量经过光栅化后显示在屏幕上。读者有时可能会看到同一个变量在不同的Shader里面使用了不同的语义修饰。例如一些Shader会使用POSITION而非SV_POSITION来修饰顶点着色器的输出。SV_POSITION是DirectX 10中引入的系统数值语义,在绝大多数平台上,它和POSITION语义是等价的,但在某些平台(如索尼PS4)上必须使用SV_POSITION来修饰顶点着色器的输出,否则无法让Shader正常工作。同样的例子还有COLOR和SV_Target。因此为了让我们的Shader有更好的跨平台性,对于这些有特殊含义的变量我们最好使用SV开头的语义进行修饰。
2.2 Unity支持的语义
下表总结了从应用阶段传递模型数据给顶点着色器时Unity使用的常用语义。这些语义虽然没有使用SV开头,但在Unity内部赋予了它们特殊的含义。

其中TEXCOORDn中的n的数目是和Shader Model有关的,例如一般在Shader Model 2(即Unity默认编译到的Shader Model版本)和Shader Model 3中,n等于8,而在Shader Model 4和Shader Model 5中,n等于16。通常情况下,一个模型的纹理坐标组数一般不超过2,即我们往往只使用TEXCOORD0和TEXCOORD1。在Unity内置的数据结构体appdata_full中,它最多使用6个坐标纹理组。
下表总结了从顶点着色器到片元着色器阶段Unity支持的常用语义。

上面的语义中,除了SV_POSITION有特别含义外,其它语义对变量的含义没有明确要求,也就是说,我们可以存储任意值带这些语义描述变量中。通常,如果我们需要把一些自定义的数据从顶点着色器传递给片元着色器,一般选用TEXCOORD0等。
下表给出了Unity中支持的片元着色器的输出语义。

2.3 如何定义复杂的变量类型
上面提到的语义绝大部分用于描述标量或矢量类型的变量,例如fixed2、float、float4、fixed4等。下面的代码给出了一个使用语义来修饰不同类型变量的例子。
struct v2f{
float4 pos:SV_POSITION;
fixed3 color0:COLOR0;
fixed4 color1 :COLOR1;
half value0:TEXCOORD0;
float2 value1:TEXCOORD1;
};
关于何时使用哪种类型的变量,我们会在后面给出一些建议,但要注意的是,一个语义可以使用的寄存器只能处理4个浮点值(float)。因此如果我们想要定义矩阵类型,例如float3×4、float4×4等变量就需要使用更多的空间。一种方法是,把这些变量拆分成多个变量,例如对float4×4的矩阵类型,我们可以拆分成4个float4类型的变量,每个变量存储了矩阵的一行数据。
第四章 开始Unity Shader学习之旅(2)的更多相关文章
- 第四章 开始Unity Shader学习之旅(1)
1. 一个最简单的顶点/片元着色器 现在,我们正式开始学习如何编写Unity Shader,更准确的说是,学习如何编写顶点/片元着色器 2.顶点/片元着色器的基本结构 我们在以前已经讲过了Unity ...
- 第四章 开始Unity Shader学习之旅(3)
1. 程序员的烦恼:Debug 调试(debug),大概是所有程序员的噩梦.而不幸的是,对一个Shader进行调试更是噩梦中的噩梦.这也是造成Shader难写的原因之一--如果发现得到的效果不对,我们 ...
- Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅
一个顶点/片元 着色器的结构大概如下: Shader "MyShaderName" { Properties { //属性 } SubShader { //针对显卡A的SubSha ...
- Unity Shader 学习之旅
Unity Shader 学习之旅 unityshader图形图像 纸上学来终觉浅,绝知此事要躬行 美丽的梦和美丽的诗一样 都是可遇而不可求的——席慕蓉 一.渲染流水线 示例图 Tips:什么是 GP ...
- Unity Shader 学习之旅之SurfaceShader
Unity Shader 学习之旅之SurfaceShader unity shader 图形图像 如果大地的每个角落都充满了光明 谁还需要星星,谁还会 在夜里凝望 寻找遥远的安慰——江河 官方文档 ...
- Unity Shader学习笔记-1
本篇文章是对Unity Shader入门精要的学习笔记,插图大部分来自冯乐乐女神的github 如果有什么说的不正确的请批评指正 目录 渲染流水线 流程图 Shader作用 屏幕映射 三角形遍历 两大 ...
- 【Unity Shader学习笔记】Unity基础纹理-单张纹理
1 单张纹理 1.1 纹理 使用纹理映射(Texture Mapping)技术,我们把一张图片逐纹素(Texel)地控制模型的颜色. 美术人员建模时,会在建模软件中利用纹理展开技术把纹理映射坐标(Te ...
- Unity shader学习之屏幕后期处理效果之高斯模糊
高斯模糊,见 百度百科. 也使用卷积来实现,每个卷积元素的公式为: 其中б是标准方差,一般取值为1. x和y分别对应当前位置到卷积中心的整数距离. 由于需要对高斯核中的权重进行归一化,即使所有权重相加 ...
- 第四章:重构代码[学习Android Studio汉化教程]
第四章 Refactoring Code The solutions you develop in Android Studio will not always follow a straight p ...
随机推荐
- Unity1-HellowWord
1.新建一个Unity工程,选择3D类型项目. 2.目录下有: Assets是主要操作的目录. 3.面板 4.做一个简单的方块移动效果: 1.在Hierarchy面板中,点击Create-3D Obj ...
- 【长期维护】C++休闲(修仙)躲方块小游戏
左右键控制小球左右移动,上键加速,Esc退出. 一个‘@’20分 #include <windows.h> #include <bits/stdc++.h> #include ...
- [考试反思]0805NOIP模拟测试13:窒息
呼啊...苟住了.rank #3 第二次分机房的收官之战.发挥比较稳定 然而差点就不稳定了!!! 过了一遍题目,难度大约是升序,但是一道都不会做!!! 本来感觉T1是一道数学题,以为45分钟以内可以切 ...
- 深入理解C#第三版部分内容
最近,粗略的读了<深入理解C#(第三版)>这本技术书,书中介绍了C#不同版本之间的不同以及新的功能. 现在将部分摘录的内容贴在下面,以备查阅. C#语言特性: 1.C#2.0 C#2的主 ...
- java中 equals和==区别
一.java当中的数据类型和“==”的含义: 基本数据类型(也称原始数据类型) :byte,short,char,int,long,float,double,boolean.他们之间的比较,应用双等号 ...
- MySQL InnoDB MVCC
MySQL 原理篇 MySQL 索引机制 MySQL 体系结构及存储引擎 MySQL 语句执行过程详解 MySQL 执行计划详解 MySQL InnoDB 缓冲池 MySQL InnoDB 事务 My ...
- java编程思想第四版第三章要点总结
1. 静态导入 使用import static方式导入一个类的所有方法. 例如: import static net.mindview.util.Print.*; 首先定义了一个Print类,里面有静 ...
- 在VMware15.5中安装CentOS7_7_64bit
一.创建虚拟机 在我的另一个随笔里有. 地址为:https://www.cnblogs.com/qi-yuan/p/11692092.html 只是在虚拟机安装操作系统时候选择 Linux 而不是 W ...
- 理解Spark运行模式(三)(STANDALONE和Local)
前两篇介绍了Spark的yarn client和yarn cluster模式,本篇继续介绍Spark的STANDALONE模式和Local模式. 下面具体还是用计算PI的程序来说明,examples中 ...
- 力扣(LeetCode)两整数之和 个人题解
不使用运算符 + 和 - ,计算两整数 a .b 之和. 示例 1: 输入: a = 1, b = 2 输出: 3 示例 2: 输入: a = -2, b = ...