29.凹凸纹理切线空间下计算

29.纹理-凹凸纹理-切线空间下计算


29.1 知识点

导入测试资源


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

关键步骤概括

  1. 属性相关

    1. 漫反射颜色
    2. 单张纹理
    3. 法线纹理
    4. 凹凸程度
    5. 高光反射颜色
    6. 光泽度
  2. 结构体相关

    1. 顶点着色器中传入
      • 使用 UnityCG.cginc 中的 appdata_full,包含顶点、法线、切线、纹理坐标等数据。
    2. 片元着色器中传入
      • 自定义结构体,包含以下数据:
        • 裁剪空间下坐标
        • UV 坐标
        • 光照方向
        • 视角方向
  3. 顶点着色器回调函数

    1. 模型坐标转裁剪坐标
    2. UV坐标缩放偏移计算(单张纹理与法线纹理)
    3. 副切线计算
      • 使用模型空间中的法线和切线进行叉乘,再乘以切线中的w确定副切线方向。
    4. 构建模型空间到切线空间的变换矩阵
      • 切线
      • 副切线
      • 法线
    5. 光照方向和视角方向转换到模型空间
      • 使用内置函数 ObjSpaceLightDirObjSpaceViewDir
    6. 光照方向和视角方向转换到切线空间
      • 通过之前的变换矩阵进行乘法运算。
  4. 片元着色器回调函数

    1. 采集法线贴图中的法线信息
      • 使用纹理采样函数 tex2D
    2. 逆运算并解压法线信息
      • 使用 UnpackNormal 内置函数。
    3. 用切线空间的法线数据乘以 BumpScale 控制凹凸程度。
    4. 获取叠加的漫反射颜色和单张纹理颜色。
    5. 基于 Blinn Phong 光照模型,利用切线空间的光照方向、视角方向、法线方向进行光照计算。

创建 Shader,保留骨架

Shader "Unlit/Lesson29_Bump_Texture_Tangent"
{
    Properties
    {

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

            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 lightDir:TEXCOORD1;
    
    //视角的方向 相对于切线空间下的
    float3 viewDir:TEXCOORD2;
};

顶点函数逻辑

进行顶点坐标模型转裁剪、对单张纹理和法线纹理 UV 坐标缩放偏移计算、计算副切线、构建变换矩阵,通过变换矩阵得到光方向和视角方向。

//  顶点着色器中传入:
//  可以使用 UnityCG.cginc 中的 appdata_full
//  其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
v2f vert(appdata_full appdata_full)
{
    v2f v2f;

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

    //单张纹理和法线纹理 UV坐标缩放偏移计算
    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;

    //在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
    //切线、副切线、法线
    //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条(确定副切线方向)
    //cross是叉乘 appdata_full.tangent是切线 appdata_full.normal是法线 appdata_full.tangent.w代表叉乘结果方向
    float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
                tangent.w;

    //构建模型空间到切线空间的变换矩阵
    float3x3 rotation = float3x3( appdata_full.tangent.xyz,
                                    binormal,
                                    appdata_full.normal);

    //模型空间下的光的方向
    //v2f.lightDir = ObjSpaceLightDir(appdata_full.vertex);
    
    //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
    v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

    //模型空间下的视角的方向
    //v2f.viewDir = ObjSpaceViewDir(appdata_full.vertex);

    //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 视角的方向了
    v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));

    return v2f;
}

片元函数逻辑

取出法线贴图中的法线信息。利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压。用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度。得到单张纹理颜色和漫反射颜色的叠加颜色。用切线空间下的 光方向、视角方向、法线方向 进行Blinn Phong光照模型计算。

//  片元着色器中传入:
//  自定义一个结构体
//  其中包含 裁剪空间下坐标、uv坐标、光的方向、视角的方向
fixed4 frag(v2f v2f) : SV_Target
{
    //纹理采样函数tex2D
    //通过纹理采样函数 取出法线纹理贴图当中的数据
    float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

    //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
    //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
    float3 tangentNormal = UnpackNormal(packedNormal);
    
    //用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
    //注意 tangentNormal不要进行单位化 直接用即可 因为乘了BumpScale就不是单位向量了
    tangentNormal *= _BumpScale;

    //接下来就来处理 带颜色纹理的 布林方光照模型计算
    
    //颜色纹理和漫反射颜色的 叠加
    fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
    
    //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(切线坐标系下的法线, 光的方向))
    fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(tangentNormal, normalize(v2f.lightDir)));
    
    //半角向量 = 视角方向 + 光的方向
    float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
    
    //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(切线坐标系下的法线, 半角向量)), 光泽度)
    fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfA)), _SpecularNum);
    
    //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
    fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

    return fixed4(color.rgb, 1);
}

创建材质

赋值给模型查看效果。可以看到有模拟凹凸感的效果,修改凹凸系数可以让凹凸感更加明显。



29.2 知识点代码

Lesson29_纹理_凹凸纹理_切线空间下计算

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

public class Lesson29_纹理_凹凸纹理_切线空间下计算 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 导入测试资源

        //对颜色(单张)纹理和发现纹理进行相关设置

        #endregion

        #region 知识点二 在切线空间下计算 实现法线纹理Shader

        //关键点:
        //1.属性相关
        //  漫反射颜色 
        //  单张纹理
        //  法线纹理
        //  凹凸程度
        //  高光反射颜色
        //  光泽度

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

        //3.顶点着色器回调函数中
        //  2-1 顶点坐标模型转裁剪
        //  2-2 单张纹理和法线纹理 UV坐标缩放偏移计算
        //  2-3 副切线计算
        //      用模型空间中的法线和切线进行叉乘 再乘以切线中的w(确定副切线方向)
        //  2-4 构建模型空间到切线空间的变换矩阵
        //      ——  切线  ——
        //      —— 副切线  ——
        //      ——  法线  ——
        //  2-5 将光照方向和视角方向转换到模型空间(利用ObjSpaceLightDir和ObjSpaceViewDir内置函数)
        //  2-6 将光照方向和视角方向转换到切线空间(利用变换矩阵进行乘法运算)

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

        #endregion
    }
}

Lesson29_Bump_Texture_Tangent.shader

Shader "Unlit/Lesson29_Bump_Texture_Tangent"
{
    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,256)) = 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 lightDir:TEXCOORD1;

                //视角的方向 相对于切线空间下的
                float3 viewDir:TEXCOORD2;
            };


            //  顶点着色器中传入:
            //  可以使用 UnityCG.cginc 中的 appdata_full
            //  其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
            v2f vert(appdata_full appdata_full)
            {
                v2f v2f;

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

                //单张纹理和法线纹理 UV坐标缩放偏移计算
                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;

                //在顶点着色器当中 得到 模型空间到切线空间的 转换矩阵
                //切线、副切线、法线
                //计算副切线 计算叉乘结果后 垂直与切线和法线的向量有两条 通过乘以 切线当中的w,就可以确定是哪一条(确定副切线方向)
                //cross是叉乘 appdata_full.tangent是切线 appdata_full.normal是法线 appdata_full.tangent.w代表叉乘结果方向
                float3 binormal = cross(normalize(appdata_full.tangent), normalize(appdata_full.normal)) * appdata_full.
                    tangent.w;

                //构建模型空间到切线空间的变换矩阵
                float3x3 rotation = float3x3(appdata_full.tangent.xyz,
                                     binormal,
                                     appdata_full.normal);

                //模型空间下的光的方向
                //v2f.lightDir = ObjSpaceLightDir(appdata_full.vertex);

                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 光的方向了
                v2f.lightDir = mul(rotation, ObjSpaceLightDir(appdata_full.vertex));

                //模型空间下的视角的方向
                //v2f.viewDir = ObjSpaceViewDir(appdata_full.vertex);

                //乘以模型空间到切线空间的转换矩阵 就可以得到切线空间下的 视角的方向了
                v2f.viewDir = mul(rotation, ObjSpaceViewDir(appdata_full.vertex));

                return v2f;
            }


            //  片元着色器中传入:
            //  自定义一个结构体
            //  其中包含 裁剪空间下坐标、uv坐标、光的方向、视角的方向
            fixed4 frag(v2f v2f) : SV_Target
            {
                //纹理采样函数tex2D
                //通过纹理采样函数 取出法线纹理贴图当中的数据
                float4 packedNormal = tex2D(_BumpMap, v2f.uv.zw);

                //利用内置的UnpackNormal函数对法线信息进行逆运算以及可能的解压
                //将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算,最终得到切线空间下的法线数据
                float3 tangentNormal = UnpackNormal(packedNormal);
                
                //用得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸程度
                //注意 tangentNormal不要进行单位化 直接用即可 因为乘了BumpScale就不是单位向量了
                tangentNormal *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                //接下来就来处理 带颜色纹理的 布林方光照模型计算
                
                //颜色纹理和漫反射颜色的 叠加
                fixed3 albedo = tex2D(_MainTex, v2f.uv.xy) * _MainColor.rgb;
                
                //兰伯特漫反射颜色 = 光的颜色 * 漫反射材质的颜色 * max(0, dot(切线坐标系下的法线, 光的方向))
                fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0, dot(tangentNormal, normalize(v2f.lightDir)));
                
                //半角向量 = 视角方向 + 光的方向
                float3 halfA = normalize(normalize(v2f.viewDir) + normalize(v2f.lightDir));
                
                //高光反射的颜色 = 光的颜色 * 高光反射材质的颜色 * pow(max(0, dot(切线坐标系下的法线, 半角向量)), 光泽度)
                fixed3 specularColor = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(tangentNormal, halfA)), _SpecularNum);
                
                //布林方光照颜色 = 环境光颜色 + 兰伯特漫反射颜色 + 高光反射的颜色
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;

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


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

×

喜欢就点赞,疼爱就打赏