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));