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个球,从左到右其粗糙度越来越大,从上到下其金属度越来越大。可以看到,随着粗糙度的增加,高光汇聚的亮度区域会越来越小;而随着金属度的增加,漫反射的部分也会越来越少。

原文地址:https://www.cnblogs.com/wickedpriest/p/13513244.html