MatCap--用低廉的成本,达到类似PBR的渲染效果

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 
 

转载:https://blog.csdn.net/poem_qianmo/article/details/55803629

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值