Hugo Elias
何咏
译
声明:本文原文由Hugo Elias
撰写,由何咏
翻译。本文仅供学习交流之用。
任何人未经本人同意不得私自转载,任何人不得将本文用于任何商业活动。
简
介:这篇文章是一个经典的辐射度算法的教程,详细的讲述了如何通过辐射度算法为静态场景计算光照贴图。这也是大多数游戏所采用的技术。现在很多的论文和书
籍都讨论了如何在实时渲染中应用光照贴图来产生逼真的光照效果,然而他们主要着重于如何组织光照贴图。而光照贴图究竟是怎样计算出来的,也就是全局照明算
法,却极少有资料进行详细的解释。我在网上搜索到这篇文章,看了之后受益匪浅,于是决定将它翻译出来,让更多的人了解这方面的知识。如果对这篇文章有不理
解的地方,可以联系作者,也可以和我共同讨论(
我的网站:http://program.stedu.net
)
。如果翻译有错漏,也敬请指正和谅解,因为这毕竟是本人第一次翻译文章。如果你的英文水平不错,建议直接看原文:
单击这里
光照和阴影投射算法可以大致地分为两大类:直接照明和全局照明。许多人都会对前者较为熟悉,同时也了解它所带来的问题。这篇文章将首先简要地介绍两种方法,然后将深入地研究一种全局照明算法,这就是辐射度。
直接照明
直接照明是一个被老式渲染引擎(
如3D Studio
、POV
等)
所采用的主要光照方法。一个场景由两种动态物体组成:普通物件和光源。光源在不被其他物件遮挡的情况下向某些物件投射光线,若光源被其他物体遮挡,则会留下阴影。
在这种思想之下有许多方法来产生阴影,如Shadow Volume(
阴影体), Z
缓冲方法,光线追踪等等。但由于它们都采用一个普遍的原则,因此这些方法都有同样的问题,而且都需要捏造一些东西来解决这些问题。
直接照明的优缺点:
|
需
要考虑的最重要的问题是,由于这些方法会产生超越真实的图像,他们只能处理只有点光源的场景,而且场景中的物体都能做到完美地反射和漫反射。现在,除非你
是某种富裕的白痴,你的房子可能并不是装满了完全有光泽的球体和点状的光源。事实上,除非你生活在完全不同的物理背景下的一个宇宙空间,你的房间是不可能
出现任何超级锐利的阴影的。
人们宣称光线追踪器和其他渲染器
能够产生照片级
的真实效果是一件非常自然的事情。但想象如果有人拿一张普通光线追踪(这种渲染方法类似经典OpenGL
光栅和光照渲染方法)的图片给你看,然后声称它是一张照片,你可能会回敬他是一个瞎子或者骗子。
同时也应该注意到,在真实世界里,我们仍然能看到不被直接照亮的物体。阴影永远都不是全黑的。直接照明的渲染器
试图通过加入环境光来解决这样的问题。这样一来所有的物体都接受到一个最小的普遍直接照明值。
全局照明
全局照明方法试图解决由光线追踪所带来的一些问题。一个光线追踪器往往模拟光线在遇到漫反射表面时只折射一次,而全局照明渲染器
模拟光线在场景中的多次反射。在光线追踪算法里,场景中的每个物体都必须被某个光源照亮才可见,而在全局照明中,这个物体可能只是简单的被它周围的物体所照亮。很快就会解释为什么这一点很重要。
全局照明的优缺点
由全局照明方法产生的图片看起来真正让人信服。这些方法独自成为一个联盟,让那些老式渲染器
艰苦地渲染一些悲哀的卡通。但是,而且是一个巨大的“
但是”
:但是
它们更加地慢。正像你可能离开你的光线追踪渲染器一
整天,然后回来看着它产生地图像激动地发抖,在这儿也一样。
|
优点 |
缺点 |
辐射度算法: |
- |
- |
蒙特卡罗法: |
- |
- |
用直接照明照亮一个简单的场景
|
|
|
|
用全局照明照亮这个简单的场景
|
|
辐射度渲染器
的工作原理
清空你脑子里任何你所知道的正常的光照渲染方法。你之前的经验可能会完全地转移你的注意力。
我想询问一个在阴影方面的专家,他会向你解释所有他们所知道的关于这个学科的东西。我的专家是在我面前的一小片墙上的油漆。
Hugo:
"
为什么你在阴影当中,而你身边的那一片跟你很相像的油漆却在光亮之中?"
油漆:
"
你什么意思?"
Hugo:
"
你是怎么知道你什么时候应该在阴影之中,什么时候不在?
你知道哪些阴影投射算法?你只是一些油漆而已啊。"
油漆:
"
听着,伙计。我不知道你在说什么。我的任务很简单:
任何击中我的光线,我把它分散开去。"
Hugo:
"
任何光线?"
油漆:
"
是的。任何光线。我没有任何偏好。"
因此你应该知道了。这就是辐射度算法的基本前提。任何击中一个表面的光都被反射回场景之中。是任何
光线。不仅仅是直接从光源来的光线。任何光线。这就是真实世界中的油漆是怎么想的,这就是辐射度算法的工作机制。
在接下来的文章中,我将详细讲解怎样制作你自己的会说话的油漆。
这样,辐射度渲染器
背
后的基本原则就是移除对物体和光源的划分。现在,你可以认为所有的东西都是一个潜在的光源。任何可见的东西不是辐射光线,就是反射光线。总之,它是一个光
的来源,一个光源。一切周围你能看到的东西都是光源。这样,当我们考虑场景中的某一部分要接受多少光强时,我们必须注意把所有的可见物体发出的光线加起
来。
基本前提:
1:
光源和普通物体之间没有区别。
2:
场景中的一个表面被它周围的所有可见的表面所照亮。
现在你掌握这个总要的思想。我将带你经历一次为场景计算辐射度光照的全过程。
|
一个简单的场景
|
|
|
|
|
|
|
|
一个面片的视角
|
|
一个较低处的面片的视角
|
|
墙拄上
|
|
整个房间的光照:
|
|
在第一次遍历之后面片的视角
|
|
整个房间的光照:
|
|
整个房间的光照:第三次遍历
|
|
|
第四次遍历 |
第十六次遍历 |
更加详细的算法描述:
面片
辐射光强(Emmision)
尽管我曾说过我们应该认为光源和普通物体是一样的,但场景中显然要有光发出的源头。在真实世界中,一些物体会辐射出光线,但有些不会。并且所有的物体会吸收某些波段的光。我们必须有某种方法区分出场景中那些能够辐射光线的物体。我们在辐射度算法中通过辐射光强来表 述这一点
。我们认为,所有的面片都会辐射出光强,然而大多数面片辐射出的光强为0
。这个面片的属性称为辐射光强(Emmision)
。
反射率(Reflectance)
当光线击中表面时,一些光线被吸收并且转化为热能(
我们可以忽略这一点)
,剩下的则被反射开去。我们称反射出去的光强比例为反射率(Reflectance)
。
入射和出射光强(incident and excident lights)
在每一次遍历的过程中,记录另外两个东西是有必要的:
有多少光强抵达一个面片,有多少光强因反射而离开面片。我们把它们称为入射光强和出射光强。出射光强是面片对外
表现的属性。当我们观看某个面片时,其实是面片的出射光被我们看见了。
incident_light( 入射光强) = sum of all light that a patch can see
excident_light (出射光强) = (incident_light *reflectance ) + emmision
面片的数据结构
既然我们了解了一个面片的所有必要属性,我们就应该定义面
片的数据结构了。稍后,我将解释这四个变量的细节。
structure
PATCH
vec4
emmision
float
reflectance
vec4
incident
vec4
excident
end
structure
我已经讲解了算法的基础,下面将再次使用伪代码的形式加以讲解,让它更加具体。很显然这还是在一个较高的层次上,但我会在后面讲述更多的细节。
辐射度算法 伪代码:
级别 1
load scene
divide each surface into roughly equal sized patches
initialise_patches:
for each Patch
in the scene
if this patch
is a light then
patch.emmision
= some amount of light
else
patch.emmision
= black
end if
patch.excident
= patch.emmision
end Patch
loop
Passes_Loop:
each patch collects light from the scene
for each Patch
in the scene
render the scene from the point of view of this patch
patch.incident
= sum of incident light in rendering
end Patch
loop
calculate excident light from each patch:
for each Patch
in the scene