本篇暂时记录一些零散的ShaderLab笔记
渲染流水线包括可配置部分和可编程部分,本篇只包括可配置部分。
如需了解ShaderLab数学部分,参见ShaderLab数学
可编程部分,也就是编辑vertex(顶点着色器)和fragment(片元做色器),参见ShaderLab函数
在本篇中,包括了渲染流水线的各个可配置部分,如果有不足的地方可指出,谢谢。

参考文献

《Shader入门精要》
UnityShaderLab学习总结
Unity手册
卡通头发高光
Unity手册

为什么要学ShaderLab呢?

图形化工具制作shader虽然能快速实现效果,但是难以优化。
需要开发人员/技术美术有能力对Shader进行功能分析、效率评估、选择、优化、兼容、甚至是Debug。
VS Code语法插件
ShaderLabFormatter
关闭右侧预览:CTRL+SHIF+P→User Setting→Text Editor-Minimap-Enabled
隐藏上方快捷栏:Window-Menu Bar Visibility-hidden
隐藏左侧快捷栏:右键空白处-Hide Active Bar
隐藏工程目录:Hide Side Bar
隐藏下方状态栏:Workbench-Appearance-Status Bar:Visible

渲染流水线解释


这里面包括很多个小阶段,有些是可控的,有些是可配置的。
如果是可控的,我们需要了解如何去操控它。如果是可配置的,就需要知道有哪些可选项。
应用阶段
在应用阶段,CPU通过向GPU提交Draw Call来发起一次渲染。
加载所有数据到内存→网格和纹理到显存→设置渲染状态-调用Draw Call
需要准备的场景中的数据:
摄像机、光源、模型
摄像机的具体数据有:摄像机位置,摄像机的视椎体
光源的具体数据有:
模型数据包括:顶点数据、渲染状态、模型Transform。
顶点数据:POSITION;NORMAL;TANGENT;TEXCOORD0~5;COLOR
渲染状态:材质的属性设置、纹理贴图、使用的shader变体(包含顶点着色器/片元着色器)、受影响的光源
模型需通过bounding box判断是否在视椎体内,进行简单剔除(culling)操作。
提交Draw Call:
CPU→GPU,要处理的内容为图元(primitive)列表。图元是个总称,描述复杂几何体的基础构成,1个顶点同时关联着多个线和面,可以用代码来进行定义。
渲染状态相同的图元可进行批处理。
几何阶段
顶点着色器-可编程
Vertex Shader:每个顶点执行一次,无法获得顶点之间的联系,每个顶点独立计算并保存数据。
模型空间→裁剪空间。
齐次去除由后续硬件负责,得到归一化的设备坐标(NDC,Normalized Device Coordinates)。
曲面细分着色器-可编程/可选
Tessellation Shader,细分图元。
参考百度百科Shader Model
Shader Model 4.0另一个重大变化就是在Vertex Shader和Pixel Shader之间引入了一个新的可编程图形层——几何渲染器(Geometry Shader)。
原来的Vertex Shader和Pixel Shader只是对逐个顶点或像素进行处理,而新的Geometry Shader可以批量进行几何处理。
然而其因性能原因而受到诟病,实际上使用它的人并不多。为了兼顾性能,DX11(SM 5.0)引入了Tessellation来取代GS的部分功能。
几何着色器-可编程/可选
Geometry Shader,可执行逐图元操作,或产生更多图元。
裁剪-可配置
Clipping。
完全在视野内的图元被保留,完全不在视野内的图元被忽略,部分可见的图元被裁剪。
在视野边界处使用新的顶点代替超出边界的顶点。
本过程发生在NDC中。
屏幕映射-无法控制
Screen Mapping,将图元坐标转换到屏幕空间。
屏幕空间:xy分量为像素坐标,左下角为(0,0);z为深度值。
屏幕映射后决定了每个顶点对应屏幕上的哪个像素,以及距离这个像素有多远。
NDC→屏幕空间。
光栅化阶段
本阶段发生在屏幕空间。光栅化阶段的主要目的是计算每个图元覆盖哪些像素,以及为这些像素计算他们的颜色。
三角形设置-无法控制
Triangle Setup,通过三角面片的三个顶点计算出三角形边界的表示方式,为下个阶段做准备。
三角形遍历-无法控制
Triangle Traversal,此阶段检查每个像素是否被一个三角面片覆盖,被覆盖的像素生成一个片元。

每个片元的信息由三个顶点所包含的数据进行差值得到,会被差值的数据包括屏幕空间坐标、深度信息、以及其他几何阶段输出的顶点信息。
注:会被差值的数据包括顶点着色器中输出的自定义数据。
片元着色器-可编程/可选
Fragment Shader,实现逐片元操作。
输入:三角形遍历阶段对顶点信息进行差值后得到的结果,也就是顶点着色器中输出的数据。
类似于片元着色器,每次操作仅可影响单个片元。
逐片元操作-可配置
Per-Fragment Operations,输出合并阶段。

像素所有权测试→裁剪测试→透明度测试→模板测试→深度测试→混合→色值抖动。
任务1:决定每个片元的可见性,也就是抛弃一些片元。
任务2:对通过了测试的片元的颜色值与已存储在颜色缓冲区的颜色值进行混合
Fragment+Associated Data:片元(一个待定的像素)在屏幕空间下的像素坐标、深度+顶点着色器传递的数据差值化后的结果。
Pixel Ownership Test:像素所有权测试,判断Framebuffer中当前位置的像素是否属于当前上下文。如果当前窗口被其他窗口遮挡住了,就不会通过测试。
Scissor Test:裁剪测试,超出矩形区域的范围不会通过测试。在使用多个视口时,每个视口都有自己独立的裁剪框。
Alpha Test:透明度测试,在SubShader的Queue标签为AlphaTest时,fragment函数中手动输入剔除条件代码。
使用此功能会让GPU禁用一些优化功能,导致性能变低。
clip (texColor.a - _Cutoff); //a通道值小于_Cutoff的片元都会被舍弃。
Stencil Test:模板测试,提供逐像素的遮罩,用于限制渲染的区域。
Depth Test:深度测试,深度缓冲区中记录了Framebuffer中当前位置的像素的深度值。如果当前片元被其他片元遮挡了,就不会通过测试。

Blending:片元通过了所有的测试,将对颜色缓冲区的值做修改。关闭混合时直接使用片元的颜色值,启用混合式根据混合操作设置计算最终颜色值。

Dithering:可用颜色较少时,去除色带;通常不能配置;在高分辨率下没有意义。
Logical Operation:逻辑与或操作,与Blending不能共存。
Framebuffer:A Framebuffer is a collection of buffers that can be used as the destination for rendering.
帧缓冲区是用于描述渲染过程中用到的缓冲区的集合,他们永远不会直接可见。
所以这里更新到Framebuffer,要更新的内容有:颜色值、深度值、模板值。
可以关闭不同颜色通道的写入。
GPU使用双重缓冲(Double Buffering),正在进行的渲染发生在后置缓冲,完成一帧渲染后交换后置缓冲区和前置缓冲区中的内容。

Properties

材质面板属性声明
在.shader文件中声明属性的意义是将属性暴露给材质面板。
所以即使不在Properties中声明,也可以通过代码为变量赋值。
Properties{
_Color ("Color",Color)=(1,1,1,1)
//变量名 标签 类型 初始值
_Vector("Vector",Vector)=(1,2,3,4)
_int("Int",int)=100
_float("Float",float)=3.14
_Range("Range",Range(1,10))=6
_2D("Texture",2D)="white"{} //2D纹理
_NormalMap("NormalMap",2D)="bump"{} //法线贴图
_2DArray("2DArray",2DArray)=""{} //2D图集
_3D("Texture",3D)="black"{}
_Cube("Cube",Cube)="_Skybox"{} //环境映射贴图
}

CGPROGRAM属性声明
SubShader{Pass{CGPROGRAM
float4 _Color;
float4 _Vector;
float _int;
float _float;
float _Range;
sampler2D _2D;
float4 _2D_ST; //xy分量为缩放 zw分量为平移
sampler2D _NormalMap;
float4 _NormalMap_ST; //xy分量为缩放 zw分量为平移
UNITY_DECLARE_TEX2DARRAY(_2DArray);
sampler3D _3D;
samplerCUBE _Cube;
ENDCG}}
浮点数精度
在测试阶段建议使用float,优化阶段可以尝试使用half。

SubShader的Tags

指定shader对应的物体被渲染顺序和这个subshader的其他参数,先渲染的物体会被后来渲染的物体遮挡
语法:Tags { "Queue" = "Geometry+1" "RenderType" = "Opaque" }
指定标签名和对应的值,SubShader Tags不能用于pass tags。
Queue,指明渲染顺序。
RenderType,着色器替换功能中的标签。
DisableBatching,指明本subshader不参与批处理。
ForceNoShadowCasting,不投射阴影。
IgnoreProjector,不被投影,通常用于半透明物体。
CanUseSpriteAtlas
PreviewType,指明材质预览使用的mesh。
Queue
指定该物体属于哪一个渲染顺序,默认有4个预定义的render queue,序列越小的先渲染。
1.Background/1000:最先渲染
2.Geometry(默认)/2000:适用于不透明物体。
3.AlphaTest/2450:低于指定alpha值的片元会被舍弃,造成了透明。
4.Transparent/3000:使用back-to-front顺序,可开启alpha-blended。
5.Overlay/4000:最后渲染,适用于覆盖效果。
注:Geometry+1相当于2001,可指定序列号用于控制渲染顺序
注:Geometry+500之前的序列是不透明队列,会通过优化渲染顺序提高性能。更高的序列是透明序列,先渲染最远的物体。
透明效果的实现
①实现步骤
SubShader部分:Queue标签值为2500以上,建议使用"Transparent"。
使用此队列后,同序列值的不透明SubShader,先渲染最远的物体。
这个优先级是判断在物体层面,而不是片元级别的判断,在物体交错时依然会有片元遮挡。
Pass部分:ZTest的默认值是LEqual;ZWrite的默认值为On;Cull默认值为Back;
Blend模式要主动设置,在设置的同时也会开启混合模式。
这里面会有多种排列组合,具体的设置可以参考Pass的Tags。
②关闭深度写入的效果分析
ZWrite Off Cull Off
Blend SrcAlpha OneMinusSrcAlpha
不再更新深度值,此时的深度值是不透明队列中的片元留下的记录。
所有同坐标的透明片元都会和这个不透明片元进行混合得到最终的颜色值。
玻璃有厚度,所以有2个面,在屏幕空间同一个坐标下会有2个片元待渲染,暂时不考虑其他透明物体的存在。
设靠近摄像机的片元颜色值为A(a,b,c,d),远离摄像机的片元颜色值为B(e,f,d,g),颜色缓冲为(f,i,j,1)。
先处理A时,最终颜色为:g(e,f,d,g)+(1-g)(d(a,b,c,d)+(1-d)(f,i,j,1))
结果的r通道值为:eg+ad-adg+f-df-fg+dfg
先处理B时,最终颜色为:d(a,b,c,d)+(1-d)(g(e,f,d,g)+(1-g)(f,i,j,1))
结果的r通道值为:ad+eg-deg+f-df-fg+dfg
两种处理顺序结果不一致,先处理B时相当于+adg后-edg,即增加B的影响同时减少A的影响。
结论:关闭深度写入难以得到完美的透明,深度测试也是必须的。
透明度混合时,未通过深度测试的片元不应被直接放弃,而是采用另一种公式弥补自己的贡献。
③双Pass处理双面的效果分析
在上例中,我们分析的玻璃有2个面,我们是可以控制正面和反面渲染顺序的。例如:
Pass1:Cull Front Zwrite Off Blend SrcAlpha OneMinusSrcAlpha
Pass2:Cull Back Zwrite Off Blend SrcAlpha OneMinusSrcAlpha
这样我们就可以使点B先于A点进行混合。
④更复杂的透明
假设有一个玻璃水杯,或者一个玻璃试管,此时在同一像素坐标下的透明片元会有4个。
假设有一个玻璃水缸里面盛满了蓝色水,斜着看水面的时候同一像素坐标下的透明片元会有5个。

RenderType
用于在运行时,通过代码控制场景中所有的已有材质中的shader的subshader(A),在符合:B中有相同Tag和对应值,且A中包含B所需的材质参数时,使用指定的shader中的subshader(B)。
public void RenderWithShader(Shader shader, string replacementTag);
Unity中有内置的RenderType值,也可以用自定义的标签和值。
LOD
Level of Detail,用于控制使用哪一个subshader,以控制细节级别。
约定中,在一个shader里可以写多个subshader,但是最终只会执行其中的一个。
这些subshader的LOD值越大,代表对应的subshader的性能越高。
在工程设置中定义LOD的最大值
Unity3D->Project Setting->QualitySettings中的Maximum LODLevel,0表示不进行判断,1表示LOD的最大值为100,2表示LOD的最大值为200,以此类推。此时大于设置值的subshader不会被编译。
脚本中设置全局的LOD的最大值:
Shader.globalMaximumLOD = 100
脚本中设置单个shader的LOD的最大值
_shader.maximumLOD
RenderState
Render state set-up,用于描述Pass的意图。
SubShader下的渲染状态设置适用于后续的所有Pass。
也可以在Pass之间插入渲染状态设置。
Cull
Cull Back | Front | Off
设置剔除模式:剔除背面(默认)/剔除正面/关闭剔除。
如果使用了Cull设置会覆盖掉其他设置;在向前渲染中可以使用多个ForwardBass Pass分别剔除前面和后面。
ZTest
ZTest (Less | Greater | LEqual(默认) | GEqual | Equal | NotEqual | Always)
深度值是物体在世界空间中距离摄像机的远近。距离越近,深度值越小;距离越远,深度值越大。
默认值是LEqual,当片元的深度值小于等于深度缓冲值时,通过深度测试。
主动设置为Always,意味着片元总能通过ZTest。
ZWrite
ZWrite On(默认) | Off
设置对depth buffer的读写模式。
默认值是On,通过了ZTest的片元将更新depth buffer中的深度值。
主动设置为Off时,通过了ZTest的片元将不会更新depth buffer中的深度值。
Blend
参考Unity关于Blending的说明
Blend Off 关闭混合(默认),直接使用片元的颜色值。
Blend SrcFactor DstFactor 最终颜色=SrcFactor*片元色值+DstFactor*颜色缓冲色值
Blend SrcFactor DstFactor, SrcFactorA DstFactorA 为RGB和Alpha通道使用不同的混合因子。
BlendOp XXX 混合操作,不指定时使用默认值Add。
BlendOp OpColor, OpAlpha,为RGB和Alpha通道使用不同BlendOp。
AlphaToMask On,Turns on alpha-to-coverage. When MSAA is used, alpha-to-coverage modifies multisample coverage mask proportionally to the pixel Shader result alpha value. This is typically used for less aliased outlines than regular alpha test; useful for vegetation and other alpha-tested Shaders.这个功能在大部分是全透明或不透明并且有很薄的“部分透明”区域的纹理上效果很好(grass, leaves and similar).
设置混合操作使用的公式,透明的片元的颜色与缓冲中已有的颜色进行计算得出半透明效果。

ShaderLab中的混合因子
One Zero
SrcColor OneMinusSrcColor
DstColor OneMinusDstColor
SrcAlpha OneMinusSrcAlpha
DstAlpha OneMinusDstAlpha
ShaderLab中的BlendOp
Add:默认值,不指定BlendOp时Blend操作的运算为:混合后的颜色相加。
Sub:指定Blend操作的运算为:混合后的源颜色-混合后的目标颜色。
RevSub:指定Blend操作的运算为:混合后的目标颜色-混合后的源颜色。
Min:指定Blend操作的运算为:逐分量取源颜色和目标颜色中的较小的值。
Max:指定Blend操作的运算为:逐分量取源颜色和目标颜色中的较大的值。
ColorMask RGB|A|0
参数表示该Pass将写入的颜色通道,默认值为RGBA,可以输入任意通道组合。
ColorMask 0 表示该Pass不写入任何颜色通道。
ColorMask RGB 3 表示在支持MRT的平台对RenderTarget #3输出RGB通道。
Offset
Offset OffsetFactor, OffsetUnits
Fallback
SubShader外使用的标签;Fallback "Standard"
可在指定的.shader文件中引用LightMode为ShadowCaster的Pass。

Stencil
Stencil { Ref [_StencilNo] Comp NotEqual Pass Keep Fail Keep }
Stencil { Ref [_StencilNo] Comp Always Pass Replace Fail Replace }

Pass的Tags

Pass(通道)是SubShader(子着色器)的组成部分,每个Pass代表一个完整的渲染流程。
Pass { [Name and Tags] [RenderSetup] }
UsePass
引用其他shader文件中的指定Name值的Pass。
UsePass "MMD4Mecanim/MMDLit/FORWARD"
值中包括shader路径和pass的Name值,Name值需全大写。
GrabPass
GrabPass{}
全屏截图,生成纹理sampler2D _GrabTexture传递给后面的Pass进行处理。
Name
Name "FORWARD"
定义Name值后可以被其他shader通过UsePass命令调用
LightMode
Tags { "LightMode"=" ForwardBase" }
如果没有指定LightMode,就无法获得光源参数,无法进行光照计算。
LightMode标签值为ForwardBase和ForwardAdd的Pass可以获得前向渲染路径的光照变量。
Always:默认值,不指定LightMode时使用此方案。总会被渲染,但不处理光照。
ForwardBase:计算环境光、最重要的逐像素光(必须是平行光、不能被设置为NOT important)、逐顶点/SH光源、Lightmaps。
有可能会存在多个ForwardBase的Pass,每个这样的Pass都执行一次。
ForwardAdd:计算额外的逐像素光源,每有一个像素光源就执行一次本Pass,额外像素光源数量可设置。
ShadowCaster:用于渲染产生阴影的物体,把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中。
注:ForwardAdd与ForwardBase的不同之处
①去掉了关于环境光、自发光的计算
②去掉了ForwardBase中计算点光源和SH光源的光照计算
③支持点光源、聚光灯的像素级别光照计算,可计算光衰减。
注:如何接受来自其他物体的阴影、向其他物体投射阴影
①如果要接受来自其他物体的阴影,就需要在Pass中对阴影映射纹理/屏幕空间的阴影图进行采样,得到的阴影值作为diffuse光照的一个系数。
也就是使用"阴影三剑客"三个宏,分别完成定义属性、顶点着色器内的阴影偏移,片元着色器内的采样。
②如果要向其他物体投射阴影,就需要在LightMode为ShadowCater的Pass中将物体加入到阴影映射纹理的计算中,如果使用了屏幕空间的投影映射(Screenspace Shadow Map)技术,这个Pass还会生成一张摄像机的深度图。
注:如何计算光照衰减
片元着色器中,将世界空间中的顶点转换到对应类型(Point/Spot)的光源空间,根据keyword判断应执行哪些采样代码。

RequireOptions
满足某些条件时才渲染该Pass,目前支持的选项:SoftVegetation。

CGPROGRAM

编译指令/声明函数

参见Unity手册
#pragma vertex name //将指定函数编译为顶点着色器

#pragma fragment name //将指定函数编译为片元做色器

#pragma geometry name //将指定函数便以为DX10 geometry shader,出现此选项时自动开启

#pragma target 4.0
指定shader编译目标级别,默认为2.5。
一些其他编译指令将自动提高target级别。编译目标级别越低适应性越好,2.0级别适用于全平台。

#pragma hull name

#pragma domain name

#pragma target name

#pragma require feature
#pragma require integers 2darray instancing

#pragma only_renderers space separated names

#pragma exclude_renderers space separated names

#pragma multi_compile
变体:定义shader code中的一片区域为不同的版本。通过不同的预定义宏(#ifdef xx +内容 + #endif 实现一个关键字)多次编译实现。
定义规则:#pragma multi_compile SPHEREMAP_OFF SPHEREMAP_MUL
效果说明:产生2个shader variants,一个定义了SPHEREMAP_OFF,一个定义了SPHEREMAP_MUL。
在运行时,多个关键词只有一个会被激活,对应代码中#ifdef~#endif区域内的代码生效。
①可以一次写2个以上的变体关键字,如:
#pragma multi_compile SPHEREMAP_OFF SPHEREMAP_MUL SPHEREMAP_ADD
②当变体关键字为全下划线时,产生一个没有关键词的空宏,效果不变,如:
#pragma multi_compile _ SPHEREMAP_MUL SPHEREMAP_ADD
默认激活规则:
①第一个被defined的关键词优先激活;
②当没有关键词被defined或者全部关键词都被defined时,使用第一个关键词。
③当第一个关键词是由全下划线定义的空宏,其默认undefined状态。
主动激活规则:
①在运行时,通过C#代码控制单个材质(Material.EnableKeyword/DisableKeyword)或全局设置关键字(Shader.EnableKeyword and DisableKeyword)。
②在材质Debug面板中,填写要激活的Shader Keywords。
观测方法:
在Frame Debugger中找到 Draw Mesh XXX,事件中注明了使用的Keywords。

#pragma shader_feature#pragma multi_compile的区别是:没有被用到的shader_feature变体在build时不会被包括进去。shader_feature主要在具体材质上设置关键字,multi_compile主要在代码全局设置。
#pragma shader_feature FANCY_STUFF 等效于 #pragma shader_feature _ FANCY_STUFF

shader_feature关联材质面板
MaterialPropertyDrawer

单选:
[Toggle] _Invert ("Invert color?", Float) = 0
材质面板出现一个Invert color?的单选项,_Invert对应的关键字为_INVERT_ON。
关键字使用示例,这里判断语句用if/ifdef/if defined皆可。

#if _INVERT_ON
col = 1 - col;
#endif

[Toggle(ENABLE_FANCY)] _Fancy ("Fancy?", Float) = 0
这里_Fancy对应的关键字被指定为ENABLE_FANCY。

[Space(50)]
装饰性语句。

多选浮点值

[Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend Mode", Float) = 1
[Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend Mode", Float) = 1
[Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 0
[Enum(UnityEngine.Rendering.CompareFunction)] _ZTest ("ZTest", Float) = 0
[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull Mode", Float) = 1

可指定enum类型名称,最好注明其namespace。可指定最多7个name/值对为一组enum,比如Off和0是一对。

关键字Enum
[KeywordEnum(None, Add, Multiply)] _Overlay ("Overlay mode", Float) = 0
为multi_compile提供的多项选,可切换关键字。需要定义关键字:
#pragma multi_compile _OVERLAY_NONE _OVERLAY_ADD _OVERLAY_MULTIPLY

非线性滑条
[PowerSlider(3.0)] _Shininess ("Shininess", Range (0.01, 1)) = 0.08
滑条居中时,对应的值低于一半。

关键词限制:Unity中只能定义256个关键字,内置的已经用掉了60多个,而关键字限制是整个项目所有shader共享的,所以不应过多的定义关键字。
内置的multi_compile关键字:通常用于处理不同渲染路径中的 light、shadow、lightmap类型。

multi_compile_fwdbase:编译所有ForwardBase pass需要的变体。这些变体处理不同的lightmap和主平行光(开启/关闭阴影)。
#pragma multi_compile_fwdbase nolightmap nodirlightmap nodynlightmap novertexlight
如果不需要任何lightmap,可以跳过这些变体

multi_compile_fwdadd:编译所有ForwardAdd pass需要的变体。这些变体处理直线、锥形、点像素光源,和cookie textures。

multi_compile_fwdadd_fullshadows:和上一个一样,但还能处理实时阴影。

multi_compile_fog:控制数个不同fog类型(off/linear/exp/exp2)。

手动跳过指定shader变体:如果你知道你不会用到一些变体,可以手动剔除他们。
#pragma skip_variants POINT POINT_COOKIE

Shader Hardware Variants:用于创建fallback或在单平台上同时适应于高端和低端机型。
#pragma hardware_tier_variants renderer
其中renderer为可用的渲染平台。将产生3个shader变体,分别带有UNITY_HARDWARE_TIER1/UNITY_HARDWARE_TIER1/UNITY_HARDWARE_TIER1关键字。可用于条件性fallback或针对不同终端的特性。使用Graphics Emulation可以测试不同tier的性能。
最终完全相同的的shader变体不会占用额外空间(不确定是否依然占用编译时间)。
Unity在加载时自带检测GPU并设置tier值,默认最高tier,可通过Shader.globalShaderHardwareTier设置,必须在shader被加载前设置,可在预加载场景设置。
硬件tier级别设置与Quality设置无关,只与GPU能力有关。
Platform Shader Setting:针对不同平台的不同tier级别,可以自主修改内置定义,比如你想强制在移动平台上使用联机阴影(cascaded shadowmaps 使近处的阴影更圆润)。参考PlatformShaderSettings来override一些能收支持特性的不同tier级别。参考USetShaderSettingsForPlatform来修改不同平台不同tier级别的设置。

优化Shader加载时间
官方博客剥离shader变体

Shader变体的打包与加载
打包时,输出到目标平台包中的shader变体会变成文件形式。运行时使用的shader会加载到内存。
会被打包的shader变体:
multi_compile中使用关键字定义的shader变体。
Graphics中设置的Always Included Shaders。
被使用了的shader_feature关键字定义的shader变体。
被使用了的Fog(雾效)或Lightmap的shader变体。
Build Assetbundle时,multi_compile定义和被使用到的shader变体。
运行时也可以编译shader为文件形式(解析)和加载到内存,可能造成卡顿。

#pragma enable_d3d11_debug_symbols

#pragma hardware_tier_variants renderer name

#pragma hlslcc_bytecode_disassembly

#pragma disable_fastmath

#pragma glsl_es2

其他命令从Unity5.0开始无效,如:
#pragma glsl, #pragma glsl_no_auto_normalization, #pragma profileoption, #pragma fragmentoption.

必须是在主shader文件中的编译指令(#pragma)才能生效
不包括include文件中的编译指令,所以.cginc文件中不应包括#pragma内容。

include包含指令

常见指令

#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "HLSLSupport.cginc"
#include "AutoLight.cginc"
#include "Lighting.cginc"

include部分在CGPROGRAM/ENDCG范围内,位置随意。因为从上至下编译,越底层的文件应放在上面。
包含后可以使用文件内提供的预定义宏(空宏、变量宏、代码块,作用类似于无脑复制)、变量、结构体、函数。
shader编译时,遇到了没有声明的变量时会立即报错,需要使用的变量应提前声明。
当主shader文件与.cginc文件中出现了已定义的同名函数时会报错。
shader主动在从上到下的“已知范围内”寻找指定的代码,遇到依赖时更换寻找目标。
当主shader中使用了用不到的函数式,会自动无视。

# if defined (XXX) || defined (XXX)
define XXX
endif
在.cginc文件中经常出现这样的语法。判断已读区域中是否已定义括号里的内容,如已定义则向已阅读区域增加内容。

shader编译顺序举例:

Pass {
    CGPROGRAM
    #include "UnityCG.cginc"
    #include "test.cginc"
    #pragma vertex vert
    #pragma fragment frag
    ENDCG
    }

shader先阅读UnityCG.cginc后再阅读test.cginc。test.cginc中包含自定义的代码。
如果先阅读test.cginc,就会出现报错,因为声明的结构体中包含未知的定义。
读到#pragma vertex vert时:程序主动在“已知范围”内寻找vert函数,这里的“已知范围”包括2个.cginc文件内容。当在"已知范围"内没有找到需要的函数时,才在#pragma vertex vert下方继续寻找。
只有主动寻找的代码块会参与编译,最终代码只与#pragma声明有关。
使用预定义宏的意义在于代码的复用。

贴图设置

表面颜色由镜面反射+漫反射决定
albedo:反照率,可用于绘制灰度图,物体吸收光的比率越小,反照率越高,物体越亮。
diffuse:漫反射系数,提供红色、绿色、蓝色分量的比例。
实际在不同工作流中,两者很可能是一样的。

纹理类型

默认为Texture。
base color:reflected diffuse color for dielectrics + reflectance for raw metal
roughness:粗糙度贴图
Metallic:金属贴图,灰度图,非0即1,白色区域表示金属。
diffuse:diffuse color
glossiness:平滑度贴图
specular:高光贴图
ambient occlusion:环境光遮蔽,AO
normal:法线贴图,使用了a和g通道,
height:高度贴图
cubemap:环境映射
RampTex:渐变纹理
MaskTex:遮罩纹理→高光遮罩

lightmap:光照贴图,用于预计算场景中物体表面的brightness,存储到图表或“light map”备用。
Unity使用Progressive Lightmapper系统烘焙场景中的lightmap,综合mesh、material、texture、light。光照贴图是渲染的一部分,GameObject在有lightmap时,会自动使用它。相关设置可参看Global Illumination
光照烘焙准备:
mesh需在Import settings中开启Generate Lightmap UVs选项。
在Window-Rendering-Lighting Settings-Scene-Lightmapping Settings-Lightmap Resolution指定光照贴图分辨率。
Scene界面的Baked Lightmap模式中,1个格子表示一个像素。
需要光照烘焙的GameObject应被设置为Lightmap Static,可用Scale In Lightmap属性单独设置光照贴图分辨率。
Light Explorer界面可全局管理场景中的光照设置。
生成光照贴图:Lightting Settings-Scene-Generate Lighting按钮,或者勾选自动生成。
查看已烘焙好的光照贴图:Scene界面旁边的2个页面。

Alpha Source

指定透明通道的值如何生成。None/Input Texture Alpha/From Gray Scale

Wrap Mode

指定纹理坐标超过[0,1]后的平铺模式。
Repeat:舍弃整数部分
Clamp:取边界值

Filter Mode

滤波模式,影响纹理在放大或缩小(倾斜)时得到的图片效果
Point模式:放大或缩小时,采样一个像素
Bilinear模式:放大或缩小时,对目标像素和其临近的4个像素进行差值,有模糊效果。
Trilnear模式:和Bilinear类似,额外在多级渐远纹理之间进行混合。

Advanced - Generate Mip Maps

多级渐远纹理技术,消耗更多的内存空间,生成多级渐远纹理,提高实时运行时采样效率。

纹理的最大尺寸与纹理模式

纹理的长宽应为2的幂。
纹理分辨率超过Max Texture Size时,会被Unity缩放至这个最大分辨率。


关注成长,注重因果。