转载请注明出处:http://blog.csdn.net/candycat1992/article/details/39994049
一直以来,Unity Surface Shader背后的机制一直是初学者为之困惑的地方。Unity Surface Shader在Unity 3.0的时候被开放给公众使用,其宣传手段也是号称让所有人都可以轻松地写shader。但由于资料缺乏,很多人知其然不知其所以然,无法理解Unity Surface Shader在背后为我们做了哪些事情。
前几天一直被问到一个问题,为什么我的场景里没有灯光,但物体不是全黑的呢?为什么我把Light的颜色调成黑色,物体还是有一些默认颜色呢?这些问题其实都是因为那些物体使用了Surface Shader的缘故。因此,了解Surface Shader背后的机制是非常重要滴~
虽然Surface Shader一直是一个神秘的存在,但其实Unity给了我们揭开她面纱的方式:查看它生成的CG代码。大家应该都知道,所谓的Surface Shader实际上是封装了CG语言,隐藏了很多光照处理的细节,它的设计初衷是为了让用户仅仅使用一些指令(#pragma)就可以完成很多事情,并且封装了很多常用的光照模型和函数,例如Lambert、Blinn-Phong等。而查看Surface Shader生成的代码也很简单:在每个编译完成的Surface Shader的面板上,都有个“Show generated code”的按钮,像下面这样:
点开后,就可以查看啦~面板上还表明了很多其他的有用信息。而这些方便的功能实际上是Unity 4.5发布出来的。详情可见这篇博文。
使用Surface Shader,很多时候,我们只需要告诉shader,“嘿,使用这些纹理去填充颜色,法线贴图去填充法线,使用Lambert光照模型,其他的不要来烦我!!!”我们不需要考虑是使用forward还是deferred rendering,有多少光源类型、怎样处理这些类型,每个pass需要处理多少个光源!!!(人们总会rant写一个shader是多么的麻烦。。。)So!Unity说,不要急,放着我来~
上面的情景当然对于小白是比较简单的方式,Surface Shader可以让初学者快速实现很多常见的shader,例如漫反射、高光反射、法线贴图等,这些常见的效果也都不错。而对应面就是,由于隐藏了很多细节,如果想要自定义一些比较复杂或特殊的效果,使用Surface Shader就无法达到了(或者非常麻烦)。在学了一段时间的Surface Shader后,我认为:
struct SurfaceOutput {
half3 Albedo;
half3 Normal;
half3 Emission;
half Specular;
half Gloss;
half Alpha;
};
c.rgb += o.Emission;
float spec = pow (nh, s.Specular*128.0) * s.Gloss;
c.a = o.Alpha;
#pragma surface surfaceFunction lightModel [optionalparams]
void surf (Input IN, inout SurfaceOutput o)
Shader "Custom/BasicDiffuse" {
Properties {
_EmissiveColor ("Emissive Color", Color) = (1,1,1,1)
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
_MySliderValue ("This is a Slider", Range(0,10)) = 2.5
_RampTex ("Ramp Texture", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf BasicDiffuse vertex:vert finalcolor:final noforwardadd
#pragma debug
float4 _EmissiveColor;
float4 _AmbientColor;
float _MySliderValue;
sampler2D _RampTex;
struct Input
{
float2 uv_RampTex;
float4 vertColor;
};
void vert(inout appdata_full v, out Input o)
{
o.vertColor = v.color;
}
void surf (Input IN, inout SurfaceOutput o)
{
float4 c;
c = pow((_EmissiveColor + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb + tex2D(_RampTex, IN.uv_RampTex).rgb;
o.Alpha = c.a;
}
inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
float difLight = max(0, dot (s.Normal, lightDir));
float hLambert = difLight * 0.5 + 0.5;
float3 ramp = tex2D(_RampTex, float2(hLambert)).rgb;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * (ramp) * atten;
col.a = s.Alpha;
return col;
}
void final(Input IN, SurfaceOutput o, inout fixed4 color) {
color = color * 0.5 + 0.5;
}
ENDCG
}
FallBack "Diffuse"
}
Shader "Custom/BasicDiffuse_Gen" {
Properties {
_EmissiveColor ("Emissive Color", Color) = (1,1,1,1)
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
_MySliderValue ("This is a Slider", Range(0,10)) = 2.5
_RampTex ("Ramp Texture", 2D) = "white"{}
}
SubShader {
Tags { "RenderType"="Opaque" "RenderType"="Opaque" }
LOD 200
// ------------------------------------------------------------
// Surface shader code generated out of a CGPROGRAM block:
// ---- forward rendering base pass:
Pass {
Name "FORWARD"
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
// compile directives
#pragma vertex vert_surf
#pragma fragment frag_surf
#pragma multi_compile_fwdbase nodirlightmap
#include "HLSLSupport.cginc"
#include "UnityShaderVariables.cginc"
#define UNITY_PASS_FORWARDBASE
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
#define INTERNAL_DATA
#define WorldReflectionVector(data,normal) data.worldRefl
#define WorldNormalVector(data,normal) normal
// Original surface shader snippet:
#line 11 ""
#ifdef DUMMY_PREPROCESSOR_TO_WORK_AROUND_HLSL_COMPILER_LINE_HANDLING
#endif
//#pragma surface surf BasicDiffuse vertex:vert finalcolor:final noforwardadd
#pragma debug
float4 _EmissiveColor;
float4 _AmbientColor;
float _MySliderValue;
sampler2D _RampTex;
struct Input
{
float2 uv_RampTex;
float4 vertColor;
};
void vert(inout appdata_full v, out Input o)
{
o.vertColor = v.color;
}
void surf (Input IN, inout SurfaceOutput o)
{
float4 c;
c = pow((_EmissiveColor + _AmbientColor), _MySliderValue);
o.Albedo = c.rgb + tex2D(_RampTex, IN.uv_RampTex).rgb;
o.Alpha = c.a;
}
inline float4 LightingBasicDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
float difLight = max(0, dot (s.Normal, lightDir));
float hLambert = difLight * 0.5 + 0.5;
float3 ramp = tex2D(_RampTex, float2(hLambert)).rgb;
float4 col;
col.rgb = s.Albedo * _LightColor0.rgb * (ramp);
col.a = s.Alpha;
return col;
}
void final(Input IN, SurfaceOutput o, inout fixed4 color) {
color = color * 0.5 + 0.5;
}
// vertex-to-fragment interpolation data
#ifdef LIGHTMAP_OFF
struct v2f_surf {
float4 pos : SV_POSITION;
float2 pack0 : TEXCOORD0;
float4 cust_vertColor : TEXCOORD1;
fixed3 normal : TEXCOORD2;
fixed3 vlight : TEXCOORD3;
// LIGHTING_COORDS在AutoLight.cginc里定义
// 本质上就是一个#define指令
// e.g.
// #define LIGHTING_COORDS(idx1,idx2) float3 _LightCoord : TEXCOORD##idx1; SHADOW_COORDS(idx2)
// #define SHADOW_COORDS(idx1) float3 _ShadowCoord : TEXCOORD##idx1;
LIGHTING_COORDS(4,5)
};
#endif
#ifndef LIGHTMAP_OFF
struct v2f_surf {
float4 pos : SV_POSITION;
float2 pack0 : TEXCOORD0;
float4 cust_vertColor : TEXCOORD1;
float2 lmap : TEXCOORD2;
LIGHTING_COORDS(3,4)
};
#endif
#ifndef LIGHTMAP_OFF
float4 unity_LightmapST;
#endif
// 定义所需的纹理坐标
float4 _RampTex_ST;
// vertex shader
v2f_surf vert_surf (appdata_full v) {
v2f_surf o;
// 使用自定义的vert函数填充Input结构
Input customInputData;
vert (v, customInputData);
// 再赋值给真正所需的v2f_surf结构
o.cust_vertColor = customInputData.vertColor;
o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
// 将顶点的纹理坐标转换到纹理对应坐标
o.pack0.xy = TRANSFORM_TEX(v.texcoord, _RampTex);
#ifndef LIGHTMAP_OFF
// 如果启用了LightMap,则计算对应的LightMap坐标
o.lmap.xy = v.texcoord1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
#endif
// 计算世界坐标系中法线的方向
// SCALED_NORMAL在UnityCG.cginc里定义
// 本质上就是一个#define指令
// #define SCALED_NORMAL (v.normal * unity_Scale.w)
float3 worldN = mul((float3x3)_Object2World, SCALED_NORMAL);
// 如果没有开启LightMap,
// 顶点法线方向就是worldN
#ifdef LIGHTMAP_OFF
o.normal = worldN;
#endif
// SH/ambient and vertex lights
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// vertex lights就是球面调和函数的结果
// 球面调和函数ShadeSH9在UnityCG.cginc里定义
float3 shlight = ShadeSH9 (float4(worldN,1.0));
o.vlight = shlight;
// unity_4LightPosX0等变量在UnityShaderVariables.cginc里定义
#ifdef VERTEXLIGHT_ON
float3 worldPos = mul(_Object2World, v.vertex).xyz;
o.vlight += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, worldPos, worldN );
#endif // VERTEXLIGHT_ON
#endif // LIGHTMAP_OFF
// pass lighting information to pixel shader
// TRANSFER_VERTEX_TO_FRAGMENT在AutoLight.cginc里定义,
// 本质上就是一个#define指令
// 用于转换v2f_surf中的_LightCoord和_ShadowCoord
TRANSFER_VERTEX_TO_FRAGMENT(o);
return o;
}
#ifndef LIGHTMAP_OFF
sampler2D unity_Lightmap;
#ifndef DIRLIGHTMAP_OFF
sampler2D unity_LightmapInd;
#endif
#endif
// fragment shader
fixed4 frag_surf (v2f_surf IN) : SV_Target {
// prepare and unpack data
#ifdef UNITY_COMPILER_HLSL
Input surfIN = (Input)0;
#else
Input surfIN;
#endif
// 使用v2f_surf中的变量给Input中的纹理坐标进行赋值
surfIN.uv_RampTex = IN.pack0.xy;
surfIN.vertColor = IN.cust_vertColor;
#ifdef UNITY_COMPILER_HLSL
SurfaceOutput o = (SurfaceOutput)0;
#else
SurfaceOutput o;
#endif
// 初始化SurfaceOutput结构
o.Albedo = 0.0;
o.Emission = 0.0;
o.Specular = 0.0;
o.Alpha = 0.0;
o.Gloss = 0.0;
#ifdef LIGHTMAP_OFF
o.Normal = IN.normal;
#endif
// call surface function
// 调用自定义的surf函数填充SurfaceOutput结构
surf (surfIN, o);
// compute lighting & shadowing factor
// LIGHT_ATTENUATION在AutoLight.cginc里定义,
// 本质上就是一个#define指令
// 用于计算光衰减
fixed atten = LIGHT_ATTENUATION(IN);
fixed4 c = 0;
// realtime lighting: call lighting function
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// 调用自定义的LightXXX函数,
// 使用填充好的SurfaceOutput等变量作为参数,
// 得到初始的像素值
c = LightingBasicDiffuse (o, _WorldSpaceLightPos0.xyz, atten);
#endif // LIGHTMAP_OFF || DIRLIGHTMAP_OFF
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// 向像素叠加vertex light的光照颜色
c.rgb += o.Albedo * IN.vlight;
#endif // LIGHTMAP_OFF
// lightmaps:
#ifndef LIGHTMAP_OFF
// 计算LightMap,这部分不懂
#ifndef DIRLIGHTMAP_OFF
// directional lightmaps
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
fixed4 lmIndTex = tex2D(unity_LightmapInd, IN.lmap.xy);
half3 lm = LightingLambert_DirLightmap(o, lmtex, lmIndTex, 0).rgb;
#else // !DIRLIGHTMAP_OFF
// single lightmap
fixed4 lmtex = tex2D(unity_Lightmap, IN.lmap.xy);
fixed3 lm = DecodeLightmap (lmtex);
#endif // !DIRLIGHTMAP_OFF
// combine lightmaps with realtime shadows
#ifdef SHADOWS_SCREEN
#if (defined(SHADER_API_GLES) || defined(SHADER_API_GLES3)) && defined(SHADER_API_MOBILE)
c.rgb += o.Albedo * min(lm, atten*2);
#else
c.rgb += o.Albedo * max(min(lm,(atten*2)*lmtex.rgb), lm*atten);
#endif
#else // SHADOWS_SCREEN
c.rgb += o.Albedo * lm;
#endif // SHADOWS_SCREEN
// 给Alpha通道赋值
c.a = o.Alpha;
#endif // LIGHTMAP_OFF
// 调用自定义的final函数,
// 对像素值进行最后的更改
final (surfIN, o, c);
return c;
}
ENDCG
}
// ---- end of surface shader generated code
#LINE 57
}
FallBack "Diffuse"
}
#ifdef LIGHTMAP_OFF
// 如果没有开启LightMap,
// 向像素叠加vertex light的光照颜色
c.rgb += o.Albedo * IN.vlight;
#endif // LIGHTMAP_OFF
// 如果没有开启LightMap,
// vertex lights就是球面调和函数的结果
// 球面调和函数ShadeSH9在UnityCG.cginc里定义
float3 shlight = ShadeSH9 (float4(worldN,1.0));
o.vlight = shlight;
// normal should be normalized, w=1.0
half3 ShadeSH9 (half4 normal)
{
half3 x1, x2, x3;
// Linear + constant polynomial terms
x1.r = dot(unity_SHAr,normal);
x1.g = dot(unity_SHAg,normal);
x1.b = dot(unity_SHAb,normal);
// 4 of the quadratic polynomials
half4 vB = normal.xyzz * normal.yzzx;
x2.r = dot(unity_SHBr,vB);
x2.g = dot(unity_SHBg,vB);
x2.b = dot(unity_SHBb,vB);
// Final quadratic polynomial
float vC = normal.x*normal.x - normal.y*normal.y;
x3 = unity_SHC.rgb * vC;
return x1 + x2 + x3;
}