title: unity-shader-MatCap模拟pbr效果
categories: Unity3d
tags: [unity, shader, matcap]
date: 2018-08-15 12:57:28
comments: false
unity-shader-MatCap模拟pbr效果, 用廉价的计算获取比较真实的类似pbr的效果. 不需要用到真实的光.
把之前零零散散的知识的整理一下
前篇
有图有真相
直接替换一个贴图, 就可以看到效果
贴图必须是方形的 2的n次幂 大小, 且中间的圆形最好与四个边相切, 可以超出一点点, 但不能覆盖不到. 下面会解释为什么要这样做.
实现原理
其原理实现也很简单, 就是归一化的 法线 从模型空间变换到 观察空间 ( 也叫视空间 ), 只取 xy 坐标来处理,
如下图, 箭头方向代表 摄像机 的观察方向, 这个球代表了归一化的法线, 然后去球面上任意一点的三维向量, 丢弃z分量的话就, 就等价于投影到一个平面, 也只能投影到圆形区域.
平面上的圆形区域也就是需要采样的区域, 但由于贴图是uv是 0~1 范围的, 所以需要将 视空间 的 法向量 由 -1~1 映射上去, uv = (viewNormal.xy + 1 ) / 2
相关shader代码
-
使用顶点的法线
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 #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #include "UnityCG.cginc" 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插件的写法
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 #pragma vertex vert #pragma fragment frag #pragma fragmentoption ARB_precision_hint_fastest #pragma shader_feature MATCAP_ACCURATE #pragma multi_compile_fog #include "UnityCG.cginc" struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float2 uv_bump : TEXCOORD1; #if MATCAP_ACCURATE fixed3 tSpace0 : TEXCOORD2; fixed3 tSpace1 : TEXCOORD3; fixed3 tSpace2 : TEXCOORD4; UNITY_FOG_COORDS(5) #else float3 c0 : TEXCOORD2; float3 c1 : TEXCOORD3; UNITY_FOG_COORDS(4) #endif }; 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); #if MATCAP_ACCURATE //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); #else //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)); #endif 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 #if MATCAP_ACCURATE //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; #else half2 capCoord = half2(dot(i.c0, normals), dot(i.c1, normals)); float4 mc = tex2D(_MatCap, capCoord*0.5+0.5) * tex * 2.0; #endif 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