30.凹凸纹理世界空间下计算

30.纹理-凹凸纹理-世界空间下计算


30.1 知识点

在世界空间下计算实现法线纹理Shader

主要思路

  • 在顶点着色器中计算切线空间到世界空间的变换矩阵
  • 在片元着色器中进行法线采样转换

关键步骤

  1. 属性相关

    • 与在切线空间下计算法线的流程相同。
  2. 结构体相关

    • 顶点着色器中传入:可以使用 UnityCG.cginc 中的 appdata_full,其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据。
    • 片元着色器中传入:自定义一个结构体,包含裁剪空间下坐标、UV坐标、世界空间下顶点位置、变换矩阵。
  3. 顶点着色器回调函数

    1. 顶点坐标模型转裁剪。
    2. 计算单张纹理和法线纹理 UV 坐标缩放偏移。
    3. 模型空间下顶点转换到世界空间(之后在片元着色器中用于计算视角方向)。
    4. 模型空间下的法线、切线转换到世界空间。
    5. 用世界空间下的法线和切线进行叉乘,再乘以切线中的 w 确定副切线方向。
    6. 构建模型空间到切线空间的变换矩阵:
    |   切线   |   副切线   |   法线   |
    
  4. 片元着色器回调函数

    1. 计算光的方向和视角方向。
    2. 取出法线贴图中的法线信息(使用纹理采样函数 tex2D)。
    3. 使用内置 UnpackNormal 函数对法线信息进行逆运算及可能的解压。
    4. 切线空间下的法线数据乘以 BumpScale 控制凹凸程度。
    5. 切线空间下法线转换到世界空间。
    6. 叠加单张纹理颜色和漫反射颜色。
    7. 用切线空间下的光方向、视角方向、法线方向进行 Blinn Phong 光照模型计算。

创建Shader骨架

Shader "Unlit/Lesson30_Bump_Texture_World_Normal"
{
    Properties
    {

    }
    SubShader
    {
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            v2f vert(appdata_full appdata_full)
            {

            }

            fixed4 frag(v2f v2f) : SV_Target
            {

            }
            ENDCG
        }
    }
}

声明属性,可以直接复制切线空间的

Properties
{
    _MainColor("MainColor", Color) = (1,1,1,1) // 漫反射颜色
    _MainTex("MainTex", 2D) = ""{} // 单张纹理
    _BumpMap("BumpMap", 2D) = ""{} // 法线纹理
    _BumpScale("BumpScale", Range(0,1)) = 1 // 凹凸程度
    _SpecularColor("SpecularColor", Color) = (1,1,1,1) // 高光反射颜色
    _SpecularNum("SpecularNum", Range(0,20)) = 18 // 光泽度
}

声明Pass语句块属性

float4 _MainColor; // 漫反射颜色
sampler2D _MainTex; // 颜色纹理
float4 _MainTex_ST; // 颜色纹理的缩放和平移
sampler2D _BumpMap; // 法线纹理
float4 _BumpMap_ST; // 法线纹理的缩放和平移
float _BumpScale; // 凹凸程度
float4 _SpecularColor; // 高光颜色
fixed _SpecularNum; // 光泽度

声明结构体

声明结构体,包括裁剪空间下坐标,纹理uv,世界坐标和切线到世界的变换矩阵

struct v2f
{
    //裁剪空间下坐标
    float4 pos:SV_POSITION;

    //纹理uv

    //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
    //float2 uvTex:TEXCOORD0;//颜色纹理
    //float2 uvBump:TEXCOORD1;//法线纹理

    //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
    float4 uv:TEXCOORD0; //纹理变量


    //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
    float3 worldPos:TEXCOORD1;

    //切线 到 世界空间的 变换矩阵
    float3x3 rotation:TEXCOORD2;
};

顶点着色器回调函数

顶点着色器回调函数中,把顶点坐标模型转裁剪。对单张纹理和法线纹理 UV坐标缩放偏移计算。把模型空间下顶点转换到世界空间下(之后在片元着色器中用于计算视角方向)。将模型空间下的法线、切线转换到世界空间下。用世界空间下的法线和切线进行叉乘 再乘以切线中的w(确定副切线方向)。构建模型空间到切线空间的变换矩阵。

v2f vert(appdata_full appdata_full)
{
    v2f v2f;

    //把模型空间下的顶点转到裁剪空间下
    v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

    //计算纹理的缩放偏移
    v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

    //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
    v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

    //把模型空间下的法线、切线转换到世界空间下
    float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
    float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

    //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
    //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
    float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

    //这个就是我们 切线空间到世界空间的 转换矩阵
    //       |    |     |
    //      切线 副切线 法线
    //       |    |     |
    v2f.rotation = float3x3(worldTangent.x, worldBinormal.x, worldNormal.x,
            worldTangent.y, worldBinormal.y, worldNormal.y,
            worldTangent.z, worldBinormal.z, worldNormal.z);


    return v2f;
}

片元着色器回调函数

片元着色器回调函数中,计算光的方向、视角方向。取出法线贴图中的法线信息(利用纹理采样函数tex2D)。利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压。用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度。将计算完毕后的切线空间下的法线转换到世界空间下。得到单张纹理颜色和漫反射颜色的叠加颜色。用切线空间下的 光方向、视角方向、法线方向 进行Blinn Phong光照模型计算。

fixed4 frag(v2f v2f) : SV_Target
{
    //世界空间下光的方向
    fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

    //世界空间下视角方向
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));

    //纹理采样函数tex2D
    //通过纹理采样函数 取出法线纹理贴图当中的数据
    float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

    //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
    //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
    float3 tangentNormal = UnpackNormal(packedNormal);

    //乘以凹凸程度的系数
    tangentNormal *= _BumpScale;

    //把计算完毕后的切线空间下的法线转换到世界空间下
    float3 worldNormal = mul(v2f.rotation, tangentNormal);


    //接下来就来处理 带颜色纹理的 布林方光照模型计算

    //颜色纹理和漫反射颜色的 叠加
    fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

    //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
    fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

    //半角向量 = 视角方向 + 光的方向
    float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));

    //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
    fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfA)), _SpecularNum);

    //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
    fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

    return fixed4(color.rgb, 1);
}

创建材质查看效果

关于光照方向的计算方式

计算光照方式的两种方式

  1. 模拟定向光源:用 _WorldSpaceLightPos0 作为光照方向,表示光线是平行的,而不是从特定点发射,模拟太阳光效果。
  2. 模拟点光源:用 _WorldSpaceLightPos0 - 顶点坐标 作为光照方向,表示光线是从特定点发射的,并朝着顶点方向,模拟点光源效果。

之前我们一直使用的是平行光源,现在改代码,改成点光源

fixed4 frag(v2f v2f) : SV_Target
{
    //世界空间下光的方向

    //改成使用点光源
    fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - v2f.worldPos);

    //下面两句是等价的 无非是一个使用api 一个直接传 UnityWorldSpaceLightDir是把世界坐标传进去得到光的方向
    // fixed3 lightDir = normalize(UnityWorldSpaceLightDir(v2f.worldPos));
    // fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
    ...
}

修改光源角度,可以看到使用点光源后,上面的面始终是亮的。因为光源位置没变,顶点没变,所以修改光源角度不起作用。


修改凹凸系数的计算方式,让法线系数不影响光照

我们目前这种直接让法线乘以凹凸系数的计算方式,并不是一个标准算法。因为当凹凸系数趋近于0时,会影响光照模型的计算。为了让凹凸系数不影响光的效果,有一种专门的算法:

  1. 只让法线中的 xy 乘以凹凸系数:

    tangentNormal.xy *= _BumpScale;
    
  2. 保证法线为单位向量(让法线不会为0,而是趋近于顶点法线):

    // x² + y² + z² = 1
    // z² = 1 - (x² + y²)
    // z = sqrt(1.0 - (x² + y²))
    tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
    

通过这样的计算,当凹凸系数在0~1之间变化时,会保证法线为单位向量,这样就不会影响光照表现了。

注意:这种算法并不是来自真实的物理规律,只是为了“看起来正常”。

fixed4 frag(v2f v2f) : SV_Target {
    ...
    tangentNormal *= _BumpScale;
    tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
    ...
}

修改后,凹凸系数的变化不会影响光照,可以进一步修改切线空间下的凹凸纹理shader。

提高性能的写法

我们目前在 v2f 结构体中,使用了 float3float3x3 的两个变量来存储世界坐标顶点位置和变换矩阵。然而,很多世界空间下计算法线贴图的Shader中,往往会使用3个 float4 类型的变量来存储它们。

这种写法的好处是,在很多情况下可以提高性能,因为它更好地与GPU的硬件架构匹配。float4 类型的寄存器是非常高效的,因为现代GPU通常会以4分量的向量为基本单位进行并行计算。相比之下,float3x3 矩阵需要更多的寄存器和指令来表示和计算。

结构体使用三个4维向量

结构体使用三个4维向量,每个向量存储 xyz 变换矩阵值,w 存储世界坐标:

struct v2f
{
    //裁剪空间下坐标
    float4 pos:SV_POSITION;

    //纹理uv

    //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
    //float2 uvTex:TEXCOORD0;//颜色纹理
    //float2 uvBump:TEXCOORD1;//法线纹理

    //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
    float4 uv:TEXCOORD0; //纹理变量


    //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
    //float3 worldPos:TEXCOORD1;

    //切线 到 世界空间的 变换矩阵
    //float3x3 rotation:TEXCOORD2;

    //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
    float4 TtoW0:TEXCOORD1;
    float4 TtoW1:TEXCOORD2;
    float4 TtoW2:TEXCOORD3;
};

修改顶点函数

修改顶点函数,给三个变量重新赋值

v2f vert(appdata_full appdata_full)
{
    v2f v2f;

    //把模型空间下的顶点转到裁剪空间下
    v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

    //计算纹理的缩放偏移
    v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
    v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

    //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
    //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
    float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

    //把模型空间下的法线、切线转换到世界空间下
    float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
    float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

    //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
    //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
    float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

    //这个就是我们 切线空间到世界空间的 转换矩阵
    //       |    |     |
    //      切线 副切线 法线
    //       |    |     |
    //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
    //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
    //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
    v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
    v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
    v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

    return v2f;
}

修改片元函数

修改片元函数,使用三个向量和法线进行矩阵运算

fixed4 frag(v2f v2f) : SV_Target
{
    //世界空间下光的方向
    fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

    //世界空间下视角方向
    // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
    float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
    fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

    //纹理采样函数tex2D
    //通过纹理采样函数 取出法线纹理贴图当中的数据
    float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

    //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
    //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
    float3 tangentNormal = UnpackNormal(packedNormal);

    //乘以凹凸程度的系数
    tangentNormal.xy *= _BumpScale;
    tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


    //把计算完毕后的切线空间下的法线转换到世界空间下
    // float3 worldNormal = mul(v2f.rotation, tangentNormal);

    //以下两种写法等价 但是可以一步到位
    //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
    //float3 worldNormal = mul(rotation, tangentNormal);
    //本质 就是在进行矩阵运算
    float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
dot(v2f.TtoW2.xyz, tangentNormal));


    //接下来就来处理 带颜色纹理的 布林方光照模型计算

    //颜色纹理和漫反射颜色的 叠加
    fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

    //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
    fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

    //半角向量 = 视角方向 + 光的方向
    float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));

    //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
    fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
        max(0, dot(worldNormal, halfA)), _SpecularNum);

    //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
    fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

    return fixed4(color.rgb, 1);
}

30.2 知识点代码

Lesson30_纹理_凹凸纹理_世界空间下计算

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson30_纹理_凹凸纹理_世界空间下计算 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 在世界空间下计算 实现法线纹理Shader

        //主要思路
        //在顶点着色器中计算切线空间到世界空间的变换矩阵
        //在片元着色器中进行法线采样转换

        //关键点:
        //1.属性相关(和切下空间下计算相同)

        //2.结构体相关
        //  顶点着色器中传入:
        //  可以使用 UnityCG.cginc 中的 appdata_full
        //  其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
        //
        //  片元着色器中传入:
        //  自定义一个结构体
        //  其中包含 裁剪空间下坐标、uv坐标、世界空间下顶点位置、变换矩阵

        //3.顶点着色器回调函数中
        //  2-1 顶点坐标模型转裁剪
        //  2-2 单张纹理和法线纹理 UV坐标缩放偏移计算
        //  2-3 模型空间下顶点转换到世界空间下(之后在片元着色器中用于计算视角方向)
        //  2-4 将模型空间下的法线、切线转换到世界空间下
        //  2-5 副切线计算
        //      用世界空间下的法线和切线进行叉乘 再乘以切线中的w(确定副切线方向)
        //  2-6 构建模型空间到切线空间的变换矩阵
        //       |    |     |
        //      切线 副切线 法线
        //       |    |     |

        //4.片元着色器回调函数中
        //  3-1 计算光的方向、视角方向
        //  3-2 取出法线贴图中的法线信息(利用纹理采样函数tex2D)
        //  3-3 利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
        //  3-4 用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
        //  3-5 将计算完毕后的切线空间下的法线转换到世界空间下

        //  3-6 得到单张纹理颜色和漫反射颜色的叠加颜色
        //  3-7 用切线空间下的 光方向、视角方向、法线方向 进行Blinn Phong光照模型计算

        #endregion

        #region 知识点二 关于光照方向的计算方式

        //计算光照方式的两种方式:
        //1.模拟定向光源
        //直接得到_WorldSpaceLightPos0光照位置 作为光照方向
        //表示光线是平行的,而不是从特定点发射
        //一般模拟太阳光效果 采用这种方式

        //2.模拟点光源
        //用光照位置_WorldSpaceLightPos0 减去 顶点坐标
        //表示光线是从特定点发射的,并朝着顶点方向
        //一般定点光源 采用这种方式

        #endregion

        #region 知识点三 修改凹凸系数的计算方式,让法线系数不影响光照

        //我们目前这种直接让 法线 * 凹凸系数的计算方式
        //并不是一个标准算法
        //因为当凹凸系数趋近于0时,会影响光照模型的计算

        //为了让凹凸系数不影响光的效果
        //有一种专门的算法

        //1.只让法线中的xy乘以凹凸系数
        //tangentNormal.xy *= _BumpScale;

        //2.保证法线为单位向量(让法线不会为0,而是趋近于顶点法线)
        //  x² + y² + z² = 1
        //  z² = 1 - (x² + y²)
        //  z = 根号下(1 - (x² + y²))
        //tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

        //通过这样的计算,当凹凸系数在0~1之间变化时,会保证法线为单位向量
        //这样就不会影响光照表现了

        //注意:这种算法并不是来自真实的物理规律
        //      只是为了“看起来正常”

        #endregion

        #region 知识点四 提高性能的写法

        //我们目前在v2f结构体中
        //世界坐标顶点位置和变换矩阵使用了
        //float3 和 float3x3 的两个变量来存储

        //但是在很多世界空间下计算 法线贴图的Shader中
        //往往会使用3个 float4 类型的变量来存储它们

        //这样做的目的是因为
        //这种写法在很多情况下可以提高性能,因为它更好地与GPU的硬件架构匹配
        //float4 类型的寄存器是非常高效的
        //因为现代GPU通常会以 4 分量的向量为基本单位进行并行计算
        //float3x3 矩阵相对来说需要更多的寄存器和指令来表示和计算

        #endregion
    }
}

Lesson30_Bump_Texture_World_Normal.shader

Shader "Unlit/Lesson30_Bump_Texture_World_Normal"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)//  漫反射颜色 
        _MainTex("MainTex", 2D) = ""{}//  单张纹理
        _BumpMap("BumpMap", 2D) = ""{}//  法线纹理
        _BumpScale("BumpScale", Range(0,1)) = 1//  凹凸程度
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)//  高光反射颜色
        _SpecularNum("SpecularNum", Range(0,20)) = 18//  光泽度
    }
    SubShader
    {
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;

                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
                float3 worldPos:TEXCOORD1;

                //切线 到 世界空间的 变换矩阵
                float3x3 rotation:TEXCOORD2;
            };

            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

                //把模型空间下的顶点转到裁剪空间下
                v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

                //计算纹理的缩放偏移
                v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
                v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

                //把模型空间下的法线、切线转换到世界空间下
                float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
                float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
                float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

                //这个就是我们 切线空间到世界空间的 转换矩阵
                //       |    |     |
                //      切线 副切线 法线
                //       |    |     |
                v2f.rotation = float3x3(worldTangent.x, worldBinormal.x, worldNormal.x,
                                        worldTangent.y, worldBinormal.y, worldNormal.y,
                                        worldTangent.z, worldBinormal.z, worldNormal.z);


                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //世界空间下光的方向

                //改成使用点光源
                // fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz - v2f.worldPos);

                //下面两句是等价的 无非是一个使用api 一个直接传 UnityWorldSpaceLightDir是把世界坐标传进去得到光的方向
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(v2f.worldPos));
                // fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //世界空间下视角方向
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));

                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //乘以凹凸程度的系数
                tangentNormal *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                //把计算完毕后的切线空间下的法线转换到世界空间下
                float3 worldNormal = mul(v2f.rotation, tangentNormal);


                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

                //半角向量 = 视角方向 + 光的方向
                float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));

                //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(worldNormal, halfA)), _SpecularNum);

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
}

Lesson30_Bump_Texture_World_High_Performance.shader

Shader "Unlit/Lesson30_Bump_Texture_World_High_Performance"
{
    Properties
    {
        _MainColor("MainColor", Color) = (1,1,1,1)//  漫反射颜色 
        _MainTex("MainTex", 2D) = ""{}//  单张纹理
        _BumpMap("BumpMap", 2D) = ""{}//  法线纹理
        _BumpScale("BumpScale", Range(0,1)) = 1//  凹凸程度
        _SpecularColor("SpecularColor", Color) = (1,1,1,1)//  高光反射颜色
        _SpecularNum("SpecularNum", Range(0,20)) = 18//  光泽度
    }
    SubShader
    {
        Pass
        {
            Tags
            {
                "LightMode"="ForwardBase"
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            float4 _MainColor; //漫反射颜色
            sampler2D _MainTex; //颜色纹理
            float4 _MainTex_ST; //颜色纹理的缩放和平移
            sampler2D _BumpMap; //法线纹理
            float4 _BumpMap_ST; //法线纹理的缩放和平移
            float _BumpScale; //凹凸程度
            float4 _SpecularColor; //高光颜色
            fixed _SpecularNum; //光泽度

            struct v2f
            {
                //裁剪空间下坐标
                float4 pos:SV_POSITION;

                //纹理uv

                //我们可以单独的声明两个float2的成员用于记录 颜色和法线纹理的uv坐标
                //float2 uvTex:TEXCOORD0;//颜色纹理
                //float2 uvBump:TEXCOORD1;//法线纹理

                //也可以直接声明一个float4的成员 xy用于记录颜色纹理的uv,zw用于记录法线纹理的uv
                float4 uv:TEXCOORD0; //纹理变量


                //顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
                //float3 worldPos:TEXCOORD1;

                //切线 到 世界空间的 变换矩阵
                //float3x3 rotation:TEXCOORD2;

                //代表我们切线空间到世界空间的 变换矩阵的3行 三个变量的w存世界坐标
                float4 TtoW0:TEXCOORD1;
                float4 TtoW1:TEXCOORD2;
                float4 TtoW2:TEXCOORD3;
            };

            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

                //把模型空间下的顶点转到裁剪空间下
                v2f.pos = UnityObjectToClipPos(appdata_full.vertex);

                //计算纹理的缩放偏移
                v2f.uv.xy = appdata_full.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                v2f.uv.zw = appdata_full.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //得到世界空间下的 顶点位置 用于之后在片元函数中计算视角方向(世界空间下的)
                //v2f.worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);
                float3 worldPos = mul(unity_ObjectToWorld, appdata_full.vertex);

                //把模型空间下的法线、切线转换到世界空间下
                float3 worldNormal = UnityObjectToWorldNormal(appdata_full.normal);
                float3 worldTangent = UnityObjectToWorldDir(appdata_full.tangent);

                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条
                //cross是叉乘 appdata_full.tangent.w代表叉乘结果方向
                float3 worldBinormal = cross(normalize(worldTangent), normalize(worldNormal)) * appdata_full.tangent.w;

                //这个就是我们 切线空间到世界空间的 转换矩阵
                //       |    |     |
                //      切线 副切线 法线
                //       |    |     |
                //v2f.rotation = float3x3( worldTangent.x, worldBinormal.x,  worldNormal.x,
                //                          worldTangent.y, worldBinormal.y,  worldNormal.y,
                //                          worldTangent.z, worldBinormal.z,  worldNormal.z);
                v2f.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                v2f.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                v2f.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);

                return v2f;
            }

            fixed4 frag(v2f v2f) : SV_Target
            {
                //世界空间下光的方向
                fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);

                //世界空间下视角方向
                // fixed3 viewDir = normalize(UnityWorldSpaceViewDir(v2f.worldPos));
                float3 worldPos = float3(v2f.TtoW0.w, v2f.TtoW1.w, v2f.TtoW2.w);
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);

                //乘以凹凸程度的系数
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));


                //把计算完毕后的切线空间下的法线转换到世界空间下
                // float3 worldNormal = mul(v2f.rotation, tangentNormal);

                //以下两种写法等价 但是可以一步到位
                //float3x3 rotation = float3x3(v2f.TtoW0.xyz, v2f.TtoW1.xyz, v2f.TtoW2.xyz );
                //float3 worldNormal = mul(rotation, tangentNormal);
                //本质 就是在进行矩阵运算
                float3 worldNormal = float3(dot(v2f.TtoW0.xyz, tangentNormal), dot(v2f.TtoW1.xyz, tangentNormal),
            dot(v2f.TtoW2.xyz, tangentNormal));


                //接下来就来处理 带颜色纹理的 布林方光照模型计算

                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;

                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(世界坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(worldNormal, normalize(lightDir)));

                //半角向量 = 视角方向 + 光的方向
                float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));

                //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(世界坐标系下的法线, 半角向量)), 光泽度)
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(
                    max(0, dot(worldNormal, halfA)), _SpecularNum);

                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

                return fixed4(color.rgb, 1);
            }
            ENDCG
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏