在上一篇入门手记中以一个简单的shader了解了一个shader的大致编写流程以及一些基本概念。这份手记是深入shader的结构分析每个“属性”的含义以及用法。
1.Shader:
code
Shader "Jhon/TestShader"
{
}
Dec
Shader的名字会直接决定shader在material里出现的路径。
2.SubShader
Code
Shader "Jhon/TestShader"
{
SubShader
{
//...
}
}
Dec
1.一个Shader至少要存在1个SubShader,没有上限。
2.SubShader = Shader的一个渲染方案。
3.SubShader是为了针对不同的渲染情况而编写的。
4.一个时刻只会选取一个SubShader进行渲染,第一个被选取的SubShader将会用于渲染,未被选取的
SubShader在这次渲染将被忽略。具体SubShader的选取规则包括:
•从上到下选取
•SubShader的标签、Pass的标签
•是否符合当前的“Unity渲染路径”
•是否符合当前的ReplacementTag
•SubShader是否和当前的GPU兼容
•等
5.当Unity3D选择1个subshader进行渲染的时候,将优先渲染1个被每个通道所定义的对象(这个对象
很可能是由光线交互决定的)。
这个操作的代价比较大,所以定义Shader可能操作的渲染通道数目应当尽可能的少。
3.SubShader的Tag
code
Shader "Jhon/TestShader"
{
SubShader
{
Tags { "Queue" = "Geometry + 10" "RenderType" = "Opaque" }
//...
}
}
Dec
SubShader内部可以有标签(Tags)的定义。
1.Tag指定了这个SubShader的渲染顺序(时机),以及其他的一些设置。
1."RenderType"标签:(可定义任意自己的RenderType这个标签所取的值。)
Unity可以运行时替换符合特定RenderType的所有Shader。
Camera.RenderWithShader或Camera.SetReplacementShader配合使用。
Unity内置的RenderType包括:
•"Opaque":绝大部分不透明的物体都使用这个;
•"Transparent":绝大部分透明的物体、包括粒子特效都使用这个;
•"Background":天空盒都使用这个;
•"Overlay":GUI、镜头光晕都使用这个;
其中Camera.RenderWithShader或Camera.SetReplacementShader不要求标签只能是RenderType,
RenderType只是Unity内部用于Replace的一个标签而已,可自定义自己全新的标签用于Replace。
举个栗子,我们编写的ShaderA.SubShaderA1(会被Unity选取到的SubShader,常为Shader文件中的
第一个SubShader)增加Tag为"Distort"="On",然后将"Distort"作为参数replacementTag传给函数。
此时,作为replacementShader实参的ShaderB.SubShaderB1中若有也有一模一样的"Distort"="On",
则此SubShaderB1将代替SubShaderA1用于本次渲染。
2."Queue"标签:定义渲染顺序。(可定义任意值,比如"Queue" = "Geometry + 10"。
项目中修改Queue 可解决特效在NGUI的层级问题,相关文章点我)
"ForceNoShadowCasting",值为"true"时,表示不接受阴影。
"IgnoreProjector",值为"true"时,表示不接受Projector组件的投影。
"Background":值为1000,最先渲染队列,常用于skyboxes等;
"Geometry":值为2000,默认渲染队列。常用于绝大多数对象。不透明几何体使用该队列;
(PS:
1.Geometry队列内部的物体的渲染顺序会有进一步的优化,从近到远绘制,
early-z AlphaTest可以剔除不需经过FS处理的图元。
2.默认情况下,Unity会基于对象距离摄像机的远近进行渲染。可以用这个队列控制对象的绘制顺序。
)
"AlphaTest":值为2450,透明通道检查的几何体使用该队列。
与Geometry队列不同是对于在所有立体物体绘制后渲染的通道检查的对象更有效;
"Transparent":值为3000,该渲染队列在Geometry和AlphaTest队列后被渲染。
任何通道混合的(不写入深度缓存的Shaders)对象使用该队列,例如玻璃和粒子效果;
"Overlay":值为4000,该渲染队列是为覆盖物效果服务的。任何最后被渲染的对象使用该队列,
例如镜头光晕;
3.关于渲染队列和Batch的经验总结是:
一帧的渲染队列的生成,依次取决于每个渲染物体的:
Shader的RenderType tag;
Renderer.SortingLayerID;
Renderer.SortingOrder;
Material.renderQueue(默认值为Shader里的"Queue");
Transform.z(ViewSpace)(默认为按z值从前到后,特殊的:Queue是“Transparent”的时候,
按z值从后到前)。
渲染队列决定了之后应该采用的是慵懒实例的机制,渲染器再依次遍历这个渲染队列,
“同一种”材质的渲染物体将合并进一个Batch。
4.Pass
code
Shader "Jhon/TestShader"
{
SubShader
{
Pass
{
//...
}
}
}
Dec
一个SubShader(渲染方案)是由若干个Pass块执行。每个Pass消耗一个DrawCall。
所以应当尽可能的降低Pass。
5.Pass的Tag
Code
Shader "Jhon/TestShader"
{
SubShader
{
Pass
{
Tags{ "LightMode" = "ForwardBase" }
//...
}
}
}
Dec
与SubShader相似Pass也有自己的Tag,其中最重要Tag是"LightMode",定义了光照管线中的pass的任务。
LightMode 的可选值:
1.Always:总是渲染,但不处理光照;
2.ForwardBase:用于正向渲染,环境主要方向灯和定点光/SH 等的应用(常用值);
3.ForwardAdd:用于正向渲染,附加的像素光被应用,每个光照一个pass(常用值);
4.PrepassBase:用于延迟光照,渲染法线/镜面指数;
5.PrepassFinal:用于延迟光照,通过结合纹理,光照和自发光渲染最终颜色;
6.Vertex:用于顶点光照渲染,当物体没有光照映射时,所有顶点光照被应用;
7.VertexLMRGBM:用于顶点光照渲染,当物体有光照映射的时候使用顶点光照渲染。
在平台上光照映射是RGBM 编码;
8.VertexLM:用于顶点光照渲染,当物体有光照映射的时候使用顶点光照渲染。
在平台上光照映射是double-LDR 编码(移动平台,及老式台式CPU);
9.ShadowCaster:将物体当做阴影产生者来渲染;
10.ShadowCollector:为了正向渲染对象的路径,将对象的阴影收集到屏幕空间缓冲区中。
6.FallBack
code
Shader "Jhon/TestShader"
{
SubShader
{
Pass{}
}
FallBack "Diffuse" /*"Diffuse"即Unity预制的固有Shader*/
// FallBack Off /*将关闭FallBack*/
}
Dec
当该Shader的所有SubShader都不支持当前显卡,就会使用FallBack语句指定的另一个Shader。
FallBack最好指定Unity自己预制的Shader实现,因其一般能够在当前所有显卡运行。
7.Properties
Code
Shader "ShaderLab Tutorials/TestShader"
{
Properties
{
_Range ("My Range", Range (0.02,0.15)) = 0.07 /*sliders*/
_Color ("My Color", Color) = (.34, .85, .92, 1) /*color*/
_2D ("My Texture 2D", 2D) = "" {} /*textures*/
_Rect("My Rectangle", Rect) = "name" { }
_Cube ("My Cubemap", Cube) = "name" { }
_Float ("My Float", Float) = 1
_Vector ("My Vector", Vector) = (1,2,3,4)
/*Display as a toggle.*/
[Toggle] _Invert ("Invert color?", Float) = 0
/*Blend mode values*/
[Enum(UnityEngine.Rendering.BlendMode)] _Blend ("Blend mode", Float) = 1
/*setup corresponding shader keywords*/
[KeywordEnum(Off, On)] _UseSpecular ("Use Specular", Float) = 0
}
SubShader
{
Pass
{
uniform float4 _Color;
float4 frag() : COLOR{ return fixed4(_Color); }
#pragma multi_compile __ _USESPECULAR_ON
}
}
SubShader
{
Pass
{
Color[_Color]
}
}
}
Dec
1.通过Properties来实现Shader在Unity编辑器暴露给美术的参数。
2.主要参数Float、Vector和Texture这3类。
3.除了通过编辑器编辑Properties,脚本也可以通过Material的接口(比如SetFloat、SetTexture编辑)
4.Shader程序通过[name](固定管线)或直接name(可编程Shader)访问这些属性。
5.在每一个Property前面也能类似C#那样添加Attribute,
以达到额外UI面板功能。(详见http://docs.unity3d.com/ScriptReference/MaterialPropertyDrawer.html)
8.Shader中的数据类型
基本参数类型
1.float:32位高精度浮点数;
2.half:16位中精度浮点数。范围是[-6万, +6万],精确到十进制的小数点后3.3位;
3.fixed:11位低精度浮点数。范围是[-2, 2],精度是1/256;
其他
1.half3:3个half组成;
2.float4x4:16个float组成;
注意事项
数据类型的精度要选择最小适用的。
(PS:
1.颜色和单位向量,使用fixed;
2.其他情况,尽量使用half(即范围在[-6万, +6万]内、精确到小数点后3.3位);否则才使用float;
)
9.ShaderLab中的Matrix
code
v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
/*1 、2、3是等价的,和4是不等价的
因为是M在左、V在右,所以是Column Vector
因为是HLSL/CG语言,所以是访问方式是Row-Major*/
/*1.*/ o.rootInView = mul(UNITY_MATRIX_MV, float4(0, 0, 0, 1));
/*2.*/ o.rootInView = float4(UNITY_MATRIX_MV[0].w, UNITY_MATRIX_MV[1].w, UNITY_MATRIX_MV[2].w, 1);
/*3.*/ o.rootInView = UNITY_MATRIX_MV._m03_m13_m23_m33;
/*4.*/ o.rootInView = UNITY_MATRIX_MV[3];
return o;
}
fixed4 frag (v2f i) : SV_Target
{
/*因为是ViewSpace是右手坐标系,所以当root在view前面的时候,z是负数,所以需要-z才能正确显示颜色*/
fixed4 col = fixed4(i.rootInView.x, i.rootInView.y, -i.rootInView.z, 1);
return col;
}
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 rootInView : TEXCOORD0;
float4 vertex : SV_POSITION;
};
Dec
当提到“Row-Major”、“Column-Major”,根据不同的场合,
它们可能指不同的意思:
数学上的:
主要是指矢量V是Row Vector、还是Column Vector。
引用自[Game Engine Architecture 2nd Edition, 183]。
留意到V和M的乘法,
当是Row Vector的时候,数学上写作VM,Matrix在右边,Matrix的最下面
一行表示Translate;
当是Column Vector的时候,数学上写作MtVt,Matrix在左边并且需要转置,
Matrix最右面一列表示Translate。
访问接口上的:
Row-Major即MyMatrix[Row][Column]、Column-Major
即MyMatrix[Column][Row]。HLSL/CG的访问接口都是Row-Major,
比如MyMatrix[3]返回的是第3行;
GLSL的访问接口是Column-Major,比如MyMatrix[3]返回的是第3列。
寄存器存储上的:
每个元素是按行存储在寄存器中、还是按列存储在寄存器中。
需要关注它的一般情况举例是:
float2x3的MyMatrix,到底是占用2个寄存器(Row-Major)、
还是3个寄存器(Column-Major)。
在HLSL里,可以通过#pragmapack_matrix设定row_major或者column_major。
上述情况,互不相干。
然后,ShaderLab中,数学上是Column Vector、访问接口上是Row-Major、
存储上是(尚未查明)。
另外,ShaderLab在World Space是左手坐标系、View Space是右手坐标系、
在Normalized Device Coordinates里是左手坐标系。