Unity的URP下法线计算

回到目录

大家好,我是阿赵。
之前写过发现贴图的计算方法,可以回顾一下:
法线贴图的计算方式

这里写一个HLSL的版本,再顺便说一下一些差异的地方

一、完整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("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));

猜你喜欢

转载自blog.csdn.net/liweizhao/article/details/130837118