前言
MatCap渲染技术有些美术还不太清楚是什么,这篇文章分四部分,第一节介绍MatCap是什么;第二节是MatCap在Unity的实现方式;第三节主要优化MatCap现有的缺陷的一个缺陷。现在把我实现的方式记录下来,希望能帮助到更多的人。
一、MatCap是什么?
Matcap Shader是一种在某些层面能替代甚至超越PBR的次时代渲染方案。
Matcap的原理并不复杂,就是使用一张中间为球面的图片作为不同法线方向的光照颜色, 然后将这些颜色根据模型的法线信息渲染到相应位置。
- MatCap优势 :
它的效率极高、计算成本极低,显示效果极佳,能完美运行于不同的移动平台。
Matcap拥有PBR不具备的优势,充满了很多新的可能性,只需简单更换Matcap贴图,就能实现各种神奇的渲染效果 - MatCap缺陷 :
♦MatCap的缺陷有很多,如:
♦只适用于单一材质;
♦没有漫反射、高光反射、菲涅尔、金属度、光滑度等参数;
♦无法响应光源与相机位置的变化;
♦在平面、圆柱体及相近形体的渲染上效果并不好等。
二、MatCapShader的实现方式
通俗的说,MatCap贴图就是一个球体的渲染图,MatCap贴图根据需求包括了漫反射,反射,高光等信息。如下图:
那么为什么这张图能够映射到模型上呢?主要还是因为一个球面已经包括了观察空间的所有法线方向了,我们只要把模型的法线方向映射到该贴图上就可以得到想要的效果了。
我们可以通过UNITY_MATRIX_V 把世界空间中的法线转为观察空间法线
//把世界空间法线转到观察空间法线
float2 viewSpaceNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);
由于法线的范围是-1到1的,我们还需要把观察空间的法线重映射到0-1
//viewSpaceNormal = viewSpaceNormal * 0.5 + 0.5;//重映射法线
//为了允许MatCap贴图有些误差,采样为如下:
viewSpaceNormal = viewSpaceNormal * 0.495 + 0.5;
最终就可以拿这个作为UV对MatCap贴图进行采样了。
完整的代码如下:
Shader "LST/BaseMatCap"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color("color",color)=(1,1,1,1)
[NoScaleOffset]_MatCapTex ("MatCapTex", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
float2 matCapUV : TEXCOORD1;//MatCap 贴图采样的UV
};
uniform sampler2D _MainTex;
uniform float4 _MainTex_ST;
uniform float4 _Color;
uniform sampler2D _MatCapTex;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
/*
下面这部分是MatCap的重要部分,法线的计算可以在片元着色器上计算(如果有法线贴图的话就一定要在片元着色器上计算了)
现在由于没有法线贴图,可以放在顶点着色器上计算,效率会高很多
*/
//把模型空间的法线转为世界空间
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
//把世界空间法线转到观察空间法线
float2 viewSpaceNormal = mul((float3x3)UNITY_MATRIX_V, worldNormal);
//把观察空间法线从(-1,1)重映射到(0,1)---- viewSpaceNormal = viewSpaceNormal * 0.5 + 0.5;
//为了允许MatCap贴图有些误差,采样为如下:
viewSpaceNormal = viewSpaceNormal * 0.495 + 0.5;
o.matCapUV = viewSpaceNormal;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv) * _Color;
//采样MatCap贴图
fixed4 matCapColor = tex2D(_MatCapTex, i.matCapUV);
col *= matCapColor;
return col;
}
ENDCG
}
}
}
最终的效果如下:
可见,MatCap可以花最快的效率达到最好的效果,当然它还有很多缺陷,美术可以根据自己的需求选择。
UnityAssetStore里有一个免费的包关于MatCap的,大家可以下载参考一下
https://assetstore.unity.com/packages/vfx/shaders/free-matcap-shaders-8221
三、基于现在的MatCapShader优化
在第一节上有说过MatCap的缺陷,其中一点是在平面上的采样效果不好,见下图
主要问题是一个平面上的法线一样,导致采样的位置一样
对于这个问题,CSDN或知乎也有人提出解决方案,其中有一篇文章写得比较好的https://zhuanlan.zhihu.com/p/40109002
但是由于这篇文章并没有给出源码,我根据这篇文章实现了一遍,发现在某些角度还是会出现拉伸的情况。
然后我又找了其他的渲染引擎(ThreeJs,一个开源的wengl渲染引擎),得到了以下的更好的解决方法,方法如下:
对于平面,法线的方向是统一的,导致采样一样,我们可以尝试通过视角方向代替法线方向,即摄像机到渲染点的方向,只要计算了这个方向,再通过这个方向进行采样即可,为什么呢?请看下图:
我们需要在观察空间中计算
先计算观察空间中,点到摄像机的方向
//观察空间中,点到摄像机的方向
viewPos = -mul(unity_MatrixMV,v.vertex);
再通过viewPos 与Y轴叉乘,模拟法线方向求切线
//viewPos 与y轴的叉乘,模拟法线方向求切线
float3 vTangent = normalize( cross(-viewPos,float3(0,1,0)));
通过viewPos模拟法线方向求副切线
//使用观察方向模拟法线方向求副切线
float3 vBinormal = cross( viewPos, vTangent );
最终把观察空间的法线和切线、副切线点乘,就可以求到MatCap贴图的UV了
float2 matCapUV = float2( dot( vTangent , viewNormal ), dot( vBinormal , viewNormal ) ) * 0.495 + 0.5;
最终的实现效果如下(左边的Cube是优化前的,右边的Cube是优化后的):
现在贴上整个工程的下载地址,里面包含优化后的源码:
UnityShader MatCapShader的实现及优化(一种低消耗“次世代”的渲染技术)