【Unity】ShaderLab语法总结
预备知识
-
模型文件存储结构(包含了顶点位置以及面的组成信息)
渲染过程
渲染管线的知识,很有必要去冯乐乐的书里看一下,在第9页( 2.3 GPU 流水线 )
不同的渲染管线会有不同的差异,但是大体还是类似,要经过以上的一些步骤。
流水线中,我简化了重点去理解:
- 首先CPU把顶点数据传递给Shader的顶点着色器(在Unity中就是通过Mesh Renderer组件),开始一次DrawCall。
- GPU在几何阶段把三维的坐标通过自己定义的顶点函数,按照给定的规则映射到二维的屏幕坐标中。
- 于是屏幕上就出现了很多个点,但是还没有连成闭合的区域,所以在三角形遍历的时候,就是通过插值的方式在各个点包围的三角形内部填充其他点的信息。
- 片元着色器把三角形中的每个点给拿过来,通过自定义的规则,为每个像素填充颜色。
- 填完颜色后,放入缓存,等待后续的测试,例如混合测试。(比如透明物体背后还有一个物体,那么两个物体的颜色怎么混合)。
- 最后输出屏幕。
可以用以下简化的图来表示,其中Vertex Processor和Pixel Processor中间还有重要的插值的一步,因为顶点的个数是远少于像素的个数的,那么这个输入输出该怎么对应?
如果像素有多少个,至少顶点也要有多少个吧,所以多余的顶点的信息(包括位置,顶点法线,颜色等等)就是通过原本的顶点插值计算出来的。
![](http://upload-images.jianshu.io/upload_images/22973577-afce712d4ebd2164.png?imageMogr2/auto-orient/strip|imageView2/2/w/1069/format/webp)
Shader是图形可编程方案的程序片段
渲染管线是一种计算机从数据到最终图形程序的形象描述
材质是商品,Shader是方法,贴图是材料
图形语言包括:
OpenGL的GLSL
DirectX的HLSL
NVIDIA的CG
等GPU采用并行计算,因此在顶点处理程序中,所有顶点信息可以同时计算,并且耗费相同时间。
渲染管线最终认识的还是Vertex Shader和Fragment Shader,Unity发明的Surface Shader只是对这两种Shader的包装。
在Vertex Shader中,包含顶点坐标转换,顶点色、顶点法线的计算等工作,例如把模型文件中的点的信息转换成屏幕上的坐标信息等。
渲染的过程中,一些Unity的全局光照设置、主摄像机等等信息都会作为全局变量,供Shader的使用。
每一个像素格都会根据顶点着色器Fragment Shader中的方法计算出结果
ShaderLab 的基本结构
- ShaderLab语言是对HLSL、CG等语言的封装。更准确的说,HLSL、CG等语言是以内嵌的方式存在于ShaderLab语言中的。所以当遇到内嵌的代码段的时候,其中的语法和其外面包裹着的ShaderLab的语法是不一样的。
shader "name"{
[Properties]
SubShaders
[Fallback]
}
以下的规则仅仅指ShaderLab的语法,不代表其中内嵌的CG、HLSL的语法。
- ShaderLab中不区分大小写
- ShaderLab中不需要分号
- ShaderLab中必须至少要有一个SubShader
- 由于硬件不同,因此可以有多个SubShader,如果第一个SubShader不支持该硬件,那么就会顺序执行第二个SubShader
- 如果所有的SubShader都不能执行,那么在Fallback中就会执行最后处理,通常在这里的处理用的都是Unity内建的一些通用的基础Shader
先从连连开ShaderForge开始
- 这里的连连看插件用的是Shader Forge,但是现在已经不更新了,最后更新是在2018的版本中,但是也可以用。
- Shader Forge最后生成的Shader代码没有Shader Graph那么多,稍微方便一点,所以先用这个。
用ShaderForge实现兰伯特光照
兰伯特光照是最基础的漫反射模型,其效果如下
![]()
在兰伯特光照中,很明显,朝向光的点最亮(灰度为1),背向光的点最暗(灰度为0),中间则介于0~1之间。
通过光的反方向lDir和某个点的朝向(该点法向量)nDir的接近程度,就可以得知该点是否面朝光。
刚好,数学上的点乘Dot刚好可以用于表示两个向量方向的接近程度,数值越大,两者方向越接近。(两个向量前提都经过了归一化处理)
点乘的定义是:a·b = |a||b|cosθ
当某点面朝光时,θ = 0,那么cosθ = 1。最后点乘结果也是等于1。
对模型上的每个点都进行一次 灰度值 = a·b 的运算,就可以得到整个模型经过漫反射之后的外观。
通过上面的描述,我们首先需要获取两个重要的量,一个是全局光照的方向IDir,一个是几何体上的某点的法向量nDir。打开ShaderForge,空白处右键:
这两个数据一个在Lighting下,是全局的,一个在Geometry Data下。
对它们进行点乘,点乘节点位于向量运算Vector Operations里。
点击左上角Compile Shader就可以将ShaderForge中的连接编译成shader代码。
选中创建的shader右键create->material,创建材质。
将材质赋予游戏物体,得到最后效果。
Fixed Function Shader( 了解 )
参考文章:
https://blog.csdn.net/weixin_30699831/article/details/95653586
固定命令的Shader,可以在所有硬件平台上运行,但是功能有限。
固定渲染管线中,主要包含以下代码块:
![](http://upload-images.jianshu.io/upload_images/22973577-6a43f22bf74594db.png?imageMogr2/auto-orient/strip|imageView2/2/w/271/format/webp)
Shader "Custom/s01"
{
Properties
{
_Color ("Color", Color) = (1,1,1,1)
_Ambient("Ambient",Color) = (0.3,0.3,0.3,0.3)
_Specular("Specular",Color) = (1,1,1,1)
_Shininess("Shininess",Float) = 0.3
_Emission("Emission",Color) = (1,1,1,1)
}
SubShader
{
Pass{
Material{
Diffuse[_Color]
Ambient[_Ambient]
Specular[_Specular]
Shininess[_Shininess]
Emission[_Emission]
}
Lighting on
SeparateSpecular on
}
}
}
CG语法
- 写在CGPROGRAM和ENDCG包裹的片段中,但是这两个关键字是ShaderLab的语法,不是CG的语法。
- CG语言区分大小写
- 通过
#pragma vertex 顶点函数名
声明顶点着色器 - 通过
#pragma fragment 片元函数名
声明片元(像素)着色器 - 通过
#include "库函数名.cginc"
声明外部引用的库
数据类型
- float4:4维向量,分量分别是xyzw。每个分量的占32bit,精确到小数点后6位。
- half4: 4维向量,每个分量占16bit,表示范围为[-60000,60000],精确到小数点后3位。
- fixed4:4维向量,分量分别是rgba。每个分量占11bit。表示范围为[-2,2],精度为1/256
使用分量的时候,可以通过.x/.y/.z/.w/.xy/.xz等等,甚至还可以.xxyy。
例如以下的赋值方式都合法的:
float4 pos;
float3 temp = pos.xyz;
temp = pos.yzz;
temp = float3(pos.yz,1);
不可以将低维向高维转换,因为缺失了高维的数据。
例如: float3(1,0,0) --x--> float4(1,0,0,?)
可以将高维向低维转换,会直接舍弃高维数据。
例如: float4(1,0,0,1) --√-->float3(1,0,0)
-
坐标位置通常会用float4表示,由于float4类型的位置可以方便矩阵变换。所以需要将原本float3类型的位置信息扩散到齐次坐标空间。并且分量w可以用于表示该坐标是一个点,还是一个向量。
点,用float4表示,就是float4(x,y,z,1)。
向量,用float4表示,就是float4(x,y,z,0)。 - 颜色通常会用fixed4来表示,因为颜色的表示不需要那么大的精度
- 齐次坐标空间:将一个原本是n维的向量用一个n+1维向量来表示
各种语义的意义和范围
Unity中Renderer组件传递给顶点着色器的关联语义
POSITION : 模型空间(相对于模型自身)的顶点坐标,float4类型。分量xyz可用于表示的范围为[-0.5,0.5],分量w的取值为0或1,0代表点,1代表向量。
NORMAL : 顶点法线,float3类型
TANGENT :顶点切线,float4类型
TEXCOORDn :第n套纹理坐标,float2或float4类型
COLOR : 顶点颜色,fixed4或float4类型顶点着色器输出数据的语义
SV_POSITION :剪裁空间(屏幕空间)下的坐标。左下角为原点(0,0) 横轴的范围是(0,屏幕宽度) 纵轴的范围是(0,屏幕高度)。结构体中必须包含一个用该语义修饰的变量。
COLOR0 : 通常用于输出第一组顶点颜色,但不是必需的
COLOR1 : 通常用于输出第二组顶点颜色,但不是必需的
TEXCOORD0~TEXCOORD7 : 通常用于输出纹理坐标,但不是必需的片元着色器输出数据的语义
SV_TARGET : 最终输出屏幕的颜色值,fixed4或float4类型。有时候这个语义可以写成COLOR,但是由于SV_TARGET在各个平台上都容易被识别,所以还是选用这个。