unity-shader-MatCap模拟pbr效果

unity-shader-MatCap模拟pbr效果, 用廉价的计算获取比较真实的类似pbr的效果. 不需要用到真实的光.
把之前零零散散的知识的整理一下


前篇

有图有真相

直接替换一个贴图, 就可以看到效果
贴图必须是方形的 2的n次幂 大小, 且中间的圆形最好与四个边相切, 可以超出一点点, 但不能覆盖不到. 下面会解释为什么要这样做.


其他资料


实现原理

其原理实现也很简单, 就是归一化的 法线 从模型空间变换到 观察空间 ( 也叫视空间 ), 只取 xy 坐标来处理,

如下图, 箭头方向代表 摄像机 的观察方向, 这个球代表了归一化的法线, 然后去球面上任意一点的三维向量, 丢弃z分量的话就, 就等价于投影到一个平面, 也只能投影到圆形区域.

平面上的圆形区域也就是需要采样的区域, 但由于贴图是uv是 0~1 范围的, 所以需要将 视空间 的 法向量 由 -1~1 映射上去, uv = (viewNormal.xy + 1 ) / 2

相关shader代码

  • 使用顶点的法线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    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插件的写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109

    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 中处理, 做好一个之后, 用来做 剪贴蒙版, 后续的处理那这个 蒙版 蒙住, 缩放一下就可以了.
下面 金黄色的图层就是做好的用来当做 剪贴蒙版.


相关参考