1. 程式人生 > 實用技巧 >PBR技術簡介(四):直接光照的程式碼實現

PBR技術簡介(四):直接光照的程式碼實現

之前介紹了有關PBR技術的一些理論知識,今天來講一下利用程式碼如何實現相應的光照演算法。
我們提到,我們最終要求解的其實就是這麼一個積分:

積分中kd的部分代表光照所產生的漫反射,ks的部分代表光照所產生的高光反射。如果充分考慮間接光照的效果(也就是從光源發射出光線後,不斷碰撞反射,最終進入人眼),那這個積分事實上是極難求解的,但是我們可以先暫時不考慮間接光照,只考慮直接光照的部分。那麼只需要在shader中,將所有的光源累加到該積分裡就可以了,這個相對來說還是比較好做的。
我們首先把Cook-Torrance BRDF相關的程式碼實現一下(用HLSL實現),基本上就是對著公式寫:

// 法向分佈函式 N
float DistributionGGX(float3 N, float3 H, float roughness)
{
	float pi = 3.14159265;
	float a = roughness * roughness;
	float a2 = a * a;
	float NdotH = max(dot(N, H), 0.0);
	float NdotH2 = NdotH * NdotH;

	float num = a2;
	float denom = (NdotH2 * (a2 - 1.0) + 1.0);
	denom = pi * denom * denom;

	return num / denom;
}

float GeometrySchlickGGX(float NdotV, float roughness)
{
	float r = (roughness + 1.0);
	float k = (r*r) / 8.0;

	float num = NdotV;
	float denom = NdotV * (1.0 - k) + k;

	return num / denom;
}

//幾何函式G
float GeometrySmith(float3 N, float3 V, float3 L, float roughness)
{
	float NdotV = max(dot(N, V), 0.0);
	float NdotL = max(dot(N, L), 0.0);
	float ggx2 = GeometrySchlickGGX(NdotV, roughness);
	float ggx1 = GeometrySchlickGGX(NdotL, roughness);

	return ggx1 * ggx2;
}

//菲涅爾公式F
float3 fresnelSchlick(float cosTheta, float3 F0)
{
	return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

然後具體的光照計算程式碼如下(以點光源為例,方向光原理):

void ComputePointLight(Material mat, PointLight light, float3 pos, float3 N, float3 V, float3 F0,
				   out float3 lo)
{
	float3 L = light.Position - pos;
	float distance = length(L);
	
	// Range test.
	if( distance > light.Range )
		return;
		
	// Normalize the light vector.
	L /= distance;
	float3 H = normalize(V + L);
	
	float attenuation = 1.0 / (distance * distance);
	float3 radiance = light.Diffuse.rgb * attenuation;

	// cook-torrance brdf
	float NDF = DistributionGGX(N, H, mat.roughness);
	float G = GeometrySmith(N, V, L, mat.roughness);
	float3 F = fresnelSchlick(max(dot(H, V), 0.0), F0);

	float3 kS = F;
	float3 kD = 1.0 - kS;
	kD *= (1.0 - mat.metallic);

	float3 numerator = NDF * G * F;
	float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
	float3 specular = numerator / max(denominator, 0.001);

	float pi = 3.14159265;
	float NdotL = max(dot(N, L), 0.0);
	lo = (kD * mat.albedo / pi + specular) * radiance * NdotL;
}

上述程式碼中,V就是著色位置到相機的方向,N是法向,F0是前面提到的物體和菲涅爾效應有關的屬性,lo是該光源在物體上最終產生的輻射。
材質的定義如下:

struct Material
{
	float3 albedo;
	float  roughness;
	float  metallic;
};

其中albedo可以認為是物體本身的顏色,roughness就是粗糙度,metallic則是金屬度。
PixelShader的程式碼如下所示:

float4 CustomPS(VertexOut pin,
	uniform int gPointLightCount,
	uniform int gDirLightCount,
	uniform bool gUseShadowMap,
	uniform bool gUseSSAO) : SV_Target
{

	float3 color = float3(0.0f, 0.0f, 0.0f);

	pin.NormalW = normalize(pin.NormalW);

	float3 V = normalize(gEyePosW - pin.PosW);
	
	V = normalize(V);

	// 根據金屬度計算物體的F0
        float3 F0 = float3(0.04, 0.04, 0.04);
	F0 = lerp(F0, gMaterial.albedo, gMaterial.metallic);

	float3 L0 = float3(0.0, 0.0, 0.0);

	float shadow = 1.0;
	if (gUseShadowMap)
		shadow = CalcShadowFactor(samShadow, gShadowMap, pin.ShadowPosH);

	float ambient_weight = 1.0;
	if (gUseSSAO)
	{
		pin.SSAOPosH /= pin.SSAOPosH.w;
		ambient_weight = gSSAOMap.Sample(samLinear, pin.SSAOPosH.xy, 0.0f).r;
	}

	float3 ambient = gMaterial.albedo * 0.03 * ambient_weight;
	
	//
	// Lighting.
	//

	if (gPointLightCount + gDirLightCount > 0)
	{
		[unroll]
		for (int i = 0; i < gDirLightCount; ++i)
		{
			float3 lo = float3(0.0, 0.0, 0.0);
			ComputeDirectionalLight(gMaterial, gDirLights[i], pin.NormalW, V, F0, lo);
			color += shadow * lo;
		}

		[unroll]
		for (int i = 0; i < gPointLightCount; i++)
		{
			float3 lo = float3(0.0, 0.0, 0.0);
			ComputePointLight(gMaterial, gPointLights[i], pin.PosW, pin.NormalW, V, F0, lo);
			color += shadow * lo;
		}
	}

	color += ambient;

	if (enableHDR)
	{
		float exposure = max(0.0, HDRexposure);
		color = 1.0 - exp(color * exposure);
	}

	if (gammaCorrection)
	{
		float gamma_ratio = 1.0 / 2.2;
		color = pow(color, gamma_ratio);	
	}
	return float4(color, 1.0);
}

基本計算過程就如上所示。最後展示一下不同粗糙度和金屬度下渲染結果。
圖中共有25個球,從左到右其粗糙度越來越大,從上到下其金屬度越來越大。