About Realtime Radiosity
前言:实时辐射度Realtime Radiosity是一种Shading着色方法,模拟了热力学能量传输,最终在场景中达到平衡的过程。这种方法首先在上世纪50年代应用于航天飞行器表面与外界的热交换工程模拟上,随后于1984年由日本Fukuyama大学、Hiroshima大学,以及Cornell康奈尔大学的研究者应用于计算机图形学。它可以很好的生成具有全局光照(Gobal Illumination)的效果的场景。但是它与RayTracing光线跟踪的方式不同,它不包括Visible可视测试算法,仅仅是一种基于Path路径的Shading着色方法。我总结了一下实时辐射度Realtime Radiosity的方法,具体的实现可以参考文中提到的论文。
Radiosity是模拟能量在场景中传递,最终达到平衡的一种Shading着色方法。算法方面最经典的是Cohen M.F与Wallace J.R.所著的《Radiosity and realistic image synthesis》一书,也就是Progressive Refinement Radiosity逐步求精的辐射度方法。但是在实时领域,由于GPU编程模型的限制 —— 首先,GPU是
光栅化硬件,而不是以光线跟踪的方式工作的;其次,不提供随机内存访问,没有指针,无法遍历场景;最后,不可以写入随机内存位置,一切的一切都是以Stream流式进行的。虽然说NVIDIA CUDA的出现即将改变这种状况,在GH2007上由来自XXX大学的XXX发表的一篇题为《Scan Primitivies On GPU》就是极大的进步,但是在实时渲染领域,无论是硬件还是软件的发展依旧还有很长的路要走。
目前实时辐射度的方法有两种,第一种是集中在最早由Universität Kaiserslautern的Alexander Keller展示的基于虚拟光源Virtual Point LIghts VPL的方法,又被称作立即辐射度Instant Radiosity。具体概括为这样的一个过程:从场景内真实的光源位置开始,根据光源的类型比如具体是点光源还是方向光等,生成一个方向分布,然后在场景中投射,命中到的位置就可以作为是虚拟的新光源。接下来在渲染的过程中,使用这些数目很大的光源迭代进行整体光照计算。
目前这个方向上最新的论文是《Incremental Instant Radiosity for Real-Time Indirect Illumination》。它的过程主要是:1、生成“合适”的VPLs,根据要求剔除或者增加VPLs;2、渲染场景到G-Buffer;3、将G-Buffer分割为Tiles;4、使用VPLs的子集对Tiles进行着色;5、将Tiles完整的拼凑回去进行过滤操作。
其实这种方法的思路很简单,真实的场景中许多都是Diffuse漫反射物体,比如墙壁、地板等。而Diffuse漫反射物体之间的能量传递最多为6次就已经相当精确了,剩下的没有进行过的迭代过程对场景效果几乎没有影响。所以说,我们可以先利用Local Ilumination的方法,将光源射线第一次命中的点当作虚拟光源,使用这个虚拟光源进行光照计算,当然可以生成更多的光源,但是那样以来就完全成了蒙特卡罗光线跟踪了。比如一个简单的室内场景,当虚拟光源的数目达到64个的时候,画面效果已经与光线跟踪软阴影以及辐射度方法没有多少差别,最最重要的,一切的一切都是实时生成的。
这种方式的缺点在于,一切都是以VPLs为中心,导致VPLs的分布极大的影响了最终全局的光照效果。生成VPLs的方法是在网格或者是圆盘、球面上采样,包括Quasi Monte Carlo、Poission Disk、Halton Points,但是无论如何,都要生成一个“符合场景”的“合适”点集。如果光源是动态的,比如是玩家手中的火把,或者是登陆舰船腹下不断变换方向的探照灯,都需要动态的生成VPLs,同时进行剔除与增加的管理操作。换句话说,美工完全可以做到,通过观察真实场景的光照分布,我们就可以得知那些地方设置VPLs可以近似的模拟全局光照。
在目前的交互式程序比如游戏,由于计算性能还无法达到要求,所以还只能使用Trick去模拟全局光照、Indirect Illumination间接光照的效果。比如S.T.A.L.K.E.R游戏就运用了这个思想,使用了Defered Shading延迟着色,“Many lights are needed only to mimic a global illumination-style look”。在gamedev.net上有个叫做51mon的网友展示了他自己的《Deferred Rendered Radiosity from First Person Perspective》技术,也是属于延迟着色与VPLs的合体。具体的实现过程为:1、Create the distribution table;2、Create the G-Buffer;3、Mip-map filter the G-Buffer;4、Fill the auxilery buffer whit instance data, the data is obtained from the mip-map chain and the distribution table directs from where to take the samples;5、Render the reflection lights with hardware instancing;6、Compose the final image。它所使用的技术源自于《Splatting Indirect Illumination》这篇Paper。但是这篇Paper,又大部分借鉴了《Reflective Shadow Maps》中的技术。
第二种就是非常经典的基于逐步求精的实时辐射度方法,《GPU GEMS2》中有改进版本,原文于2004年发表,我也和原文作者交流过。不必揣摩那高深的线性代数知识,那都是唬人的。它的思想很简单,通过不断的在场景中迭代计算接受与发射元素之间的辐射度传递,每个多边形都具有两张图,RadiosityMap辐射图,也可以简单的理解为是物体表面的光照图,以及ResidualMap残余能量图,也就是该元素能够发射多少能量的贴图。在实现的时候之所以必须使用"Triangles Soup"而没有使用索引化的三角形是因为,需要计算面积,需要准确的定位每个三角形的ID,这样就必须把每个三角形分开,所以就需要de-normalize,这种准备工作在OOCSX模型简化中也使用过。
HemiSphereProjection的效果、meshID的效果类似于以下两张图:
/*Vertex Shader*/
attribute vec4 TEXCOORD0;
uniform vec2 NearFar;
varying vec2 MeshTriID;
void main()
{
vec3 CameraPos = vec3(gl_ModelViewMatrix*vec4(gl_Vertex.xyz,1.0));
vec3 HemiPt = normalize(CameraPos.xyz);
float f_minus_n = NearFar.y - NearFar.x;
vec4 ProjPos;
ProjPos.xy = HemiPt.xy * f_minus_n;
ProjPos.z = ( -2.0*CameraPos.z - NearFar.y - NearFar.x );
ProjPos.w = f_minus_n;
MeshTriID = TEXCOORD0.xy;
gl_Position = ProjPos;
}
/*Fragment Shader*/
varying vec2 MeshTriID;
void main()
{
gl_FragColor = vec4(MeshTriID, 0.0,1.0);
}
所有的伪代码如下:
de-normalize each triangle, let it be a "triangles soup"
get out all triangles' vertices coordinates
compute triangles' id, consider it as vertex attribute MeshID
compute the area of each triangle
add the result as the 4th element of each vertex instead of 1.0
get out all triangles' normal
prepare the UV coordinates which can wrap whole each mesh
create 2 floating texture, one is radiosity map, another is residual map
Get your radiosity map :
render from ShootPos, first time maybe it's a light
use vertex attribute MeshID as color , generate a ItemBuffer, use HemiSphereProjection
use hardware occlusion to determine which mesh should be rendered
render mesh to its UV texture space, consider UV coordinates as clipped screen coordinates, like this, " gl_Position = vec4(gl_TexCoord[0].st*2.0-1.0,0.0,1.0); "
if the vertex from mesh can be seen from Shooter's view, calculate radiosity,write to 2 maps ,else discard the fragment
iterate this procedure to compute each radiosity attached on each mesh
next shoots' energy directly read from its residual map, Shooter's postion is its vertex position,as mentioned in paper, mipmap & depth selection
Use your radiosity map :
render scene ( meshes ) use its radiosity map, simply use UV mapping to present the light effect on its surface
总的来说,这种方法改进了传统的离线逐步求精辐射度方法,但是它的缺点也有很多,其中最大的莫过于迭代计算需要许多重复的Pass,象素填充率要求非常高,但是最终的效果却没有那么的出色。还有就是在光照的变化区域较高的地方需要进行自适应分割采样,失去了通用性,也增加了复杂度。所以对比VPLs与Defered Shading,它只是经典过程的一种重现,并不能当作是一种。而且,它的算法本身无法处理反射、折射、刻蚀,只能使用其他的方法或者是Trick了。据悉在PS3上已经开始着手开发基于实时辐射度的引擎,而Lightsprint提供了目前市场上唯一的实时全局光照C++中间件,这里有Demo下载,至于效果还希望大家评判,我个人是觉得,根本不如Crysis、Unreal3引擎所展示的效果。