Unity’s URP lower normal calculation

back to catalog

Hello everyone, I am Zhao.
I wrote the calculation method of discovering the map before, you can review it:
How the normal map is calculated

Write an HLSL version here, and by the way, some differences

1. Complete shader

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("shinness", 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
HLSL PROGRAM
#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;
//In order to build the TBN matrix, the following three values are to be obtained
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);

//Get HalfLambert diffuse reflection value
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;
}

//Get BlinnPhong highlights
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;
VertexPositionInputsvertexInput = 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
{
//sample the color of the diffuse texture
half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
// Calculate the UV of the normal map
half2 normalUV = i.uv * _NormalTex_ST.xy + _NormalTex_ST.zw;
//Get the normal direction of the tangent space
half3 normalVal = UnpackNormalScale(SAMPLE_TEXTURE2D(_NormalTex, sampler_NormalTex, normalUV), _normalScale);
//Construct TBN matrix
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);
//Through the normal direction of the tangent space and the TBN matrix, the normal direction of the world space of the object represented by the normal map is obtained
float3 worldNormal = float3(dot(tanToWorld0, normalVal), dot(tanToWorld1, normalVal), dot(tanToWorld2, normalVal));

//If the TransformTangentToWorld method is used, the process of constructing the matrix above to find the normal direction of the world space can be simplified as follows
//half3 worldNormal = TransformTangentToWorld(normalVal, half3x3(i.worldTangent.xyz, i.worldBitangent, i.worldNormal));

//The world space normal of the normal map, which is diffuse reflection
half diffuseVal = GetHalfLambertDiffuse(worldNormal);
\t\t\t\t\t
//The world space normal of the normal map, calculate the highlight angle
half specVal = GetBlinnPhongSpec(i. worldPos, worldNormal);
half3 specCol = _specColor.rgb * specVal *_specIntensity;

// final color = ambient color + diffuse color + specular color
half3 finalCol = UNITY_LIGHTMODEL_AMBIENT.rgb * _ambientIntensity + col.rgb*diffuseVal + specCol;
return half4(finalCol,1);
}
ENDHLSL
}
}
}

2. Points to note

1. Import core.hlsl and Lighting.hlsl, because you need to use the space conversion method and get the lighting direction
2. VertexNormalInputs structure and GetVertexNormalInputs method.
Similar to the GetVertexPositionInputs method used for vertex conversion, the core also provides the method GetVertexNormalInputs for converting normals, which returns a structure that contains the normal, tangent and bitangent of the world space.
I happen to use all three of them here, so I don’t need to calculate them one by one by using this method directly. If you don’t need all the data, you can calculate one of them separately to save some calculation

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 method
UnpackNormalScale is a method provided in Packing.hlsl. Since packing has been referenced in core.hlsl, we can use it directly.

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. About TBN matrix
In order to explain the principle before, I took the trouble to set up a TBN matrix to calculate. In fact, if I use the TransformTangentToWorld method to convert, the process of converting the TBN matrix can be simplified as

half3 worldNormal = TransformTangentToWorld(normalVal, half3x3(i.worldTangent.xyz, i.worldBitangent, i.worldNormal));