Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第二十一章:环境光遮蔽(AMBIENT OCCLUSION)

本文介绍了3D游戏编程中环境光遮蔽的概念,通过光线追踪和屏幕空间环境光遮蔽(SSAO)两种方法进行探讨。详细讲解了SSAO的实现步骤,包括法线和深度值渲染、随机采样、遮挡测试及模糊处理,展示了如何在DirectX 12中应用环境光遮蔽提升3D场景的真实感。
摘要由CSDN通过智能技术生成


学习目标

  1. 熟悉环境光遮蔽的基本思路,以及通过光线跟踪的实现方法;
  2. 学习如何在屏幕坐标系下实现实时模拟的环境光遮蔽。


1 通过光线追踪实现的环境光遮蔽

在这里插入图片描述
其中一种估算点P遮蔽的方法是光线跟踪。我们随机跟踪点P半圆内的光线,然后查看和网格相交的光线。如果跟踪了N条光线,相交了h条,那么点P的遮蔽值为:
在这里插入图片描述
只有交点q到p的距离小于某个入口距离d才能贡献遮蔽估算;因为如果p和q的距离过远的话,就无法遮挡。
在这里插入图片描述
遮挡因子代表了点P有多闭塞,而为了计算为目的,我们需要知道点P能接收到多少光,我们叫它可达性(accessibility),它由遮挡值计算得到:
在这里插入图片描述
下面的代码计算了逐三角形的光线追踪,结果通过三角形三个顶点的平均值计算得到。光线的原点是三角形的中点,然后在半圆范围了随机创建射线:

void AmbientOcclusionApp::BuildVertexAmbientOcclusion(
	std::vector<Vertex::AmbientOcclusion>& vertices,
	const std::vector<UINT>& indices)
{
	UINT vcount = vertices.size();
	UINT tcount = indices.size()/3;
	std::vector<XMFLOAT3> positions(vcount);
	
	for(UINT i = 0; i < vcount; ++i)
		positions[i] = vertices[i].Pos;
		
	Octree octree;
	octree.Build(positions, indices);
	
	// For each vertex, count how many triangles contain the vertex.
	std::vector<int> vertexSharedCount(vcount);
	
	// Cast rays for each triangle, and average triangle occlusion
	// with the vertices that share this triangle.
	for(UINT i = 0; i < tcount; ++i)
	{
		UINT i0 = indices[i*3+0];
		UINT i1 = indices[i*3+1];
		UINT i2 = indices[i*3+2];
		XMVECTOR v0 = XMLoadFloat3(&vertices[i0].Pos);
		XMVECTOR v1 = XMLoadFloat3(&vertices[i1].Pos);
		XMVECTOR v2 = XMLoadFloat3(&vertices[i2].Pos);
		XMVECTOR edge0 = v1 - v0;
		XMVECTOR edge1 = v2 - v0;
		XMVECTOR normal = XMVector3Normalize(XMVector3Cross(edge0, edge1));
		XMVECTOR centroid = (v0 + v1 + v2)/3.0f;
		
		// Offset to avoid self intersection.
		centroid += 0.001f*normal;
		const int NumSampleRays = 32;
		float numUnoccluded = 0;
		
		for(int j = 0; j < NumSampleRays; ++j)
		{
			XMVECTOR randomDir = MathHelper::RandHemisphereUnitVec3(normal);
			
			// Test if the random ray intersects the scene mesh.
			//
			// TODO: Technically we should not count intersections
			// that are far away as occluding the triangle, but
			// this is OK for demo.
			if( !octree.RayOctreeIntersect(centroid, randomDir) )
			{
			 	numUnoccluded++;
			}
		}
		
		float ambientAccess = numUnoccluded / NumSampleRays;
		
		// Average with vertices that share this face.
		vertices[i0].AmbientAccess += ambientAccess;
		vertices[i1].AmbientAccess += ambientAccess;
		vertices[i2].AmbientAccess += ambientAccess;
		vertexSharedCount[i0]++;
		vertexSharedCount[i1]++;
		vertexSharedCount[i2]++;
	}
	
	// Finish average by dividing by the number of samples we added,
	// and store in the vertex attribute.
	for(UINT i = 0; i < vcount; ++i)
	{
		vertices[i].AmbientAccess /= vertexSharedCount[i];
	}
}

本Demo使用了八叉树(octree)进行射线/三角形相交检测。因为网格可能有成千上万个三角形,逐三角形进行射线检测计算量非常大,八叉树对三角形在空间上排序,这样就可以检测最有机会相交的三角形,这样就减少了相交检测。八叉树是一个空间的数据结构。
在这里插入图片描述
上图是使用上面算法实现的屏幕截图,它进行了环境光遮蔽的预计算,然后保存到顶点结构中。(场景中没有灯光)
预计算的环境光遮蔽对于静态的模型来说可以有很好的效果,甚至有一些工具(http://www.xnormal.net)来创建环境光遮蔽贴图–纹理来保存环境光遮蔽数据。但是对于运动的模型就无法实现(计算量太大)。



2 屏幕空间环境光遮蔽

屏幕空间环境光遮蔽(screen space ambient occlusion (SSAO))的策略是:对每一帧,在视景空间下,渲染法线到一个渲染目标,然后渲染深度值到深度/模板缓冲中;然后使用上面2张计算的纹理来模拟逐像素的模拟环境光遮蔽;当我们有了环境光遮蔽的纹理,我们进行正常的渲染到后置缓冲中,但是要以SSAO数据对每个像素进行环境光遮蔽处理。


2.1 渲染法线和深度值

首先我们渲染法线到屏幕尺寸,DXGI_FORMAT_R16G16B16A16_FLOAT格式的纹理中,并且在深度/模板缓冲中渲染深度值,着色器代码如下:

// Include common HLSL code.
#include “Common.hlsl”

struct VertexIn
{
	float3 PosL : POSITION;
	float3 NormalL : NORMAL;
	float2 TexC : TEXCOORD;
	float3 TangentU : TANGENT;
};

struct VertexOut
{
	float4 PosH : SV_POSITION;
	float3 NormalW : NORMAL;
	float3 TangentW : TANGENT;
	float2 TexC : TEXCOORD;
};

VertexOut VS(VertexIn vin)
{
	VertexOut vout = (VertexOut)0.0f;
	
	// Fetch the material data.
	MaterialData matData = gMaterialData[gMaterialIndex];
	
	// Assumes nonuniform sc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值