30.纹理-凹凸纹理-世界空间下计算
30.1 知识点
在世界空间下计算实现法线纹理Shader
主要思路
- 在顶点着色器中计算切线空间到世界空间的变换矩阵
- 在片元着色器中进行法线采样转换
关键步骤
属性相关
- 与在切线空间下计算法线的流程相同。
结构体相关
- 顶点着色器中传入:可以使用 UnityCG.cginc 中的
appdata_full
,其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据。 - 片元着色器中传入:自定义一个结构体,包含裁剪空间下坐标、UV坐标、世界空间下顶点位置、变换矩阵。
- 顶点着色器中传入:可以使用 UnityCG.cginc 中的
顶点着色器回调函数
- 顶点坐标模型转裁剪。
- 计算单张纹理和法线纹理 UV 坐标缩放偏移。
- 模型空间下顶点转换到世界空间(之后在片元着色器中用于计算视角方向)。
- 模型空间下的法线、切线转换到世界空间。
- 用世界空间下的法线和切线进行叉乘,再乘以切线中的
w
确定副切线方向。 - 构建模型空间到切线空间的变换矩阵:
| 切线 | 副切线 | 法线 |
片元着色器回调函数
- 计算光的方向和视角方向。
- 取出法线贴图中的法线信息(使用纹理采样函数
tex2D
)。 - 使用内置
UnpackNormal
函数对法线信息进行逆运算及可能的解压。 - 切线空间下的法线数据乘以 BumpScale 控制凹凸程度。
- 切线空间下法线转换到世界空间。
- 叠加单张纹理颜色和漫反射颜色。
- 用切线空间下的光方向、视角方向、法线方向进行 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);
}
创建材质查看效果
关于光照方向的计算方式
计算光照方式的两种方式
- 模拟定向光源:用
_WorldSpaceLightPos0
作为光照方向,表示光线是平行的,而不是从特定点发射,模拟太阳光效果。 - 模拟点光源:用
_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时,会影响光照模型的计算。为了让凹凸系数不影响光的效果,有一种专门的算法:
只让法线中的
xy
乘以凹凸系数:tangentNormal.xy *= _BumpScale;
保证法线为单位向量(让法线不会为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
结构体中,使用了 float3
和 float3x3
的两个变量来存储世界坐标顶点位置和变换矩阵。然而,很多世界空间下计算法线贴图的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