UnityShader·笔记
shaderlab速查手册,趟坑记录以及等等等等٩(◦`꒳´◦)۶
Shader概况
Unity支持三种Shader,surface shader,fragment and vertex shader,以及fixed function shader。
Unity所用的shader语言为Cg/HLSL,相关函数可在Nvida查阅。
Shader代码结构
一个Shader文件大致长下面这样:
Shader "ShaderName"
{
Properties
{
//...
}
SubShader
{
Tags { }
CGPROGRAM
// Cg / HLSL code of the shader
// ...
ENDCG
}
}
-
SubShader:SubShader中包含的是实际上会被GPU执行的指令。一个shader文件中可以包含多个SubShader,一般是针对不同平台的多个版本的shader代码,U3D会挨个尝试执行它们,直到找到能用的那一个为止。
-
Properties:在Properties中声明需要调控的变量,这些变量会先是在U3D的面板上
-
Tags:用来告知引擎渲染顺序。不写也行,有默认模式。以下是比较常用的:
- Queue:决定物体渲染的顺序。比如先渲染透明物体还是先渲染不透明物体。
-
RenderType: 一个分类tag,需要结合方法
camera.SetReplacementShader(Shader, "RenderType")
使用。用来批量替换同RenderType物体的Shader。比如物体A的RenderType是awsl
,B的RenderType是keke
,用来替换的shader X中有RenderType是awsl
的SubShader,但是没有keke
的SubShader,那么这个替换方法执行后,A物体按照shader X渲染,物体B则不被渲染。
- Pass:一个SubShader中可以有多个Pass块,当某个SubShader被执行时,它的所有Pass都会被执行一遍。Pass中会包含一系列有关图形硬件的状态设置,常见的几种如下:
SubShader
{
Tags { }
Pass {
CGPROGRAM
// Cg / HLSL code of the shader
// ...
ENDCG
}
}
- Fallback:定义在SubShader之后,如果没有一个SubShader能被执行,U3D就会执行Fallback的函数。
Surf vs. Vert/Frag
两者的主要区别在于vert/frag shader没有物理语义,包括albedo,gloss,specular都不会在这个层面呈现。而surface shader的输出结构(SurfaceOutput
等)则包含了这些内容。
Surface shader可以指定光照模型,shader中通常会有一个叫surf的函数,在此计算输出颜色,再将其输入光照模型获得最后的结果。所有的Surface shader被编译时都会转化成vert/frag shader,即surface shader是Unity提供的一种简易shader写法。
Vertex/Fragment则没有内置光照模型的概念,更适合用在非现实渲染、2D图形和后期处理上。(当然也可以在里头写光照只是相对不大方便)
Surface Shader
声明surface function,将需要的数据输入shader并进行计算,最终打包以SurfaceOutput
格式输出。SurfaceOutput
会定义有关表面的各项属性,包括albedo、normal、emission、specularity等。
一个surf shader中必须包含的内容有二:
-
surfaceFunction:这个func必须包含一个
void surf(Input IN, inout SurfaceOutput o)
。 -
lightModel:可以使用built-in或自己写的光照模型。常用的内置模型有
Standard
、StandardSpecular
、Lambert
、BlinnPhong
。例如#pragma surface surf Lambert
声明了该shader是surf
shader,光照模型用的是Lambert。
其他常用到但不必须的:
- Custom modifier functions:自定义修饰函数,用来修改输入的顶点数据或最终输出的片段颜色。之前积雪那篇就用到了vert修改(vertex displacement)
- Shadows and Tessellation:给出控制shadow和tessellation的额外指令。Tessellation
- Code generation options:默认情况下surf shader会将所有的光照、阴影等场景信息纳入计算,但某些情况下可能可以跳过其中一些计算,比如不应用shadow、ambient、lightmap、fog等。这样可以加速shader的加载。
在void surf
中,输入变量之一的IN
是自定义的结构,通常会包含shader所需的变量信息。这个结构的中的变量命名是有要求的。
- 注意:贴图坐标的命名需要以
uv_
(或者uv2_
)开头,后接变量名,比如float2 uv_ModelTex : TEXCOORD0;
。一个模型的uv是可以有好几套的,冒号后的TEXCOORDn
中的n即意为第n套uv。
Vert/Frag Shader
TBC…
其他趟坑
- _MainTex_ST
如果要对贴图进行uv计算,则需要同时声明带有_ST
后缀的同名贴图变量。
- TRANSFORM_TEX
TRANSFORM_TEX主要作用是拿顶点的uv去和材质球的tiling和offset作运算, 确保材质球里的缩放和偏移设置是正确的。在Material中的Tiling对应xy,Offset对应zw,以下两种变换是等价的:
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
添加这个贴图和uv坐标间的变换后,inspector中的相关tiling、offset参数调整才会生效。
- unity_ColorSpaceDouble
//待续…
- HideInInspector
如其名,声明变量但不显示在inspector中。程序相关但不希望美术碰的变量都可以这样处理。
- 关于UV的又一个坑
看插件代码的时候经常碰到拿amplfied shader editor制作的远古shader,代码里经常出现一些奇怪的东西,比如properties声明里经常有类似以下隐藏变量
[HideInInspector] _texcoord2("", 2D) = "white" {}
[HideInInspector] _texcoord("", 2D) = "white" {}
上述这样不明变量。用途是为后续uv_变量计算使用,但并不是直接使用上述的贴图变量,而是图一个同名(shader里的又一同名规则)。
struct Input
{
float2 uv_texcoord;
float2 uv2_texcoord2;
};