unity-shader-MatCap模拟pbr效果
unity-shader-MatCap模拟pbr效果, 用廉价的计算获取比较真实的类似pbr的效果. 不需要用到真实的光.
把之前零零散散的知识的整理一下
前篇
有图有真相

直接替换一个贴图, 就可以看到效果
贴图必须是方形的 2的n次幂 大小, 且中间的圆形最好与四个边相切, 可以超出一点点, 但不能覆盖不到. 下面会解释为什么要这样做.
其他资料
- 插件 Outlined MatCap Shader (Toon Shader).unitypackage
- 演示 http://www.jeanmoreno.com/unity/matcap/
- matcap 贴图制作工具 http://www.zbrushcentral.com/showthread.php?92157-MaCrea-Material-Creation-Tool
实现原理
其原理实现也很简单, 就是归一化的 法线 从模型空间变换到 观察空间 ( 也叫视空间 ), 只取 xy 坐标来处理,
如下图, 箭头方向代表 摄像机 的观察方向, 这个球代表了归一化的法线, 然后去球面上任意一点的三维向量, 丢弃z分量的话就, 就等价于投影到一个平面, 也只能投影到圆形区域.
平面上的圆形区域也就是需要采样的区域, 但由于贴图是uv是 0~1 范围的, 所以需要将 视空间 的 法向量 由 -1~1 映射上去, uv = (viewNormal.xy + 1 ) / 2

相关shader代码
- 使用顶点的法线 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72- Shader "ITS/test/Bumped_Textured_Multiply2" 
 {
 Properties
 {
 _MainTex ("Base (RGB)", 2D) = "white" {}
 _MatCap_0 ("MatCap (RGB)", 2D) = "white" {}
 }
 
 Subshader
 {
 Tags { "RenderType"="Opaque" }
 
 Pass
 {
 Tags { "LightMode" = "Always" }
 
 CGPROGRAM
 
 
 
 
 struct v2f
 {
 float4 pos : SV_POSITION;
 float4 uv : TEXCOORD0;
 };
 
 uniform float4 _MainTex_ST;
 
 v2f vert (appdata_base v)
 {
 v2f o;
 o.pos = UnityObjectToClipPos (v.vertex);
 o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
 
 // 变换 法线 从 模型空间 -> 观察空间
 // 方式一
 // float3 worldNorm = UnityObjectToWorldNormal(v.normal).xyz;
 // float3 viewNormal = mul((float3x3)UNITY_MATRIX_V, worldNorm); // 将法线转到观察空间下, 因为matcap贴图是摄像机看到的贴图
 // o.uv.zw = viewNormal.xy; // 转换法线值为贴图值
 // 方式二
 float3 viewNormal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal.xyz);
 o.uv.zw = viewNormal.xy;
 // 方式三
 // o.uv.z = mul(UNITY_MATRIX_IT_MV[0], v.normal);
 // o.uv.w = mul(UNITY_MATRIX_IT_MV[1], v.normal);
 return o;
 }
 
 uniform sampler2D _MainTex;
 uniform sampler2D _MatCap_0;
 
 fixed4 frag (v2f i) : COLOR
 {
 fixed4 tex = tex2D(_MainTex, i.uv.xy);
 tex.rgba = tex.rgba * _Color;
 float2 mUv = i.uv.zw * 0.5 + 0.5; // 转换法线值为贴图值用来采样
 // fixed4 mc = tex2D(_MatCap_0, mUv);
 fixed4 mc = tex2D(_MatCap_0, mUv) * tex; // 这里使用叠加的方式必然会使原图更加暗色, 可以点参数来提高亮度
 return mc;
 }
 ENDCG
 }
 }
 
 Fallback "VertexLit"
 }
- 使用法线贴图, 这个是参考unity的matcap插件的写法 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 Shader "ITS/test/Bumped_Textured_Multiply"
 {
 Properties
 {
 _MainTex ("Base (RGB)", 2D) = "white" {}
 _BumpMap ("Normal Map", 2D) = "bump" {}
 _MatCap ("MatCap (RGB)", 2D) = "white" {}
 [Toggle(MATCAP_ACCURATE)] _MatCapAccurate ("Accurate Calculation", Int) = 0
 }
 
 Subshader
 {
 Tags { "RenderType"="Opaque" }
 
 Pass
 {
 Tags { "LightMode" = "Always" }
 
 CGPROGRAM
 
 
 
 
 
 
 
 struct v2f
 {
 float4 pos : SV_POSITION;
 float2 uv : TEXCOORD0;
 float2 uv_bump : TEXCOORD1;
 
 
 fixed3 tSpace0 : TEXCOORD2;
 fixed3 tSpace1 : TEXCOORD3;
 fixed3 tSpace2 : TEXCOORD4;
 UNITY_FOG_COORDS(5)
 
 float3 c0 : TEXCOORD2;
 float3 c1 : TEXCOORD3;
 UNITY_FOG_COORDS(4)
 
 };
 
 uniform float4 _MainTex_ST;
 uniform float4 _BumpMap_ST;
 
 v2f vert (appdata_tan v)
 {
 v2f o;
 o.pos = UnityObjectToClipPos (v.vertex);
 o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
 o.uv_bump = TRANSFORM_TEX(v.texcoord, _BumpMap);
 
 
 //Accurate bump calculation: calculate tangent space matrix and pass it to fragment shader
 fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
 fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
 fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; // 根据 法线和切线 计算出 副切线, 组成 切线空间 的坐标系
 o.tSpace0 = fixed3(worldTangent.x, worldBinormal.x, worldNormal.x);
 o.tSpace1 = fixed3(worldTangent.y, worldBinormal.y, worldNormal.y);
 o.tSpace2 = fixed3(worldTangent.z, worldBinormal.z, worldNormal.z);
 
 //Faster but less accurate method (especially on non-uniform scaling)
 v.normal = normalize(v.normal);
 v.tangent = normalize(v.tangent);
 TANGENT_SPACE_ROTATION;
 o.c0 = mul(rotation, normalize(UNITY_MATRIX_IT_MV[0].xyz));
 o.c1 = mul(rotation, normalize(UNITY_MATRIX_IT_MV[1].xyz));
 
 UNITY_TRANSFER_FOG(o, o.pos);
 return o;
 }
 
 uniform sampler2D _MainTex;
 uniform sampler2D _BumpMap;
 uniform sampler2D _MatCap;
 
 fixed4 frag (v2f i) : COLOR
 {
 fixed4 tex = tex2D(_MainTex, i.uv);
 fixed3 normals = UnpackNormal(tex2D(_BumpMap, i.uv_bump)); // 采样 法线贴图 并把 贴图值0~1转成 法线值-1~1
 
 
 //Rotate normals from tangent space to world space
 float3 worldNorm;
 worldNorm.x = dot(i.tSpace0.xyz, normals);
 worldNorm.y = dot(i.tSpace1.xyz, normals);
 worldNorm.z = dot(i.tSpace2.xyz, normals);
 worldNorm = mul((float3x3)UNITY_MATRIX_V, worldNorm);
 float4 mc = tex2D(_MatCap, worldNorm.xy * 0.5 + 0.5) * tex * 2.0;
 
 half2 capCoord = half2(dot(i.c0, normals), dot(i.c1, normals));
 float4 mc = tex2D(_MatCap, capCoord*0.5+0.5) * tex * 2.0;
 
 
 UNITY_APPLY_FOG(i.fogCoord, mc);
 return mc;
 }
 ENDCG
 }
 }
 
 Fallback "VertexLit"
 }
优缺点
- 优点 : 计算效率高, 不用使用到光
- 缺点 : 无法响应光源与相机位置的变化,Matcap采样贴图是静态的,通过相机空间映射的
matcap贴图制作
简单粗暴不正统
在 substance painter 有很多素材可以拿来处理一下就可以用了

如果没有合适的, 就自己调出一个来, 用max建个球形模型, 丢到 painter 中慢慢调出来.
简单粗暴直接截图, 然后在 ps 中处理, 做好一个之后, 用来做 剪贴蒙版, 后续的处理那这个 蒙版 蒙住, 缩放一下就可以了.
下面 金黄色的图层就是做好的用来当做 剪贴蒙版.

相关参考
- Matcap Shader 详解【1】-基础思想与U3D实现 - https://zhuanlan.zhihu.com/p/37702186