Unity Shader入门精要读书笔记系列
第1章 欢迎来到Shader的世界
第2章 渲染流水线
第3章 Unity Shader基础
第4章 学习Shader所需的数学基础
第5章 开始 Unity Shader 学习之旅
前言
在基础篇中,我们学习了渲染流水线,并给出了 Unity Shader 的基本概况,同时还有了一定的数学基础。从本章开始,我们将真正开始学习如何在 Unity中编写 Unity Shader。
一、一个简单的Shader
1.两条编译指令
告诉Unity哪个函数包含顶点着色器代码,哪个函数包含片元着色器代码,函数名可自定义
#pragma vertex vert
#pragma fragment frag
2.输入数据来源(Application Stage to Vertex Shader)
使用一个结构体来定义顶点着色器的输入
// 对于顶点着色器的输入, Unity 支持的语义有: POSITION, TANGENT, NORMAL, TEXCOORDO, TEXCOORD1, TEXCOORD2, TEXCOORD3, COLOR 等。
// 在每帧调用Draw Call时,Unity的Mesh Render组件把它负责渲染的模型数据发送给Unity Shader。(顶点位置、法线、切线、纹理坐标、顶点颜色等)
struct a2v
{
// POSITION语义告诉Unity, 用模型空间的顶点坐标填充vertex变量
float4 vertex : POSITION;
// NORMAL语义告诉Unity, 用模型空间的法线方向填充normal变量
float3 normal : NORMAL;
// TEXCOORD0语义告诉Unity, 用模型的第一套纹理坐标填充texcoord变量
float4 texcoord : TEXCOORD0;
};
3.顶点着色器与片元着色器通信
顶点着色器是逐顶点调用的,而片元着色器是逐片元调用的
片元着色器的输入实际上是把顶点着色器的输出进行插值后得到的结果。
struct v2f
{
// 顶点着色器的输出结构中,必须包含 SV_POSITION
// SV_POSITION语义告诉Unity, pos里包含了顶点在裁剪空间中的位置信息
float4 pos : SV_POSITION;
// COLORO语义可以用于存储颜色信息
float3 color : COLOR0;
float3 nDirWS : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// v.normal包含了顶点的法线方向 , 其分量范围在[-1.0, 1.0]
// 下面的代码把分量范围映射到了[0.0, 1.0]
o.nDirWS = UnityObjectToWorldNormal(v.normal);
o.uv = v.texcoord;
// 存储到o.color中传递给片元着色器
o.color = v.normal * 0.5 + fixed3(0.5, 0.5, 0.5);
return o;
}
// SV_Target表示把用户的输出颜色存到一个渲染目标(render target)中,这里默认是帧缓存
fixed4 frag (v2f i) : SV_Target
{
float3 nDir = i.nDirWS;
float3 mask1 = max(nDir.g, 0.0);
float3 mask2 = max(-nDir.g, 0.0);
float3 mask3 = 1 - mask1 - mask2;
float3 EnvLightCol = mask1 * _Color1 + mask2 * _Color2 + mask3 * _Color3;
return float4(EnvLightCol, 1.0);
}
4.使用属性
在之前的学习中我们知道可以通过Properties语义设置属性。
为了能在材质面板调节属性来调整材质的效果,我们需要将属性声明的变量应用到Shader计算中。
属性在上面第3点中参与Shader计算
Properties
{
// 声明Color类型的属性
_Color1("Color Tint 1", Color) = (1.0, 1.0, 1.0, 1.0)
_Color2("Color Tint 2", Color) = (1.0, 1.0, 1.0, 1.0)
_Color3("Color Tint 3", Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
Pass
{
// 包裹CG代码
CGPROGRAM
// 在CG代码中,我们需要定义一个与属性名称和类型都匹配的变量
fixed4 _Color1;
fixed4 _Color2;
fixed4 _Color3;
//...
ENDCG
}
}
有时,读者可能会发现在 CG 变量前会有 uniform 关键字
uniform fixed4 Color;
unifom 关键词是 CG 中修饰变量和参数的一种修饰词,它仅仅用于提供一些关于该变量的初始值是如何指定和存储的相关信息。在Unity中可以省略。
Shaderlab属性类型和CG变量类型的匹配关系
Shaderlab属性类型 | CG变量类型 |
---|---|
Color, Vector | float4, half4, fixed4 |
Range, Float | float, half, fixed |
2D | sampler2D |
Cube | samplerCube |
3D | sampler3D |
以上就是实现了环境光遮蔽的一个简单Shader代码。
二、Unity提供的内置文件和变量
Unity提供了很多内置文件,这些文件包括了很多提前定义的函数、变量和宏等。可以帮助我们节省很多操作和代码量。
可以在 . .\Unity\Editor\Data\CGIncludes文件夹中找到所有内置文件。
Unity中一些常用的包含文件
文件名 | 描述 |
---|---|
UnityCG.cginc | 包含了最常使用的帮助函数、宏和结构体等 |
UnityShaderVariables.cginc | 在编译Unity Shader时,会被自动包含进来。包含了许多内置的全局变量,如UNITY_MATRIX_MVP等 |
Lighting.cginc | 包含了各种内置的光照模型,如果编写的是Surface Shader的话,会自动包含进来。 |
HLSLSupport.cginc | 在编译Unity Shader时会被自动包含进来。声明了很多用于跨平台编译的宏和定义 |
使用 #include指令将他们包含进Shader:
#include "UnityCG.cginc"
其中UnityCG.cginc提供了一些常用的结构体和帮助函数:
UnityCG.cginc中一些常用的结构体
名称 | 描述 | 包含的变量 |
---|---|---|
appdata_base | 可用于顶点着色器的输入 | 顶点位置、顶点法线、第一组纹理坐标 |
appdata_tan | 可用于顶点着色器的输入 | 顶点位置、顶点切线、顶点法线、第一组纹理坐标 |
appdata_full | 可用于顶点着色器的输入 | 顶点位置、顶点切线、顶点法线、四组(或更多)纹理坐标 |
appdata_img | 可用于顶点着色器的输入 | 顶点位置、第一组纹理坐标 |
v2f_img | 可用于顶点着色器的输出 | 裁剪空间中的位置、纹理坐标 |
UnityCG.cginc中一些常用的帮助函数
函数名 | 描述 |
---|---|
float3 WorldSpaceViewDir (floal4 v) | 输入一个模型空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向 |
float3 UnityWorldSpaceViewDir (floal4 v) | 输入一个世界空间中的顶点位置,返回世界空间中从该点到摄像机的观察方向 |
float3 ObjSpaceViewDir (tloat4 v) | 输入一个模型空间中的顶点位置,返回模型空间中从该点到摄像机的观察方向 |
float3 WorldSpaceLightDir (float4 v) | 仅可用用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化 |
float3 ObjSpaceLightDir (float4 v) | 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。 没有被归一化 |
float3 UnityObjectToWorldNormal (float3 norm) | 把法线方向从模型空间转换到世界空间中 |
float3 UnityObjectToWorldDir (float3 dir) | 把方向矢量从模型空间变换到世界空间中 |
float3 UnityWorldToObjectDir(float3 dir) | 把方向矢量从世界空间变换到模型空间中 |
Unity还提供了用于访问时间、 光照、 雾效和环境光等目的的变量。 这些内置变量大多位于
UnityShaderVariables.cginc中,与光照有关的内置变量还会位于Lighting.cginc、 AutoLight.cginc
等文件中.
三、Shader调试:Debug
TODO