轉自:http://blog.csdn.net/poem_qianmo/article/details/55803629
這篇文章將基于MatCap的思想,在Unity中實現了具有高度真實感的MatCap車漆Shader。采用MatCap思想的Shader,用低廉的計算成本,就可以達到類似PBS非常真實的渲染效果,可謂是在移動平臺實現次時代渲染效果的一種優秀解決方案。
本文以車漆Shader為例,但MatCap思想能實現的,并不局限于車漆Shader。本來準備給本文取名《一種基于MatCap的低計算成本、高真實感移動平臺Shader解決方案》的,但這個名字太大了,遂改之。
先看一下最終的效果圖。
一、MatCap概述
Material Capture(材質捕獲),通常被簡稱為MatCap,在Zbrush、Sculptris、Mudbox等3D軟件中有比較多的使用。
MatCap Shader的基本思路是,使用某特定材質球的貼圖,作為當前材質的視圖空間環境貼圖(view-space environment map),來實現具有均勻表面著色的反射材質物體的顯示。考慮到物體的所有法線的投影的范圍在x(-1,1),y(-1,1),構成了一個圓形,所以MatCap 貼圖中存儲光照信息的區域是一個圓形。
基于MatCap思想的Shader,可以無需提供任何光照,只需提供一張或多張合適的MatCap貼圖作為光照結果的“指導”即可。
上圖來自(http://digitalrune.github.io/DigitalRune-Documentation/html/9a8c8b37-b996-477a-aeab-5d92714be3ca.htm)
不像一般的Shader,需要提供光照,需要在Shader代碼中進行漫長的演算,基于MatCap思想的Shader相當于MatCap貼圖就把光照結果應該是怎樣的標準答案告知Shader,我們只用在試卷下寫出答案,進行一些加工即可。
需要注意,MatCap Shader有一定的局限性。因為從某種意義上來說,基于MatCap的Shader,就是某種固定光照條件下,從某個特定方向,特定角度的光照表現結果。
正是因為是選擇的固定的MatCap貼圖,得到相對固定的整體光照表現,若單單僅使用MatCap,就僅適用于攝像機不調整角度的情形,并不適合攝像機會頻繁旋轉,調節角度的情形。但我們可以在某些Shader中,用MatCap配合與光照交互的其他屬性,如將MatCap結合一個作為光照反射的顏色指導的Reflection Cube Map,就有了與光照之間的交互表現。這樣,就可以適當彌補MatCap太過單一整體光照表現的短板。
關于MatCap,《UnityShaders and Effects Cookbook》一書的Chapter 5: LightingModels中,The Lit Sphere lighting model一節也有一些涉及。
二、MatCap貼圖的獲取
需要使用基于MatCap Shader,合適的MatCap 貼圖必不可少。顯而易見,MatCap貼圖的獲取,一般來說有兩種方式。
自己制作。對著3D軟件中的材質球截圖。
從網絡上獲取。在網絡上使用“matcap“等關鍵字搜索后獲得。
這邊提供幾個可以獲取MatCap貼圖的網址:
[1] https://www.pinterest.com/evayali/matcap/
[3]http://pixologic.com/zbrush/downloadcenter/library/#prettyPhoto
三、基于MatCap實現Physically Based Shading的思路簡析
關于基于MatCap思想實現Physicallybased Shading,這篇文章(http://blog.csdn.net/ndsc_dw/article/details/50700201)提供了一定的思路,簡單來說,就是用幾張MatCap貼圖來提供PBS需要的光滑度和金屬度,來模擬出PBS的效果。繼續展開下去就脫離本文的主線了,有興趣的朋友可以深入進行了解。
四、基于MatCap思想的車漆Shader實現
此車漆Shader,除了用到MatCap,主要還需要提供一個Reflection Cube Map作為反射的顏色指導,就可以適當彌補MatCap太過單一的整體光照表現的短板,實現非常真實且高效的車漆Shader效果。
此Shader賦給Material后,Material在Inspector中的屬性表現如下:
其中的MatCap貼圖為:
Shader源碼如下:
[cpp] view plain copy
Shader "ShaderPrac/Car Paint Shader"
{
Properties
{
//主顏色
_MainColor("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
//細節顏色
_DetailColor("Detail Color", Color) = (1.0, 1.0, 1.0, 1.0)
//細節紋理
_DetailTex("Detail Textrue", 2D) = "white" {}
//細節紋理深度偏移
_DetailTexDepthOffset("Detail Textrue Depth Offset", Float) = 1.0
//漫反射顏色
_DiffuseColor("Diffuse Color", Color) = (0.0, 0.0, 0.0, 0.0)
//漫反射紋理
_DiffuseTex("Diffuse Textrue", 2D) = "white" {}
//Material Capture紋理
_MatCap("MatCap", 2D) = "white" {}
//反射顏色
_ReflectionColor("Reflection Color", Color) = (0.2, 0.2, 0.2, 1.0)
//反射立方體貼圖
_ReflectionMap("Reflection Cube Map", Cube) = "" {}
//反射強度
_ReflectionStrength("Reflection Strength", Range(0.0, 1.0)) = 0.5
}
SubShader
{
Tags
{
"Queue" = "Geometry"
"RenderType" = "Opaque"
}
Pass
{
Blend Off
Cull Back
ZWrite On
CGPROGRAM
#include "UnityCG.cginc"
#pragma fragment frag
#pragma vertex vert
float4 _MainColor;
float4 _DetailColor;
sampler2D _DetailTex;
float4 _DetailTex_ST;
float _DetailTexDepthOffset;
float4 _DiffuseColor;
sampler2D _DiffuseTex;
float4 _DiffuseTex_ST;
sampler2D _MatCap;
float4 _ReflectionColor;
samplerCUBE _ReflectionMap;
float _ReflectionStrength;
//頂點輸入結構
struct VertexInput
{
float3 normal : NORMAL;
float4 position : POSITION;
float2 UVCoordsChannel1: TEXCOORD0;
};
//頂點輸出(片元輸入)結構
struct VertexToFragment
{
float3 detailUVCoordsAndDepth : TEXCOORD0;
float4 diffuseUVAndMatCapCoords : TEXCOORD1;
float4 position : SV_POSITION;
float3 worldSpaceReflectionVector : TEXCOORD2;
};
//------------------------------------------------------------
// 頂點著色器
//------------------------------------------------------------
VertexToFragment vert(VertexInput input)
{
VertexToFragment output;
//漫反射UV坐標準備:存儲于TEXCOORD1的前兩個坐標xy。
output.diffuseUVAndMatCapCoords.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DiffuseTex);
//MatCap坐標準備:將法線從模型空間轉換到觀察空間,存儲于TEXCOORD1的后兩個紋理坐標zw
output.diffuseUVAndMatCapCoords.z = dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w = dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));
//歸一化的法線值區間[-1,1]轉換到適用于紋理的區間[0,1]
output.diffuseUVAndMatCapCoords.zw = output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;
//坐標變換
output.position = mul(UNITY_MATRIX_MVP, input.position);
//細節紋理準備準備UV,存儲于TEXCOORD0的前兩個坐標xy
output.detailUVCoordsAndDepth.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DetailTex);
//深度信息準備,存儲于TEXCOORD0的第三個坐標z
output.detailUVCoordsAndDepth.z = output.position.z;
//世界空間位置
float3 worldSpacePosition = mul(unity_ObjectToWorld, input.position).xyz;
//世界空間法線
float3 worldSpaceNormal = normalize(mul((float3x3)unity_ObjectToWorld, input.normal));
//世界空間反射向量
output.worldSpaceReflectionVector = reflect(worldSpacePosition - _WorldSpaceCameraPos.xyz, worldSpaceNormal);
return output;
}
//------------------------------------------------------------
// 片元著色器
//------------------------------------------------------------
float4 frag(VertexToFragment input) : COLOR
{
//鏡面反射顏色
float3 reflectionColor = texCUBE(_ReflectionMap, input.worldSpaceReflectionVector).rgb * _ReflectionColor.rgb;
//漫反射顏色
float4 diffuseColor = tex2D(_DiffuseTex, input.diffuseUVAndMatCapCoords.xy) * _DiffuseColor;
//主顏色
float3 mainColor = lerp(lerp(_MainColor.rgb, diffuseColor.rgb, diffuseColor.a), reflectionColor, _ReflectionStrength);
//細節紋理
float3 detailMask = tex2D(_DetailTex, input.detailUVCoordsAndDepth.xy).rgb;
//細節顏色
float3 detailColor = lerp(_DetailColor.rgb, mainColor, detailMask);
//細節顏色和主顏色進行插值,成為新的主顏色
mainColor = lerp(detailColor, mainColor, saturate(input.detailUVCoordsAndDepth.z * _DetailTexDepthOffset));
//從提供的MatCap紋理中,提取出對應光照信息
float3 matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;
//最終顏色
float4 finalColor=float4(mainColor * matCapColor * 2.0, _MainColor.a);
return finalColor;
}
ENDCG
}
}
Fallback "VertexLit"
}
Shader注釋已經比較詳細,下面對代碼中也許會不太理解,需要注意的地方進行說明。
要使用MatCap貼圖,主要是將法線從模型空間轉換到視圖空間,并切換到適合提取紋理UV的區域[0,1]。(需要將法線從模型空間轉換到視圖空間,關于一些推導可以參考http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix或者http://www.cnblogs.com/flytrace/p/3379816.html)
Unity內置的矩陣UNITY_MATRIX_IT_MV,是UNITY_MATRIX_MV的逆轉置矩陣,其作用正是將法線從模型空間轉換到觀察空間。于是頂點著色器vert中的這兩句代碼就很容易理解了:
[cpp] view plain copy
//MatCap坐標準備:將法線從模型空間轉換到觀察空間,存儲于TEXCOORD1的后兩個紋理坐標zw
output.diffuseUVAndMatCapCoords.z =dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w= dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));
而得到的視圖空間的法線,區域是[-1,1],要轉換到提取紋理UV的區域[0,1],就需要乘以0.5并加上0.5,那么頂點著色器vert中接下來的的這句代碼也就可以理解了:
[cpp] view plain copy
//歸一化的法線值區間[-1,1]轉換到適用于紋理的區間[0,1]
output.diffuseUVAndMatCapCoords.zw= output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;
稍后,在片元著色器frag中,用在頂點著色器vert中準備好的法線轉換成的UV值,提取出MatCap的光照細節即可:
[cpp] view plain copy
//從提供的MatCap紋理中,提取出對應光照信息
float3matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;
此Car Paint Shader其他實現,主要就是一些基本的Shader知識點的配合,如頂點坐標轉換,反射,漫反射,細節紋理的計算,都是比較基礎的內容,這里就不再贅述,直接看Shader源碼即可。
最后看幾張截圖,然后就是相關Shader與MatCap資源的下載:
五、Shader源碼與MatCap資源下載
從Github下載:
參考與延伸
[1] http://wiki.unity3d.com/index.php/MatCap
[2] https://www.assetstore.unity3d.com/en/#!/content/8221
[3] http://blog.csdn.net/cubesky/article/details/38682975
[4] https://www.assetstore.unity3d.com/en/#!/content/76361
[5] http://www.cnblogs.com/flytrace/p/3379816.html
[6] http://www.cnblogs.com/flytrace/p/3395911.html
[8] https://forum.unity3d.com/threads/_object2world-or-unity_matrix_it_mv.112446/
[9]http://www.lighthouse3d.com/tutorials/glsl-12-tutorial/the-normal-matrix