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
72Shader "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