回到目录
大家好,我是阿赵。
之前写过发现贴图的计算方法,可以回顾一下:
法线贴图的计算方式
这里写一个HLSL的版本,再顺便说一下一些差异的地方
Shader "azhao/NormalHLSL"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_NormalTex("Normal Tex", 2D) = "black"{}
_normalScale("normalScale", Range(-1 , 1)) = 0
_specColor("SpecColor",Color) = (1,1,1,1)
_shininess("shininess", Range(1 , 100)) = 1
_specIntensity("specIntensity",Range(0,1)) = 1
_ambientIntensity("ambientIntensity",Range(0,1)) = 1
}
SubShader
{
Tags { "RenderType" = "Opaque" }
LOD 100
Pass
{
cull off
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
float4 tangent:TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos : TEXCOORD1;
//为了构建TBN矩阵,所以要获取下面这三个值
float3 worldNormal : TEXCOORD2;
float3 worldTangent :TEXCOORD3;
float3 worldBitangent : TEXCOORD4;
};
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float4 _NormalTex_ST;
float _normalScale;
float4 _specColor;
float _shininess;
float _specIntensity;
float _ambientIntensity;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
TEXTURE2D(_NormalTex);
SAMPLER(sampler_NormalTex);
//获取HalfLambert漫反射值
float GetHalfLambertDiffuse(float3 worldNormal)
{
Light light = GetMainLight();
float3 lightDir = normalize(light.direction);
float NDotL = saturate(dot(worldNormal, lightDir));
float halfVal = NDotL * 0.5 + 0.5;
return halfVal;
}
//获取BlinnPhong高光
float GetBlinnPhongSpec(float3 worldPos, float3 worldNormal)
{
float3 viewDir = normalize(GetCameraPositionWS().xyz - worldPos);
Light light = GetMainLight();
float3 lightDir = normalize(light.direction);
float3 halfDir = normalize((viewDir + lightDir));
float specDir = max(dot(normalize(worldNormal), halfDir), 0);
float specVal = pow(specDir, _shininess);
return specVal;
}
v2f vert(appdata v)
{
v2f o;
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
o.pos = vertexInput.positionCS;
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.worldPos = vertexInput.positionWS;
VertexNormalInputs normalInput = GetVertexNormalInputs(v.normal, v.tangent);
o.worldNormal = normalInput.normalWS;
o.worldTangent = normalInput.tangentWS;
o.worldBitangent = normalInput.bitangentWS;
return o;
}
half4 frag(v2f i) : SV_Target
{
//采样漫反射贴图的颜色
half4 col = SAMPLE_TEXTURE2D(_MainTex,sampler_MainTex, i.uv);
//计算法线贴图的UV
half2 normalUV = i.uv * _NormalTex_ST.xy + _NormalTex_ST.zw;
//得到切线空间的法线方向
half3 normalVal = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, normalUV), _normalScale);
//构建TBN矩阵
float3 tanToWorld0 = float3(i.worldTangent.x, i.worldBitangent.x, i.worldNormal.x);
float3 tanToWorld1 = float3(i.worldTangent.y, i.worldBitangent.y, i.worldNormal.y);
float3 tanToWorld2 = float3(i.worldTangent.z, i.worldBitangent.z, i.worldNormal.z);
//通过切线空间的法线方向和TBN矩阵,得出法线贴图代表的物体世界空间的法线方向
float3 worldNormal = float3(dot(tanToWorld0, normalVal), dot(tanToWorld1, normalVal), dot(tanToWorld2, normalVal));
//如果使用TransformTangentToWorld方法,上面构建矩阵求世界空间法线方向的过程可以简略成这样写
//half3 worldNormal = TransformTangentToWorld(normalVal, half3x3(i.worldTangent.xyz, i.worldBitangent, i.worldNormal));
//用法线贴图的世界空间法线,算漫反射
half diffuseVal = GetHalfLambertDiffuse(worldNormal);
//用法线贴图的世界空间法线,算高光角度
half specVal = GetBlinnPhongSpec(i.worldPos, worldNormal);
half3 specCol = _specColor.rgb * specVal *_specIntensity;
//最终颜色 = 环境色+漫反射颜色+高光颜色
half3 finalCol = UNITY_LIGHTMODEL_AMBIENT.rgb * _ambientIntensity + col.rgb*diffuseVal + specCol;
return half4(finalCol,1);
}
ENDHLSL
}
}
}
1、导入core.hlsl和Lighting.hlsl,因为需要用到空间转换方法和获取光照方向
2、VertexNormalInputs结构体和GetVertexNormalInputs方法。
和顶点的转换使用的GetVertexPositionInputs方法类似,core里面也提供了转换法线的方法GetVertexNormalInputs,它会返回一个结构体,里面包含了世界空间的法线、切线和bitangent。
我这里刚好三种都用到了,所以直接用这个方法,就不需要自己逐个算。如果不需要全部数据,也可以单独算其中一种,省点计算量
struct VertexNormalInputs
{
real3 tangentWS;
real3 bitangentWS;
float3 normalWS;
};
VertexNormalInputs GetVertexNormalInputs(float3 normalOS, float4 tangentOS)
{
VertexNormalInputs tbn;
// mikkts space compliant. only normalize when extracting normal at frag.
real sign = tangentOS.w * GetOddNegativeScale();
tbn.normalWS = TransformObjectToWorldNormal(normalOS);
tbn.tangentWS = TransformObjectToWorldDir(tangentOS.xyz);
tbn.bitangentWS = cross(tbn.normalWS, tbn.tangentWS) * sign;
return tbn;
}
3、UnpackNormalScale方法
UnpackNormalScale是在Packing.hlsl里面提供的方法,由于core.hlsl里面已经引用了packing,所以我们可以直接使用。
real3 UnpackNormalScale(real4 packedNormal, real bumpScale)
{
#if defined(UNITY_NO_DXT5nm)
return UnpackNormalRGB(packedNormal, bumpScale);
#else
return UnpackNormalmapRGorAG(packedNormal, bumpScale);
#endif
}
// Unpack from normal map
real3 UnpackNormalRGB(real4 packedNormal, real scale = 1.0)
{
real3 normal;
normal.xyz = packedNormal.rgb * 2.0 - 1.0;
normal.xy *= scale;
return normal;
}
// Unpack normal as DXT5nm (1, y, 0, x) or BC5 (x, y, 0, 1)
real3 UnpackNormalmapRGorAG(real4 packedNormal, real scale = 1.0)
{
// Convert to (?, y, 0, x)
packedNormal.a *= packedNormal.r;
return UnpackNormalAG(packedNormal, scale);
}
4、关于TBN矩阵
之前为了说明白原理,我都是不厌其烦的组建一个TBN矩阵来运算,其实如果用TransformTangentToWorld方法来转换,可以把转换TBN矩阵的过程简化成
half3 worldNormal = TransformTangentToWorld(normalVal, half3x3(i.worldTangent.xyz, i.worldBitangent, i.worldNormal));