Surface Shader
上一小结主要了解了Surface Shader使用了“#pragma surface surf Standard fullforwardshadows”指令的意义,这一小节主要了解“surf”surface函数。
void surf (Input IN, inout SurfaceOutputStandard o) { // Albedo comes from a texture tinted by color fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; }
surface函数总是无返回的,但在Unity当中要渲染物体,一个函数总是要输出最后的结果,这个"surf"函数没有返回结果,实际上它并没有以返回值的形式进行输出,它有两个参数:“Input IN”和“inout SurfaceOutputStandard o”。
第一个参数“Input IN”就是前面定义的“Input”结构体,在这个结构体中当前只有主纹理的uv坐标。关于这个纹理坐标我们可以查看一下Unity手册中的Writing Surface Shaders,在“Surface Shader input structure”中描述了除了使用“uv”作为开头的成员外,还有其他的成员,这些主要用于光照的计算,关于这些内容的使用,只有在系统地学习Cg语法、Vertex & Fragment Shader程序设计和计算机图形学的一些经典光照算法后,才能更灵活地使用这些功能,目前我们只要了解在“Input”结构体当中的纹理uv坐标值的功能。
第二个参数使用一个特性"inout",这个特性是Cg语言中比较重要的内容。如果在参数前没有任何修饰,那么默认是指输入的;用“out”修饰的,它的值在最后当做输出,并且在外部可以被直接使用。如果用“inout”,它描述的是这个参数既是输入的也是输出的。这个"surf"函数虽然没有返回值,但是它的第二个参数是有输出功能的。
第二个参数的类型是“SurfaceOutputStandard”,关于这个类型可以查看一下官方手册,有一个结构体为“SurfaceOutput”,是Unity5.0之前使用的,而在Unity5.0当中,“SurfaceOutput”具有不同的形态,它演变成两种结构体,分别是“SurfaceOutputStandard”和“SurfaceOutputStandardSpecular”,它们能被用于基于物理的光照模型,从而Unity就具有了PBS(基于物理着色)特性。关于基于物理着色,很多现代引擎都在追求和实现,而真正实现了基于物理着色的引擎才可以被称之为次世代引擎,因为它可以从很大程度上去真实地模拟光线跟踪。Unity使用了美国迪士尼公司所使用的一种称为Cook-Torrance的BRDF模型,再加上全局光照的计算,因此在Unity5当中我们可以把场景和物体渲染得比以前的版本更真实、更漂亮。
接下来简单了解一下“SurfaceOutput”和“SurfaceOutputStandard”两种结构体的对比。
struct SurfaceOutput { fixed3 Albedo; // diffuse color fixed3 Normal; // tangent space normal, if written fixed3 Emission; half Specular; // specular power in 0..1 range fixed Gloss; // specular intensity fixed Alpha; // alpha for transparencies };
struct SurfaceOutputStandard { fixed3 Albedo; // base (diffuse or specular) color fixed3 Normal; // tangent space normal, if written half3 Emission; half Metallic; // 0=non-metal, 1=metal half Smoothness; // 0=rough, 1=smooth half Occlusion; // occlusion (default 1) fixed Alpha; // alpha for transparencies };
两个结构体都有“Albedo”,但是在Unity5以后,“Albedo”指的是基本的漫反射或者高光的颜色值,而在早期版本中,“Albedo”就只是漫反射颜色值;另外,两者都有“Normal”、“Emission”和“Alpha”值,这些两者都是一样的;但是早期版本有“Specular”,新版已经把它涵盖在“Albedo”当中了;早期版本有“Gloss”,而新版已经用“Smoothness”取代了;新版本有“Metallic”和“Occlusion”,“Metallic”用来显示金属化程度,“Occlusion”是一种剔除特性,这些早期版本都是没有的。
为了将当前的Surface Shader精简化,我们可以把它改成旧版的Surface Shader。
Shader "Lesson/SurfaceShader2" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf Lambert fullforwardshadows // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 sampler2D _MainTex; struct Input { float2 uv_MainTex; }; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" }
其中删除了“_Color”、“_Glossiness”和“_Metallic”属性,将“#pragma surface surf Standard fullforwardshadows”改成“#pragma surface surf Lambert fullforwardshadows”。回到场景中,效果如图:
可以观察到原先球体的金属光泽已经没有,球体仅仅只有漫反射的感觉,这就是早期Surface Shader的效果。
在当前surface函数当中基本是一些赋值操作,“fixed4 c = tex2D (_MainTex, IN.uv_MainTex);”声明了一个颜色值,“tex2D”是Cg中的一个函数,它主要的功能就是从纹理中进行采样,使用了一个纹理坐标对纹理采样,得到了一个颜色值。然后将采样得到的颜色的rgb值交给了“SurfaceOutput”的“Albedo”值,并将得到颜色的alpha值交给了“SurfaceOutput”的“alpha”值。
在大概了解了surface函数的意义后,可以考虑将贴图调整成基于灰度来获取alpha通道,但是发现这个物体并没有透明效果,在Surface Shader当中,要达到透明效果,就不能使用先前使用过的alpha混合,启用一个blend选项来实现,因为Surface Shader没有pass通道,但是Unity官方指明了另外的方案,可以在Surface Shader文档的“Optional parameters”找到解决方案,如:
- alpha or alpha:auto
- alpha:blend
- alpha:fade
- alpha:premul
- alphatest:VariableName
- keepalpha
- decal:add
- decal:blend
其中,“alpha or alpha:auto”的功能是会自动选择使用“alpha:fade”或者“alpha:premul”的功能,如果使用的是简单光照函数等同于使用“alpha:fade”,如果使用基于物体的光照函数等同于使用“alpha:premul”左乘运算;“alphatest:VariableName”可以指定一个变量,我们可以用这个变量自身的第四个分量,比如说一个颜色值的alpha值就可以取代真正的"alphatest";“keepalpha”表示保持alpha值;“decal:add”和“decal:blend”都是用来描述印花的方案,其中“decal:add”实际上就是“blend”当中源和目标混合的one和one。
接下来可以先尝试使用“alpha”,将它放在“pragma”附加的指令部分:“#pragma surface surf Lambert fullforwardshadows alpha”。回到工程中,效果如图:
可以发现球体有了变化,似乎有透明的感觉,但是让人感觉很不自然,上半部分被削了一块,原因是这里没有指定它的渲染序列,需要在Tags当中添加一个渲染序列:“Tags { "RenderType"="Opaque" "queue"="transparent"}”,指定一个“transparent”,表示它是一个透明物体。
回到场景中,重新给物体选择一下shader,编译通过后可以发现球体已经透明了,效果如图:
对于一个透明物体,有时并不需要它产生阴影,把阴影选项“fullforwardshadows”删除,回到场景中发现阴影还存在,这是因为shader的“Fallback”里有一个“Diffuse”,如果把“Fallback”注释掉,可以发现场景里球体的阴影消失了。因此,可以发现“Fallback”有一个很好的用途,当Surface Shader编译后没有得到一个阴影投射器时,通过“Fallback”会自动在指定的具有阴影投射器通道的shader中(这里是“Diffuse”)去选择这个阴影投射器使用,这就是“Fallback”的一个妙用。
如果没有“Fallback”,又希望物体有阴影效果,可以在“pragma”中增加一个阴影投射通道“addshadow”:“#pragma surface surf Lambert alpha addshadow”。回到场景中,发现物体并没有投射阴影,原因是如果一个具有了alpha混合的半透明物体去投射一个阴影是不能得到最终需要的阴影效果的,因为有可能会有其他的物体光线投射并且透过这个物体落到这个物体的阴影范围内,让这个物体去投射一个实体的阴影不符合半透明物体的阴影形象。要产生阴影,需要删除"pragma"的“alpha”选项,取消“transparent”渲染序列,然后回到场景中,就可以到看到阴影效果了,不过这个球体就不透明了。代码以及效果如下:
Shader "Lesson/SurfaceShader2" { Properties { _MainTex ("Albedo (RGB)", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque"} LOD 200 CGPROGRAM #pragma surface surf Lambert addshadow // Use shader model 3.0 target, to get nicer looking lighting #pragma target 3.0 struct Input { float2 uv_MainTex; }; sampler2D _MainTex; void surf (Input IN, inout SurfaceOutput o) { fixed4 c = tex2D (_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } // FallBack "Diffuse" }