HPP_Graphics_2.5 Bump Mapping

a). Bump Map的分类

BumpMap00

b). Normal Map

  • 法线贴图:存有物体局部(切线空间)表面法线信息的一张贴图

Link1 NOTE.md

Link2

  • 储存空间:
    • (一般采用)切线空间
    • 模型空间

b.1). Tangent Space

Link3 切线空间(Tangent Space)

  • 切线空间中,x轴为切线,y轴为副切线,z轴为表面初始法线

    TangentSpace00

    • 其中,切线的方向对应UV空间的 $u$ 轴,副切线对应 $v$ 轴(详见Link1、Link3)
  • 使用切线空间的优点:

    1. 自由度高,可复用
    2. 可进行uv动画
    3. 可压缩

c). Unity中的法线压缩格式

UnityNormalZip

d). Parallax Mapping(时差映射)

Parallax_Mapping

d.1). 普通实现方式

Parallax_Mapping01

  • 这里能用切线空间的视线方向 $viewDirTS$ 来计算 UV 的偏移的原因正是因为切线空间的$x, y$轴和UV空间的 $u, v$ 轴对应;

d.2). Steep Parallax Mapping(陡峭视差映射)

  • 采用光线步进;

SteepParallaxMapping

  • 但如效果仍有不足,可采用浮雕贴图(Relief Mapping)

e). Relief Mapping(浮雕贴图)

ReliefMapping

  • 实现方法:
    • 最后一步步进后,在最后一步的范围内使用二分查找,直到找到近似P点的点;

ReliefMapping02

  • 优化方法:
    • 二分法性能开销过大;
    • 最后一个步进时得到高度 $h1$ 和 $h2$ ,$H_p = (h1+h2)/2$ (见上图)

作业 视差贴图、浮雕贴图的实现

  • 资料补充:视差贴图(Parallax Mapping)与陡峭视差贴图(Steep Palallax Mapping, 已收藏在收藏夹)

  • Shader见 S_Parallax_Relief.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
    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
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    Shader "BRJH/MetallicWorkflow"
    {
    Properties
    {
    _MainTex ("Texture", 2D) = "white" {}
    _DiffuseTint ("Diffuse Tint", color) = (1, 1, 1, 1)
    _Metallic ("Metallic", Range(0, 1)) = 0.5
    _Gloss ("Gloss", Range(1, 255)) = 20
    _NormalMap ("Normal", 2D) = "bump" {}
    _NormalScale ("Normal Scale", Range(0, 5)) = 1
    _HeightMap ("Height Map", 2D) = "black" {}
    _HeightScale ("Height Scale", Range(0, 0.3)) = 0.2
    _CubeMap ("Cube Map", Cube) = "_skybox" {}
    _EnvIntensity ("Envirment Intensity", Range(0, 5)) = 1
    [KeywordEnum(Default, POM)] _PARALLAX_MAPPING("Parallax Mapping Type", Float) = 0
    [KeywordEnum(T, F, C)] _USING_IBL("If use IBL", Float) = 0
    }
    SubShader
    {
    Tags { "RenderType"="Opaque"
    "LightMode"="ForwardBase" }

    Pass
    {
    CGPROGRAM
    #pragma vertex vert
    #pragma fragment frag
    // make fog work
    #pragma multi_compile_fog
    #pragma multi_compile _USING_IBL_T _USING_IBL_F
    #pragma multi_compile _PARALLAX_MAPPING_Default _PARALLAX_MAPPING_POM


    #include "UnityCG.cginc"
    #include "UnityPBSLighting.cginc"
    #include "Lighting.cginc"

    #define MAX_LAYER_NUM 20
    #define MIN_LAYER_NUM 20
    sampler2D _MainTex;
    fixed4 _MainTex_ST;
    sampler2D _NormalMap;
    fixed4 _NormalMap_ST;
    sampler2D _HeightMap;
    samplerCUBE _CubeMap;
    fixed4 _DiffuseTint;
    half _Metallic;
    fixed _Gloss;
    fixed _NormalScale;
    fixed _HeightScale;
    fixed _EnvIntensity;

    struct a2v {
    float4 vertex : POSITION;
    float3 normal : NORMAL;
    float4 tangent : TANGENT;
    float2 texcoord : TEXCOORD0;
    };

    struct v2f {
    float4 pos : SV_POSITION;
    float3 TangentLightDir : TEXCOORD0;
    float3 TangentViewDir : TEXCOORD1;
    float2 uv : TEXCOORD2;
    #if defined(_USING_IBL_T)
    float4 TtoW0 : TEXCOORD4;
    float4 TtoW1 : TEXCOORD5;
    float4 TtoW2 : TEXCOORD6;
    #endif
    };

    float3 ACESToneMapping(float3 color, float adapted_lum) {
    const float A = 2.51f;
    const float B = 0.03f;
    const float C = 2.43f;
    const float D = 0.59f;
    const float E = 0.14f;

    color *= adapted_lum;
    return (color * (A * color + B)) / (color * (C * color + D) + E);
    }

    // 浮雕映射
    float2 ReliefMapping(float2 uv, half3 TangentViewDir) {
    float NumLayer = 40;//lerp(MIN_LAYER_NUM, MAX_LAYER_NUM, abs(dot(Normal, TangentViewDir)));
    float2 dtex = _HeightScale * TangentViewDir.xy / TangentViewDir.z / NumLayer;
    float2 addUV = float2(0, 0);
    float2 currentUV = uv + addUV;
    float currentLayerHeight = 0;
    float currentHeightMapValue = tex2D(_HeightMap, currentUV + addUV).r;

    for (int n = 0; n < NumLayer; n++) {
    currentLayerHeight += 1.0/NumLayer;
    currentUV += dtex;
    currentHeightMapValue = tex2D(_HeightMap, currentUV).r;
    if (currentHeightMapValue < currentLayerHeight) {
    break;
    }
    }

    float2 T0 = currentUV, T1 = currentUV - dtex;
    for (int j = 0; j<20 ;j++) {
    float2 P0 = (T0+T1)/2;
    float P0_HeightMapValue = tex2D(_HeightMap, P0).r;
    float P0_LayerHeight = length(P0) / length(dtex*NumLayer);
    if (P0_HeightMapValue < P0_LayerHeight) {
    T1 = P0;
    }
    else {
    T0 = P0;
    }
    }
    return (T0 + T1) / 2 - uv;
    }

    v2f vert(a2v v) {
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);

    o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

    TANGENT_SPACE_ROTATION;

    o.TangentLightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
    o.TangentViewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;

    #if defined(_USING_IBL_T)
    float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
    float3 worldNormal = UnityObjectToWorldNormal(v.normal);
    float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;

    o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
    o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
    o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
    #endif
    return o;
    }

    fixed4 frag(v2f i) : SV_TARGET {
    //Base
    fixed3 TangentLightDir = normalize(i.TangentLightDir);
    fixed3 TangentViewDir = normalize(i.TangentViewDir);
    fixed3 HalfDir = normalize(TangentLightDir + TangentViewDir);

    float Height = tex2D(_HeightMap, i.uv).x;




    fixed4 packedNormal = (1-tex2D(_NormalMap, i.uv));
    fixed3 Normal = UnpackNormal(packedNormal);
    #if defined(_PARALLAX_MAPPING_Default) // 视察贴图
    float2 offsetUV = TangentViewDir.xy / TangentViewDir.z * Height * _HeightScale;
    i.uv += offsetUV;
    #elif defined(_PARALLAX_MAPPING_POM) // 浮雕贴图
    // float NumLayer = 20;//lerp(MIN_LAYER_NUM, MAX_LAYER_NUM, abs(dot(Normal, TangentViewDir)));
    // float2 dtex = _HeightScale * TangentViewDir.xy / TangentViewDir.z / NumLayer;
    // float currentLayerHeight = 0;

    // for (int n = 0; n < NumLayer; n++) {
    // currentLayerHeight += 1.0/NumLayer;
    // i.uv += dtex;
    // if (Height > currentLayerHeight) {
    // Height = tex2D(_HeightMap, i.uv).r;
    // }
    // else {
    // Height = (Height + tex2D(_HeightMap, i.uv).r)/2;
    // break;
    // }
    // }
    i.uv += ReliefMapping(i.uv, TangentViewDir);
    Height = tex2D(_HeightMap, i.uv).x;
    #endif
    fixed3 albedo = tex2D(_MainTex, i.uv) * _DiffuseTint.rgb;
    fixed3 SpecularTint;
    fixed OneMinusReflectivity;
    albedo = DiffuseAndSpecularFromMetallic(albedo, _Metallic, SpecularTint, OneMinusReflectivity);

    packedNormal = tex2D(_NormalMap, i.uv);
    Normal = UnpackNormal(packedNormal);

    // 通过高度计算IBL方向

    #if defined(_USING_IBL_T)
    float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
    float3 worldNormal = normalize(float3(i.TtoW0.z, i.TtoW1.z, i.TtoW2.z));
    float3 worldPosUndata = worldPos - worldNormal*Height*_HeightScale;
    float3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPosUndata));

    float NormalRef = normalize(cross(ddy(worldPosUndata), ddx(worldPosUndata))); // 通过高度贴图计算法线方向;
    fixed3 worldRef = reflect(-worldViewDir, NormalRef);
    // fixed3 WorldRef = normalize(fixed3(dot(i.TtoW0.xyz, TangentRef),
    // dot(i.TtoW1.xyz, TangentRef),
    // dot(i.TtoW2.xyz, TangentRef)));


    fixed3 Reflection = ACESToneMapping(texCUBElod(_CubeMap, float4(worldRef, (255-_Gloss)*8/255)).rgb, 1) * SpecularTint * _EnvIntensity;
    #endif

    Normal.xy *= _NormalScale;
    Normal.z = sqrt(1.0 - saturate(dot(Normal.xy, Normal.xy)));



    fixed3 Ambient = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo;

    fixed3 Diffuse = lerp(Ambient, _LightColor0.rgb * albedo, saturate(dot(Normal, TangentLightDir)));

    fixed3 Specular = _LightColor0.rgb * SpecularTint * pow(saturate(dot(HalfDir, Normal)), _Gloss);

    #if defined(_USING_IBL_T)
    //Reflection.rgb = lerp(diffuse*Specular, )
    fixed4 color = fixed4(Ambient+Diffuse+Specular+Reflection, 1);
    #elif defined(_USING_IBL_F)
    fixed4 color = fixed4(Ambient+Diffuse+Specular, 1);
    #endif

    return color;
    }

    ENDCG
    }
    }
    }

  • 在使用浮雕/视差贴图时,使用IBL;
    • 需要用Offset后的高度图和世界空间坐标得到更新后的世界坐标,再通过ddx,ddy叉乘获得世界法线,进一步进行CubeMap的采样;

Homework