Skip to content

Instantly share code, notes, and snippets.

@stilllisisi
Created March 11, 2020 03:30
Show Gist options
  • Save stilllisisi/91142011acf282402a115f98d811b40a to your computer and use it in GitHub Desktop.
Save stilllisisi/91142011acf282402a115f98d811b40a to your computer and use it in GitHub Desktop.
【游戏-unity】Unity Shader-Matcap(材质捕获)使用解析
//1. Matcap的实现(提前了解Matcap的原理)
//我们使用了UNITY_MATRIX_IT_MV,即正常矩阵的逆转置进行计算,原因在于直接用ModelView矩阵变换法线时,对于非uniform变换可能导致法线与平面不垂直的问题。
//我们使用一张Matcap,在Matcap中,我们最终采样的是一个法线方向变换到(0,1)区间的结果,实际上有效的区域就只是贴图中心周围的圆形范围内有效。
//而Matcap比较直白的效果就是,在当前视角方向看,Matcap的上下左右方向的颜色就对应模型在当前视角方向对应的法线所指向的上下左右方向。
//shader代码如下:
/********************************************************************
FileName: Matcap.shader
Description: Matcap效果
history: 4:11:2018 by puppet_master
*********************************************************************/
Shader "Unlit/Matcap"
{
Properties
{
_MatCap ("Matcap", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 matcapuv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MatCap;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//乘以逆转置矩阵将normal变换到视空间
float3 viewnormal = mul(UNITY_MATRIX_IT_MV, v.normal);
//需要normalize一下,否则保证normal处在(-1,1)区间,否则有scale的object效果不对
viewnormal = normalize(viewnormal);
o.matcapuv = viewnormal.xy * 0.5 + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 mat = tex2D(_MatCap, i.matcapuv);
return mat;
}
ENDCG
}
}
}
//对于一个球体,球体上显示的,实际上就是Matcap上对应的内容,不论我们从哪个视角看,球体上都是这样的一个表现。Matcap左上角的高光两点,对应到模型上左上角的部分也会有高光亮点。
//另外,还有一点需要注意,在我们把Normal变换到视空间后,我们进行了一次Normalize操作。网上看到了绝大部分的版本的Matcap的uv计算实现是这样的:
o.matcapuv.x = mul(UNITY_MATRIX_IT_MV[0], v.normal);
o.matcapuv.y = mul(UNITY_MATRIX_IT_MV[1], v.normal);
//可能是出于性能的考虑,直接使用UNITY_MATRIX_IT_MV的x和y轴作为基,直接求Normal在xy轴的投影,虽然可以减少矩阵的计算,但是这样有一个比较严重的问题。
//如果对象的Scale是1,那么效果没有问题,但是如果对象的Scale非1,那么得到的法线并非在(-1,1)区间,转化之后的uv值肯定也跑偏了,结果自然就不对了
//2. Matcap优化
//在比较圆润的对象上,我们可以看到较好的效果,但是在平面、正方体上,我们看不到任何变化,整个平面都是平的;
//传统的Matcap整体的渲染效果与视角关系不大,有区别的情况仅在于平面的法线分布不同导致表面效果不同,这导致了Matcap在视角方面有一些限制,仅对于固定的相机视角较好。
//从上面我们采样matcap的操作可以了解这种情况的原因,当遇到一个平面时,这个平面上的所有的法线方向都是相同的,转化到视空间后,方向也是相同的,再*0.5+0.5之后的uv值也是相同的,最终导致一个平面上所有的像素点采样matcap得到是值都是相同的。
//缓解这种情况,就不仅仅考虑视方向的问题,还需要考虑一下位置的问题,我们可以把相机的位置也加入计算,物体相对于相机的方向也作为matcap采样的影响因子之一。
//我们可以在计算方向的时候,不直接使用Normal计算,而是根据当前像素点的相机空间位置,相机空间法线,计算一个反射的方向,再用这个反射的方向进行matcap采样即可:
float3 viewnormal = mul(UNITY_MATRIX_IT_MV, v.normal);
float3 viewPos = UnityObjectToViewPos(v.vertex);
float3 r = reflect(viewPos, viewnormal);
r = normalize(r);
o.matcapuv = r.xy * 0.5 + 0.5;
//还有一种方式,也是使用了反射方向进行计算,但是不是直接用反射方向的xy,而是通过一个公式将xy按照一个权重进行调制,得到的效果更好。
//优化后的Shader如下:
/********************************************************************
FileName: Matcap.shader
Description: Matcap效果
history: 5:11:2018 by puppet_master
*********************************************************************/
Shader "Unlit/MatcapReflect"
{
Properties
{
_MatCap ("Matcap", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct v2f
{
float2 matcapuv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MatCap;
sampler2D _GlobalMatcap;
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
//乘以逆转置矩阵将normal变换到视空间
float3 viewnormal = mul(UNITY_MATRIX_IT_MV, v.normal);
viewnormal = normalize(viewnormal);
float3 viewPos = UnityObjectToViewPos(v.vertex);
float3 r = reflect(viewPos, viewnormal);
float m = 2.0 * sqrt(r.x * r.x + r.y * r.y + (r.z + 1) * (r.z + 1));
o.matcapuv = r.xy / m + 0.5;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 mat = tex2D(_MatCap, i.matcapuv);
return mat;
}
ENDCG
}
}
}
//3. 动态生成Matcap
//Matcap目前主要还是用于实现一些好玩的特殊效果,但是对于动态光照等效果,Matcap着实是做不到的。
//毕竟Matcap本身就是一种预计算好的特殊光照效果贴图,要想随着场景的光进行动态变化,第一方法,仅把Matcap作为一个输入的参数,额外按照光照计算一个权重来调制Matcap效果;
//另一种直接渲染一个球体,作为动态的Matcap,这个球体可以使用很复杂的计算Shader,然后场景里面其他的对象采样Matcap。这种方式可能会节省一些光照的计算,但是没有具体测试过。
//下面,我们实现一下动态生成一张Matcap进行一个最简单的diffuse计算效果,首先我们使用一个简单的Shader如下:
Shader "Unlit/SimpleLight"
{
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#define UNITY_PASS_FORWARDBASE
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : NORMAL;
};
v2f vert (appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 lightDir = _WorldSpaceLightPos0.xyz;
float3 normal = normalize(i.worldNormal);
float ndotl = saturate(dot(lightDir, normal));
return fixed4(ndotl,ndotl,ndotl,1);
}
ENDCG
}
}
}
//然后我们用CommandBuffer在相机前面绘制一个Sphere到RT上作为动态的Matcap:
var cam = GetComponent<Camera>();
matcap = RenderTexture.GetTemporary(512, 512, 24, RenderTextureFormat.Default, RenderTextureReadWrite.Default, 4);
var commandbuffer = new CommandBuffer();
commandbuffer.ClearRenderTarget(true, true, Color.black);
commandbuffer.SetRenderTarget(matcap);
commandbuffer.DrawRenderer(sphereRenderer, sphereRenderer.sharedMaterial);
cam.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, commandbuffer);
Shader.SetGlobalTexture("_GlobalMatcap", matcap);
//我们在场景中放置两个模型,左侧为Matcap效果,右侧为SimpleLight效果,上面的Sphere显示动态的Matcap,可见Matcap效果与SimpleLight效果类似;
//通过这样的一个方式,如果计算方式很复杂的光照计算,我们就可以通过Matcap进行预计算一次,然后其他所有对象采样Matcap来达到动态的效果。
@stilllisisi
Copy link
Author

@stilllisisi
Copy link
Author

1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment