//
一.MatCap是啥?
- MatCap全名叫做Material Capture(材质捕捉),最开始是在Zbrush上应用的。
- MatCap基本的概念—映射
- 将光源,材质信息离线烘焙成一张材质球放在贴图上,渲染时候直接拿来用,通过 计算将MatCap的纹理信息映射到模型上。
- 基于MatCap制作的Shader
- 不需要提供任何光照信息,只通过一张或者多张 MatCap就可以来模拟出想要的材质效果,只需要 制作合适的MatCap图。
二.能去做什么?
- 由于是采样贴图,所以可以将材质的各种属性,效果事先烘焙在纹理上
- 例如:粗糙度,金属度,风格化,硬阴影,反射,Fresnel等.....
- 可以制作多张MatCap图,通过角色粗糙度的不同在粗糙和光滑两张金属球之间插值采样
- 也可制作多张固定粗糙度的材质球,通过粗糙度范围直接指认所需要使用的材质球,无需插值
- 通过PS,SD等软件对matcap图进行高斯模糊从而达到对应不同粗糙度的需求。
粗糙度 对应的MatCap 输出
获取Matcap的几种方法
- 网络上直接搜索关键词,下载图片
- 一些网站分享
- 使用Blender,ZB等一些DCC软件去渲染
- 在引擎中制作好材质球,截图输出
- 手绘制作,通过PS调色
- 通过MaCrea软件制作生成
三.项目里为什么要用?
- 移动端游戏,用低廉的成本,达到类似PBR的渲染效果
- 给角色制作假光照
- 制作轮廓光,方向光,顶光,补光等...
- 风格化渲染
- 光影,各种高光(头发,眼球),皮肤等...
- 用来模拟反射球
- 对于Matcap的选用及制作
- 整体,因为matcap无法做到完全物理,所以就需要美术同学去关注以及处理Matcap与整体画面的和谐程度以及处理好那个度,避免本末倒置。
- 可以有主观的审美倾向,选择一些颜色丰富的材质,来呈现画面的颜色变化,以及冷暖对比等。
******************************************************************************************
一.基础MatCap
- 基础版的核心原理:将法线转换到视ViewSpace,并计算到适合采样UV纹理的区间,对MatCap进行采样
- 由于ViewSpace下的法线区域是【-1,1】,而UV的区域是【0,1】,所以要进行一个*0.5+0.5的操作
- 最后将输出的颜色与BaseColor(emissive)混合即可
Unity_ShaderLab
UE4_材质蓝图
- 基础版的核心原理:将法线转换到视ViewSpace,并计算到适合采样UV纹理的区间,对MatCap进行采样
-
- 可替换成使用基于模型法线进行采样
默认法线采样
基于模型法线采样效果会更好
- 但是基础版MatCap会出现单点采样的问题,对平面模型很不友好
- 基于模型法线方向去采样,同一个面上的法线方向都一样,因此采样的MatCap也是相同点,就会出现全部要同一颜色
- 置于屏幕边缘时采样溢出
迭代版MatCap
- 因为MatCap是在ViewSpace下采样的,所以人眼的方向是(0,0,1),以及因为模型所呈现出来的颜色就是对MatCap球面的映射,因此只需要获得到球面的法线方向,然后作为MatCap贴图的采样数据,就可以解决单点采样的问题
- 在球面上Bn是Br和Be(0,0,1)的半角矢量,Br和Be的和对其单位化就能求出Bn的值
手绘
手绘
对应的UE4核心代码
效果还是挺不错的
- 迭代版所存在的问题:边缘会出现采样不正确的情况。
- 会出现边缘颜色溢出
MatCap图
采样错误的效果
******************************************************************************************
第二种迭代版MatCap
由于WorldSpaceToViewSpace变换矩阵的精度问题,导致出现了上述方案的问题,因此需要基于世界下的CameraVector来重构变换矩阵
- 解决了边缘采样不正确的问题
左边第二种,右边原版
****************************************************************************************
四.应用
- 角色所出现的问题
- 角色在暗部下,整体效果偏暗
- 角色在暗部下,高光效果不明显
- 尽可能保持角色在亮部的效果不受改变
- 解决方案
- 通过dot(normal,fakelight)来给角色增加类平行光
- 通过MatCap来增强非金属/金属材质在暗部下的表现效果
- 通过Shadow来判断是否在阴影下,保持亮部效果不变
**************************************************************************************
- 测试角色在各个环境下的材质效果
- 部分较黑材质表现不佳
- 整体质感偏平
- 不同环境下,明暗色阶过大
室外 阴影下 室内
- 增加FakeLight
- 添加Matcap层
- 将金属材质和非金属材质所使用的四张MatCap存放在贴图的RGBA四个通道下,通过角色自身的粗糙度以及金属度来去选择是有某一张或者在其中做差值计算。
- 在BasePass内通过Shadow来判断是否在阴影下,从而决定功能是否生效
室外 迭代后 室内默认
*****************************************************************************************
冰材质效果提升
- 可以实现类似CubeMap的效果,并且性能消耗很低
默认 迭代
未添加Matcap
迭代后
******************************************************************************************
所存在的问题
- 因为Matcap是非完全物理的,属于trick,使用的时候要格外的小心,避免对游戏整体的PBR环境有过大的影响。
- 迭代版的Matcap在曲率过大的模型上(例:圆柱,圆锥体)依旧会出现一些显示不正确的情况
- matcap图是固定的,效果难以与场景环境有所交互,因此在角色上只适合作为一种补光方案出现
///
PBR虽然渲染效果真实,但是性能要求较高。尤其是在移动平台,计算能力有限,采用MatCap思想的Shader,用较低的计算成本,就可以达到类似PBR一样真实的渲染效果,可谓是在移动平台实现次时代渲染效果的一种优秀解决方案。
MatCap (Material Capture):材质捕获, 使用特定材质球的贴图,作为当前材质的视图空间环境贴图,从而实现具有均匀表面着色的反射材质物体的显示。
不像一般的Shader,需要提供光照,需要在Shader代码中进行漫长的演算,基于MatCap思想的Shader相当于MatCap贴图就把光照结果告知Shader。
需要注意,MatCap Shader有一定的局限性。因为从某种意义上来说,基于MatCap的Shader,就是某种固定光照条件下,从某个特定方向,特定角度的光照表现结果。
优点:不用提供光照,只要一张或多张MatCap贴图作为光照结果就好。
缺点:不适合相机频繁旋转和角度调节。需要结合一些光照交互,适当弥补MatCap太过单一整体光照表现的短板,比如结合一个光照反射的Reflection Cube Map。
Shader "MatCapShader"
{
Properties
{
//主颜色
_MainColor("Main Color", Color) = (1.0, 1.0, 1.0, 1.0)
//细节颜色
_DetailColor("Detail Color", Color) = (1.0, 1.0, 1.0, 1.0)
//细节纹理
_DetailTex("Detail Textrue", 2D) = "white" {}
//细节纹理深度偏移
_DetailTexDepthOffset("Detail Textrue Depth Offset", Float) = 1.0
//漫反射颜色
_DiffuseColor("Diffuse Color", Color) = (0.0, 0.0, 0.0, 0.0)
//漫反射纹理
_DiffuseTex("Diffuse Textrue", 2D) = "white" {}
//Material Capture纹理
_MatCap("MatCap", 2D) = "white" {}
//反射颜色
_ReflectionColor("Reflection Color", Color) = (0.2, 0.2, 0.2, 1.0)
//反射立方体贴图
_ReflectionMap("Reflection Cube Map", Cube) = "" {}
//反射强度
_ReflectionStrength("Reflection Strength", Range(0.0, 1.0)) = 0.5
}
SubShader
{
Tags
{
"Queue" = "Geometry"
"RenderType" = "Opaque"
}
Pass
{
Blend Off
Cull Back
ZWrite On
CGPROGRAM
#include "UnityCG.cginc"
#pragma fragment frag
#pragma vertex vert
float4 _MainColor;
float4 _DetailColor;
sampler2D _DetailTex;
float4 _DetailTex_ST;
float _DetailTexDepthOffset;
float4 _DiffuseColor;
sampler2D _DiffuseTex;
float4 _DiffuseTex_ST;
sampler2D _MatCap;
float4 _ReflectionColor;
samplerCUBE _ReflectionMap;
float _ReflectionStrength;
//顶点输入结构
struct VertexInput
{
float3 normal : NORMAL;
float4 position : POSITION;
float2 UVCoordsChannel1: TEXCOORD0;
};
//顶点输出(片元输入)结构
struct VertexToFragment
{
float3 detailUVCoordsAndDepth : TEXCOORD0;
float4 diffuseUVAndMatCapCoords : TEXCOORD1;
float4 position : SV_POSITION;
float3 worldSpaceReflectionVector : TEXCOORD2;
};
//------------------------------------------------------------
// 顶点着色器
//------------------------------------------------------------
VertexToFragment vert(VertexInput input)
{
VertexToFragment output;
//漫反射UV坐标准备:存储于TEXCOORD1的前两个坐标xy。
output.diffuseUVAndMatCapCoords.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DiffuseTex);
//MatCap坐标准备:将法线从模型空间转换到观察空间,存储于TEXCOORD1的后两个纹理坐标zw
output.diffuseUVAndMatCapCoords.z = dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w = dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));
//归一化的法线值区间[-1,1]转换到适用于纹理的区间[0,1]
output.diffuseUVAndMatCapCoords.zw = output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;
//坐标变换
output.position = UnityObjectToClipPos(input.position);
//细节纹理准备准备UV,存储于TEXCOORD0的前两个坐标xy
output.detailUVCoordsAndDepth.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DetailTex);
//深度信息准备,存储于TEXCOORD0的第三个坐标z
output.detailUVCoordsAndDepth.z = output.position.z;
//世界空间位置
float3 worldSpacePosition = mul(unity_ObjectToWorld, input.position).xyz;
//世界空间法线
float3 worldSpaceNormal = normalize(mul((float3x3)unity_ObjectToWorld, input.normal));
//世界空间反射向量
output.worldSpaceReflectionVector = reflect(worldSpacePosition - _WorldSpaceCameraPos.xyz, worldSpaceNormal);
return output;
}
//------------------------------------------------------------
// 片元着色器
//------------------------------------------------------------
float4 frag(VertexToFragment input) : COLOR
{
//镜面反射颜色
float3 reflectionColor = texCUBE(_ReflectionMap, input.worldSpaceReflectionVector).rgb * _ReflectionColor.rgb;
//漫反射颜色
float4 diffuseColor = tex2D(_DiffuseTex, input.diffuseUVAndMatCapCoords.xy) * _DiffuseColor;
//主颜色
float3 mainColor = lerp(lerp(_MainColor.rgb, diffuseColor.rgb, diffuseColor.a), reflectionColor, _ReflectionStrength);
//细节纹理
float3 detailMask = tex2D(_DetailTex, input.detailUVCoordsAndDepth.xy).rgb;
//细节颜色
float3 detailColor = lerp(_DetailColor.rgb, mainColor, detailMask);
//细节颜色和主颜色进行插值,成为新的主颜色
mainColor = lerp(detailColor, mainColor, saturate(input.detailUVCoordsAndDepth.z * _DetailTexDepthOffset));
//从提供的MatCap纹理中,提取出对应光照信息
float3 matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;
//最终颜色
float4 finalColor=float4(mainColor * matCapColor * 2.0, _MainColor.a);
return finalColor;
}
ENDCG
}
}
Fallback "VertexLit"
}
要使用MatCap贴图,主要是将法线从模型空间转换到视图空间,并切换到适合提取纹理UV的区域[0,1]。
1. TRANSFORM_TEX将模型顶点的uv和材质的Tiling、Offset两个变量进行运算,计算出实际显示用的uv。
TRANSFORM_TEX定义在UnityCG.cginc里:
// Transforms 2D UV by scale/bias property
#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)
tex参数为纹理坐标,name参数为材质
texcoord0 表示 0级mipmap对应的纹理,uv 存储在texcoord.xy,texcoord.zw不使用。依次类推。mipmap生成8张不断降采样的纹理。
所以使用他有两个前提:
1). #include "UnityCG.cginc"
2). 定义name##_ST,name##_ST又是指什么呢?是指纹理图片的缩放和偏移,S指Scale,T指Transform。
name##_ST.xy存储的是缩放值,而name##_ST.zw存储的是偏移值,分别对应着材质面板中,Tiling的x和y值,Offset的x和y值。name##_ST是float4类型的,其值为(Tiling.x,Tiling.y,Offset.x,Offset.y)。
output.diffuseUVAndMatCapCoords.xy = TRANSFORM_TEX(input.UVCoordsChannel1, _DiffuseTex);
2. Unity内置的矩阵UNITY_MATRIX_IT_MV,是UNITY_MATRIX_MV的逆转置矩阵,其作用正是将法线从模型空间转换到观察空间。于是顶点着色器vert中的这两句代码就很容易理解了:
//MatCap坐标准备:将法线从模型空间转换到观察空间,存储于TEXCOORD1的后两个纹理坐标zw
output.diffuseUVAndMatCapCoords.z = dot(normalize(UNITY_MATRIX_IT_MV[0].xyz), normalize(input.normal));
output.diffuseUVAndMatCapCoords.w = dot(normalize(UNITY_MATRIX_IT_MV[1].xyz), normalize(input.normal));
3. 而得到的视图空间的法线,区域是[-1,1],要转换到提取纹理UV的区域[0,1],就需要乘以0.5并加上0.5,那么顶点着色器vert中接下来的的这句代码也就可以理解了:
//归一化的法线值区间[-1,1]转换到适用于纹理的区间[0,1]
output.diffuseUVAndMatCapCoords.zw = output.diffuseUVAndMatCapCoords.zw * 0.5 + 0.5;
4. 稍后,在片元着色器frag中,用在顶点着色器中准备好的法线转换成的UV值,提取出MatCap的光照细节即可:
//从提供的MatCap纹理中,提取出对应光照信息
float3matCapColor = tex2D(_MatCap, input.diffuseUVAndMatCapCoords.zw).rgb;
渲染结果:
工程代码:
链接:https://pan.baidu.com/s/1Xa6kF1R5UjinxOZzYygBmw
提取码:dy8y