unity的shader初体验
第一个实例代码
Shader "Unlit/Chapter5/SimpleShader"
{
Properties
{
//_MainTex ("Texture", 2D) = "white" {}
//声明一个color类型的属性
_Color("Color Tint",Color) = (1.0, 1.0, 1.0, 1.0)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert//vert即为函数名
#pragma fragment frag//frag也为函数名
//告诉unity哪个函数中包含了定点着色器的代码,哪个函数包含片元着色器代码
// make fog work
//在Cg代码中,我们需要定义一个与属性名称和类型都匹配的变量
fixed4 _Color;
//使用一个结构体来定义顶点着色器的输入
struct a2v {
//POSITION语义告诉unity用模型空间的定点坐标填充vertex变量
float4 vertex : POSITION;
//NORMAL语义告诉unity用模型空间的法线方向填充normal变量
float3 normal : NORMAL;
//TEXCOORD0语义告诉unity,用模型的第一套纹理坐标填充texcoord变量
float4 texcoord : TEXCOORD0;
};
//使用一个结构体来定义顶点着色器的输出
struct v2f {
//SV_POSITION语义告诉unity,pos里包含了顶点裁剪空间中的位置信息
float4 pos : SV_POSITION;
//COLOR0语义可以用于存储颜色信息
fixed3 color : COLOR0;
};
//unity支持的语义有:POSITION TANGENT NORMAL TEXCOORD0~3 COLOR等
v2f vert(a2v v) {
//声明输出结构
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//v.normal包含了顶点的法线方向,其分量范围在[-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显示到屏幕上
fixed3 c = i.color;
//使用_Color属性来控制输出颜色
c *= _Color.rgb;
return fixed4(c, 1.0);
}
ENDCG
}
}
}
在书的 p106 中有ShaderLab属性类型与Cg变量类型的匹配关系
在书的p108中包含了CGInclude中主要的包含文件以及主要用处、UnityCG.cginc中的一些常用的结构体、一些常用的帮助函数
Unity提供的Cg/HLSL语义
输入结构体:a2v
输出结构体:v2f
对于一些语义在 a2v 和 v2f 中出现时的作用并不一样,例如:
TEXCOORD0在输入结构体中的作用为:把模型的第一组纹理坐标存储在该变量中
而在输出结构体中的作用为:修饰的变量含义可以由我们来决定
系统数值语义
这类语义一般以SV开头(system-value),在上述代码中我们使用了SV_POSITION语义去修饰顶点着色器的输出变量pos,这些语义描述的变量是不可以随便赋值的,渲染引擎会把用SV_POSITION修饰的变量经过光栅化后显示在屏幕上
注:为了让shader更具有跨平台性,最好对有特殊意义的变量我们最好使用以SV开头的语义进行修饰
p110页具有Unity在渲染阶段支持的常用语义
(appdata_full最多使用了6个坐标纹理组 基本上包含了所有的模型数据)
如何调试?
假彩色图像
用假彩色生成一种图像
因为颜色分量范围在[0,1]之间,我们要小心处理需要调试的变量的范围,可以将需要测试的变量范围*映射到[0,1]*之间,把它们作为颜色输出到屏幕上
例子:
Shader "Unity Shaders Book/Chapter 5/False Color"
{
Properties
{
//_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
//Tags { "RenderType"="Opaque" }
//LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#include "UnityCG.cginc"
struct v2f
{
//float2 uv : TEXCOORD0;
//UNITY_FOG_COORDS(1)
//float4 vertex : SV_POSITION;
float4 pos : SV_POSITION;
fixed4 color : COLOR0;
};
v2f vert (appdata_full v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//可视化法线方向
o.color = fixed4(v.normal * 0.5 + fixed3(0.5, 0.5, 0.5),1.0);
//可视化切线方向
o.color = fixed4(v.tangent.xyz * 0.5 + fixed3(0.5, 0.5, 0.5),1.0);
//可视化副切线方向
fixed3 binormal = cross(v.normal, v.tangent.xyz) * v.tangent.w;
o.color = fixed4(binormal * 0.5 + fixed3(0.5, 0.5, 0.5), 1.0);
//可视化第一组纹理坐标
o.color = fixed4(v.texcoord.xy, 0.0, 1.0);
//可视化第二组纹理坐标
o.color = fixed4(v.texcoord1.xy, 0.0, 1.0);
//可视化第一组纹理坐标的小数部分
o.color = frac(v.texcoord);
if (any(saturate(v.texcoord) - v.texcoord))
{
o.color.b = 0.5;
}
o.color.a = 1.0;
//可视化第二组纹理坐标的小数部分
o.color = frac(v.texcoord1);
if (any(saturate(v.texcoord1) - v.texcoord1)) {
o.color.b = 0.5;
}
o.color.a = 1.0;
//可视化顶点颜色
//o.color = v.color;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
return i.color;
}
ENDCG
}
}
}
帧调试器
通过 window->analyze->Frame Debugger 即可启用帧调试器
可以用于查看渲染该帧时进行的各种渲染事件,
- 最上方的滑动条:重放渲染事件
- 左侧区域显示所有事件的树状图,可以从每个事件了解渲染的整个过程
- 右侧窗口体现事件的细节
内在功能其实是停止渲染查看各个渲染事件的结果,即运行到哪一步,就在哪一步停下来,供我们查看。
渲染平台差异
坐标差异
引起的一大原因是OpenGL与DirectX的屏幕空间出现不同,竖直方向的相反。
不过在平常情况下unity会为我们处理这种不同,只有在当我们打开了抗锯齿且进行了渲染到纹理的操作后,此时得到的图像不会被unity翻转,因此在采样这一环节就应该符合DirectX平台的规定。
UNITY_UV_STARTS_AT_TOP用于判断当前平台类型是否为DirectX类型
_MainTex_TexelSize.y是否小于0用来检验是否开了抗锯齿
语法差异
DirectX对Shader的语义会更加严格
语义差异
不同平台上有一些语义是不等价的
例如:SV_POSITION 和POSITION在一些平台上并不是等价的
书写Shader时保证简洁的一些建议
精度问题
float、half、fixed的精度在不同平台上的表现是不一样的,尽可能采用低精度,确保性能。
在移动平台上要尽可能注意到精度的问题,因为移动平台的优化技术是非常重要的
语法规范
注意初始化问题(对DirectX平台的适配问题
避免不必要的计算
要小心需要的临时寄存器数目或指令数目超过当前可支持的数目,不同的Shader Target、不同的着色器阶段可使用的临时寄存器和指令数目是不同的,可以通过指定更高级别的ShaderTarget来消除这些错误。
p118有各个Shader Target支持的指令数目
慎用分支和循环语句
它们会降低GPU的并行处理操作!!!
如果必须要用那么:
- 分支判断语句中使用的条件变量最好是常数,即在Shader运行过程中不会发生变化;
- 每个分支中包含的操作指令数尽可能少;
- 分支点嵌套层数尽可能少。
不要除以0
这会导致一些不可预测的后果,平台不同时就会导致微妙的变化