【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型

时间:2024-04-03 11:07:50

【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型

转载请注明出处:http://www.cnblogs.com/-867259206/p/5664792.html

  1. 如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以。

  2. 水平有限,难免有谬误之处,望指出。


LitSphere(Matcap)

发光球体光照模型就是将发光球体的纹理映射在球体上,来实现光照效果。这可以创造一些效果细腻的发光球体效果,但是它不受光照影响,改变光照的方向,球体的光照效果不变。如果要在固定视角的场景里制作细腻的球体光照,这会是一个不错的选择。
准备小球纹理贴图:
【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型
【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型
【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型

  1. 定义Properties:

    Properties {
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
}
  1. 编写预编译命令:

        #pragma surface surf Unlit vertex:vert
#pragma target 3.0
  1. 在subshader里关联properties:

        float4 _MainTint;
sampler2D _MainTex;
sampler2D _NormalMap;
  1. 定义光照函数Unlit:

        inline half4 LightingUnlit (SurfaceOutput s, fixed3 lightDir, fixed atten)
{
half4 c = half4(1,1,1,1);
c.rgb = s.Albedo;
c.a = s.Alpha;
return c;
}

这是一个无光照的光照函数,因为我们要用纹理上的光照效果,所以不需要计算光照。

  1. 定义Input结构体:

        struct Input {
float2 uv_MainTex;
float2 uv_NormalMap;
float3 tan1;
float3 tan2;
};
  1. 定义顶点函数:

        void vert (inout appdata_full v, out Input o)
{
UNITY_INITIALIZE_OUTPUT(Input,o); TANGENT_SPACE_ROTATION;
o.tan1 = mul(rotation, UNITY_MATRIX_IT_MV[0].xyz);
o.tan2 = mul(rotation, UNITY_MATRIX_IT_MV[1].xyz);
}

解释:
NITY_INITIALIZE_OUTPUT(Input,o);在HLSLSupport.cginc文件里是这样定义的:

#if defined(UNITY_COMPILER_HLSL) || defined(SHADER_API_PSSL) || defined(SHADER_API_GLES3) || defined(SHADER_API_GLCORE)
#define UNITY_INITIALIZE_OUTPUT(type,name) name = (type)0;
#else
#define UNITY_INITIALIZE_OUTPUT(type,name)
#endif

如果是HLSL编译器或某些版本的Shader API,则将变量赋值为0,否则什么都不做。这样编译器就不会报错说变量未初始化。
TANGENT_SPACE_ROTATION;是Unity提供的一个宏,它定义了一个rotation矩阵用于从Object Space变换到Tangent Space。它在UnityCG.cginc里的定义如下:

// Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal ));

binormal是切空间的Y轴,tangent是切空间的X轴,normal是切空间的Z轴。为什么它定义了一个切空间旋转看这篇
UNITY_MATRIX_IT_MV是Object Space(or Model Space)变换到View Space(or Eye Space)的矩阵的逆转置矩阵。View Space以摄像机为原点,摄像机朝向为Z轴。[]是取出矩阵的一列,UNITY_MATRIX_IT_MV[0].xyz是说将向量(1,0,0)(即X轴)左乘MV矩阵的逆转置矩阵。UNITY_MATRIX_IT_MV[1].xyz则是向量(0,1,0)(即Y轴)左乘MV矩阵的逆转置矩阵。
顺便科普一下,OpenGL是列向量,变换矩阵应该左乘向量。如果要将法线从Object Space变换到View Space是不能用MV矩阵的,原因看这篇博文。要将法线从Object Space变换到View Space要用MV矩阵的逆转置矩阵。那么,反过来,如果要将法线从View Space变换到Object Space只要将MV矩阵的逆转置矩阵右乘法线即可。这里的代码可以理解为将View Space的X轴和Y轴变换到了Object Space。变换到了Object Space以后再乘rotation变换到Tangent Space。

  1. 最后,编写表面处理函数:

        void surf (Input IN, inout SurfaceOutput o)
{
float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
o.Normal = normals; float2 litSphereUV;
litSphereUV.x = dot(IN.tan1, o.Normal);
litSphereUV.y = dot(IN.tan2, o.Normal); half4 c = tex2D (_MainTex, litSphereUV*0.5+0.5);
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}

这部分需要的解释不多。在表面处理函数里将法线和tan1、tan2点乘,相当于是把法线投影到了View Space的X轴和Y轴上。后面就是将法线当作UV来采样。乘0.5加0.5是把区间变到[0,1]。


LitSphere有点像把我们能看见的小球的那一面当作一层皮扒下来,然后平铺在纹理上,使纹理上的球严丝合缝地投影在了小球上。效果如下:

【Unity Shaders】学习笔记——SurfaceShader(十一)光照模型
LitSphere

如果我们用纹理本身的UV坐标的话,小球图片是矩形,四周还有黑边,那么采样到小球上会是一个变形的四周有黑边的圆球图案。相当于是把画了个球的纹理贴在球身上。而用法线作UV的话,就把纹理上有球的那部分映射到了我们看得见的球的表面。
我觉得把这理解为把球的表面投影到纹理上更好理解一点。这样要把法线从切空间变换到视空间,每个顶点都要变换,计算量太大,所以换过来,转换到切空间计算会cheap一点。
这个光照模型也叫Matcap(Material Capture)。
这个光照模型只能用于比较圆的物体,比如Sphere和Capsule,Cube是不能用的,因为Cube能看见的法线就只有三个,所以没办法使用这个光照模型。
另一个版本的Matcap