`

[转]辐射度算法(二) - GameRes.com

    博客分类:
  • 3D
阅读更多

http://dev.gameres.com/Program/Visual/3D/Radiosity_Translation.htm

 

修正半立方体图像

    这是一个三个同样大小的球体的视图。以90°的透视视角渲染,三个球距摄像机的距离相同,但由于透视变换的属性,在视图两边的物体被拉伸而占据了比中间的物体更大的屏幕面积。

    如果这是半立方体正中间的图像,且三个球体都是光源,那么在图像边缘的物体投射到面片上的光线就会偏多。这会导致精确性,因此我们必须修正这个问题。

    如果你想用半立方体来计算总共的入射光强,并且仅将半立方体中的像素值都加起来,那些处在图像边缘的物体就会得到一个不公平的权重。这会向面片投射更多的光线。

    为了弥补这一点,将图片边缘的像素变暗是有必要的。这样才能让所有的物体均匀地向面片投射光线。不管它们位于图像的那些位置,我不想完整地解释为什么,只想 告诉你这是怎样做的。

    半立方体表面的像素应乘以摄影机方向和光线入射方向之间的夹角的余弦值。

     左边的贴图用来弥补这个失真。

兰伯特的余弦定律

    任何初学计算机图形学的人都应该知道兰伯特的余弦定律:表面的亮度正比于表面法线和光源方向的夹角的余弦值。因此,我们在这里也应该应用这个定律。这只是简单地将半立方体图像与相关系数相乘。

    左边是一张应用了余弦定律的贴图。白色代表1.0,黑色代表0.0

两者叠加:乘法贴图

现在注意了,这一点非常重要

    将两个贴图相乘得到了这个贴图。这个贴图对于产生精确的辐射度解决方案是必要的。它用来调节透视投影带来的失真,也包括了兰伯特的余弦定律。

    创建了这个贴图之后,正中间的值应该是1.0,四周角落的值应该是0.0。在它可以使用之前,这个贴图必须被单位化。

    也就是说,贴图中所有的像素值之和应为1.0。 方法如下:

·         对乘法贴图中所有的像素求和

·         将每个像素的值除以这个和.

    现在,贴图中心的像素值应远小于1.0

计算入射光强

    这个过程在场景中选取一个点(通常是一个面片),以及改点所在表面的法向量,然后计算所有到达该点的光强。

    首先,算法使用RenderView函数渲染半立方体的5个面。这个过程的参数包括一个点,描述了摄影机应放在哪里,以及一个向量,描述了摄影机正前方向,还有一个参数告诉这个过程要渲染半立方体的哪个面。这5张图片存储在hemicube的结构里,记为H(下图的左列)

    一旦半立方体H被渲染完毕,它就与乘法贴图M相乘(下图中间列)。结果存储在半立方体R(下图右列)

    之后,R中的所有像素值相加后除以半立方体的像素总数,这就得到了该点的入射光强。

procedure Calc_Incident_Light(point: P, vector: N

    light TotalLight
    hemicube H, R, M
    H = empty
    M = Multiplier Hemicube
    R = empty

    div = sum of pixels in M

    camera C
    C.lens = P

    C.direction = N
    H.front = RenderView(C, N, Full_View)

    C.direction = N rotated 90
° down
    H.down = RenderView(C, N, Top_Half)

    C.direction = N rotated 90
° up
    H.up = RenderView(C, N, Bottom_Half)

    C.direction = N rotated 90
° left
    H.left = RenderView(C, N, Right_Half)

    C.direction = N rotated 90
° right
    H.right = RenderView(C, N, Left_Half)

    multiply all pixels in H by corresponding
    pixels in M, storing the results in R

    TotalLight = black

    loop p through each pixel in R
      add p to TotalLight
    end loop
   
    divide TotalLight by div

    return TotalLight
  end procedure

对伪代码中的变量类型的说明

light: 用于存储光照强度,如:

  structure light
    float Red
    float Green
    float Blue
  end structure

hemicube: 用于存储从某一点所观察到的场景。一个半立方体应包含5个图片,如之前所说明的那样,每个像素的类型都是light。对于乘法半立方体来说,所存储的并不是一个光照强度值,而是一些小于1.0乘法因子。之前已经说明。

  structure hemicube
    image front
    image up
    image down
    image left
    image right
  end structure

camera: 如:

  structure camera
    point  lens
    vector direction
  end structure

增加解决方案的精确度

    你可能会自己想到,这种鬼东西似乎要很多的渲染过程。做这些东西使得处理器处在高强度状态。你当然是正确的。基本上你不得不渲染几千次带有纹理的场景。

    所幸的是,这是一个自从黎明破晓的时候人们就在研究的问题了。自从光栅显示器诞生的那一刻起,自从那个时候就有了关于如何快速渲染带有纹理的场景的许多工作。我不会在这一方面走得太深,我确实不是一个最具资格的人来讨论如何优化渲染过程。我自己的渲染器是如此的慢以致于你会用诅咒的语言来描述它。算法本身很适合用3D硬件来加速,可是你必须做 一些额外的前期准备工作来让硬件渲染32位的纹理。

    我即将讨论的速度优化方法不会关心具体的加速半立方体的渲染方法,但是会讨论如何减少半立方体的渲染次数。你会,也理应会注意到光照贴图看起来呈现一种低分辨率的块状,但不要怕,它们的分辨率可以根据你的需要进行调节。

    看一些左边用红线标出的表面。光照效果基本上十分简单,有一个较亮的区域,还有一个不太亮的区域,两者之间有一条相当锐利的界线。要减少边缘的锐利程度,你一般情况下需要一个更高分辨率的光照贴图,因此必须渲染更多的半立方体。但是似乎并不 值得为那些较黑或较亮的区域计算过多的半立方体,处在这两个区域之中的面片的颜色几乎是一致的。但是在锐利的边缘附近多渲染一些半立方体会更加有价值,而对那些处在亮或暗区域之中的面片则不需要过于细分。

    这是十分简单的。我即将讲述的算法将渲染少量的半立方体均匀地覆盖在表面上,然后在靠近边缘的区域渲染更多的半立方体,对于剩下的光照贴图纹素,仅用线性插值来填充。

 

 

算法: 左下角你可以看见正在被创建的光照贴图。在它旁边,你能看到有些像素通过计算半立方体来确定,而有些通过线性插值来决定。

 

1:使用半立方体为每4个像素确定一个值. (左图红色的点)

这些像素在右图用表示.

2: 遍历1: 检查相邻两个之间的值的差。如果这个差大于某个阈值,则为像素(左图绿色区域)单独渲染半立方体。否则像素的值由插值决定。

3: 遍历2: 检查位于四个像素中心的像素 。如果相邻的两个像素差别太大,为这个像素单独渲染半立方体,否则使用线性插值决定像素的颜色值。

4: 遍历 1: 如同第二步,只是空间缩小一半。

5: 遍历 2: 如同第三步,只是空间缩小一半。

    你应该能够看到,在左边的图中,大多数光照贴图像素都是通过线性插值决定的。事实上,对于一个由1769个象素的光照贴图来说,仅有563个像素是通过渲染半立方体来决定的。而另外1206个像素是通过线性插值决定的。现在,由于渲染一个 半立方体需要非常长的时间,比起几乎不花费时间的线性插值,这个方法是速度提升了大约60%!

    至此,这个方法还不是完美的。它偶尔会错过光照贴图上一些细节。但在大多数情况下它的结果是非常好的。有个简单的方法来捕获微小的细节,但我把它留给你自己去思考。

    以下是伪代码,注释就不翻译了。

####  CODE EDITING IN PROGRESS - BIT MESSY STILL ####
 
 float ratio2(float a, float b)
 {
     if ((a==0) && (b==0))    return 1.0;
     if ((a==0) || (b==0))    return 0.0;
 
     if (a>b)    return b/a;
     else        return a/b;
 }
 
 float ratio4(float a, float b, float c, float d) 
 {
     float q1 = ratio2(a,b);
     float q2 = ratio2(c,d);
 
     if (q1<q2)    return q1;
     else          return q2;
 }
 
 
 procedure CalcLightMap()
 
 vector  normal = LightMap.Surface_Normal
 float   Xres   = LightMap.X_resolution
 float   Yres   = LightMap.Y_resolution
 point3D SamplePoint
 light   I1, I2, I3, I4
 
 Accuracy = Some value greater than 0.0, and less than 1.0.  
            Higher values give a better quality Light Map (and a slower render).
            0.5 is ok for the first passes of the renderer.
            0.98 is good for the final pass.
 
 Spacing = 4     Higher values of Spacing give a slightly faster render, but
                 will be more likely to miss fine details. I find that 4 is
                 a pretty reasonable compromise. 
 
 
 // 1: Initially, calculate an even grid of pixels across the Light Map.
 // For each pixel calculate the 3D coordinates of the centre of the patch that
 // corresponds to this pixel. Render a hemicube at that point, and add up
 // the incident light. Write that value into the Light Map.
 // The spacing in this grid is fixed. The code only comes here once per Light
 // Map, per render pass. 
 
 for (y=0; y<Yres; y+=Spacing)
     for (x=0; x<Xres; x+=Spacing)
     {
         SamplePoint = Calculate coordinates of centre of patch
         incidentLight = Calc_Incident_Light(SamplePoint, normal)
         LightMap[x, y] = incidentLight
     }
 
 // return here when another pass is required
 Passes_Loop:
     threshold = pow(Accuracy, Spacing)
 
 
     // 2: Part 1.
     HalfSpacing = Spacing/2;
     for (y=HalfSpacing; y<=Yres+HalfSpacing; y+=Spacing)
     {
         for (x=HalfSpacing; x<=Xres+HalfSpacing; x+=Spacing)
         {
             // Calculate the inbetween pixels, whose neighbours 
               are above and below this pixel
             if (x<Xres)    // Don't go off the edge of the Light Map now
             {
                 x1 = x
                 y1 = y-HalfSpacing
 
                 // Read the 2 (left and right) neighbours from the Light Map
                 I1 = LightMap[x1+HalfSpacing, y1]
                 I2 = LightMap[x1-HalfSpacing, y1]
 
                 // If the neighbours are very similar, then just interpolate.
                 if ( (ratio2(I1.R,I2.R) > threshold) &&
                      (ratio2(I1.G,I2.G) > threshold) &&
                      (ratio2(I1.B,I2.B) > threshold) )
                 {
                     incidentLight.R = (I1.R+I2.R) * 0.5
                     incidentLight.G = (I1.G+I2.G) * 0.5
                     incidentLight.B = (I1.B+I2.B) * 0.5
                     LightMap[x1, y1] = incidentLight
                 }
                 // Otherwise go to the effort of rendering a hemicube, 
                       and adding it all up.
                 else
                 {
                     SamplePoint = Calculate coordinates of centre of patch
                     incidentLight = Calc_Incident_Light(SamplePoint, normal)
                     LightMap[x1, y1] = incidentLight
                 }
             }
             
 
             // Calculate the inbetween pixels, whose neighbours are left and 
               right of this pixel
             if (y<Yres)    // Don't go off the edge of the Light Map now
             {
                 x1 = x-HalfSpacing
                 y1 = y
              
                 // Read the 2 (up and down) neighbours from the Light Map
                 I1 = LightMap[x1,y1-HalfSpacing];
                 I2 = LightMap[x1,y1+HalfSpacing];
 
                 // If the neighbours are very similar, then just interpolate.
                 if ( (ratio2(I1.R,I2.R) > threshold) &&
                      (ratio2(I1.G,I2.G) > threshold) &&
                      (ratio2(I1.B,I2.B) > threshold) )
                 {
                     incidentLight.R = (I1.R+I2.R) * 0.5
                     incidentLight.G = (I1.G+I2.G) * 0.5
                     incidentLight.B = (I1.B+I2.B) * 0.5
                     LightMap[x1,y1] = incidentLight
                 }
                 // Otherwise go to the effort of rendering a hemicube, 
                       and adding it all up.
                 else
                 {
                     SamplePoint = Calculate coordinates of centre of patch
                     incidentLight = Calc_Incident_Light(SamplePoint, normal)
                     LightMap[x1, y1] = incidentLight
                 }
 
             }//end if
 
         }//end x loop
     }//end y loop
 
 
 
     // 3: Part 2
     // Calculate the pixels, whose neighbours are on all 4 sides of this pixel
    
     for (y=HalfSpacing; y<=(Yres-HalfSpacing); y+=Spacing)
     {
         for (x=HalfSpacing; x<=(Xres-HalfSpacing); x+=Spacing)
         {
             I1 = LightMap[x, y-HalfSpacing]
             I2 = LightMap[x, y+HalfSpacing]
             I3 = LightMap[x-HalfSpacing, y]
             I4 = LightMap[x+HalfSpacing, y]
 
             if ( (ratio4(I1.R,I2.R,I3.R,I4.R) > threshold) &&
                  (ratio4(I1.G,I2.G,I3.G,I4.G) > threshold) &&
                  (ratio4(I1.B,I2.B,I3.B,I4.B) > threshold) )
             {
                 incidentLight.R = (I1.R + I2.R + I3.R + I4.R) * 0.25
                 incidentLight.G = (I1.G + I2.G + I3.G + I4.G) * 0.25
                 incidentLight.B = (I1.B + I2.B + I3.B + I4.B) * 0.25
                 LightMap[x,y] = incidentLight
             }
             else
             {
                 SamplePoint = Calculate coordinates of centre of patch
                 incidentLight = Calc_Incident_Light(SamplePoint, normal)
                 LightMap[x, y] = incidentLight;
             }
         }
     }
 
 
     Spacing = Spacing / 2
     Stop if Spacing = 1, otherwise go to Passes_Loop

点光源

     人们普遍认为辐射度算法不能很好地处理点光源。从某种程度上讲确实如此,但在真实场景中出现点光源几乎是不可能的。

    我试过向场景增加点状物体作为光源,使它作为粒子像素(Wu-Pixel)被渲染。在渲染半立方体时,它们作为一个明亮的像素出现在渲染出来的图片上,因而向面片投射闪耀的光。它运行得基本正确,但是渲染出来的图像 会出现无法令人接受的假相。右图所示的场景被三个点状聚光灯所照亮,其中的两个光源位于柱子的背后,还有一盏光源位于图片左上角附近,方向指向照相机。场景从这个角度看起来良好,但如果摄影机来回移动,就会出现令人厌恶的假相。

 

    你可以看到,在下方的图片里,出现了三条暗线。这看起来似乎是因为点光源在靠近半立方体边缘的地方就消失了。可能如果我的数学好些的话就不会这么糟糕,但我想就算那样也还是会有引人注意的像。

    因此,与其将点光源渲染到半立方体中,你不如使用光线追踪,在面片和点光源之间投射光线。

分享到:
评论

相关推荐

    遗传算法解决TSP问题.rar

    /* 用遗传算法(GA)解决TSP(旅行商)问题 ...Blog: blog.gameres.com/show.asp?BlogID=1450&column=0 E-mail: starsftk@yahoo.com.cn ps:初学遗传算法,很多都不懂,程序还有很多不足,若你改进了别忘了告诉我 */

    GIF文档 - GameRes_com

    GIF文档 - 中文 详细 全面 希望对大家有帮助 GIF文档 - 中文 详细 全面 希望对大家有帮助 GIF文档 - 中文 详细 全面 希望对大家有帮助 GIF文档 - 中文 详细 全面 希望对大家有帮助

    一种简单、快速、高效的多边形减面算法

    一种简单、快速、高效的多边形减面算法 - GameRes_com.mht 看看

    游戏策划之路-GameRes文档中心-游戏制作文章、游.doc

    - **售后服务**:解决玩家遇到的问题,提供技术支持和更新,维护游戏社区的活跃度。 ### 第三章:游戏的策划工作如何进行 3.1部分详细讨论了游戏策划的核心任务,包括确定游戏的题材、类型和风格: - **游戏题材*...

    Windows游戏编程快速入门 - GameRes文档中心- 游戏制作文章、游戏开发 ....doc

    《Windows游戏编程快速入门》是面向初学者的一篇教程,旨在引导读者踏入游戏开发的世界。本文档主要通过实际操作步骤,介绍了如何使用Visual Studio 2003 .NET来创建一个简单的Windows游戏项目,以此来教授基本的...

    sef.rar_sef_sef软件_图形图像处理

    - `计算几何算法概览 - GameRes_com.htm`:这可能是一个网页文档,提供计算几何的基本概念、常用算法的概述,以及在游戏开发中的应用。 - `e913ACM.pps`:可能是PowerPoint演示文稿,详细介绍了ACM竞赛中的计算...

    泡泡堂单机版[模拟] 源代码

    - 2ccc.com.nfo:NFO文件通常包含有关软件或分享内容的作者信息和版权声明。 - 开发日志.txt:记录了项目开发过程中的笔记、进度和问题。 - res 文件夹:可能包含游戏的其他资源文件,如图片、声音等。 - Map 文件夹...

    游戏脚本系统的demo

    游戏脚本系统是一种在游戏中实现动态行为和交互的关键技术,它允许开发者通过编程语言来控制游戏逻辑,而无需深入到游戏引擎的核心代码。"游戏脚本系统的demo"是针对这一主题的一个示例,提供了实践操作的经验,尽管...

    赫赫大名的A*寻路算法(vb.net版本)

    在网上看到一篇A*寻路算法的译文 http://data.gameres.com/message.asp?TopicID=25439 按此原理写了以下程序另外补充:1.此算法不是最短路径算法. 2.在实际应用中肯定还需要优化,以适合具体游戏. 3.(vb.net2005测试...

    VC版重装机兵(MetalMax)(游戏+源码)

    作品发布:http://data.gameres.com/showmessage.asp?TopicID=163262 如下为作品相关信息,如有转载,请详细标明如下相关信息,以示尊重!! ==================作品信息================== 名称:MetalMax 作者:...

    编程新手真言,转自gameres

    编程新手真言,转自gameres

    游戏中Windows中文输入法的处理类

    代码说明 ---------------------------------------------------- 这是一个Win32的窗口应用程序工程。 用于调试与游戏环境类似的非图象部分...该程序参考了www.gameres.com中的一个输入法例程,在此对原作者表示感谢。

    huluwa.rar_GBGM.m_c++ 锁_huluwa官网_www. huluwa .com

    本压缩包文件"**huluwa.rar_GBGM.m_c++ 锁_huluwa官网_www.huluwa.com**"可能与一个名为"Huluwa"的游戏或软件相关,它似乎使用了C++语言,并涉及到了互斥锁(Mutex)的概念。"GBGM.m"可能是项目中的一个模块或者类名...

    50个论坛地址

    - **GameRes**(http://www.gameres.com/): 游戏开发资源和技术交流的论坛。 - **17173**(http://www.17173.com/): 以游戏资讯为主的论坛,但也包含一定的游戏开发内容。 - **52RD**(http://www.52rd.com/): ...

    Parallel World 3D场景漫游

    《Parallel World》为网友dongch007原创的3D场景漫游示例Demo,内含天空、陆地、湖泊、植被、建筑、角色动画、UI及场景特效等诸多场景必备元素,...原帖地址:http://data.gameres.com/showmessage.asp?TopicID=131818

    Dx9 SDK DirectShow开发

    - www.pudn.com.txt:这个文件可能是从网站pudn.com下载资源的说明,可能提到了获取更多DirectShow开发相关资源的链接或信息。 - Include:这个目录应该包含了DirectX 9 SDK的头文件,供你的项目引用。 - Lib:这个...

    正则表达式解析库

    String email = "example@example.com"; Matcher matcher = pattern.matcher(email); boolean isValid = matcher.matches(); ``` 在提供的压缩包文件中,我们可以看到几个可能与正则表达式解析库相关的文件: 1. `...

    Pro.OGRE.3D.Programming 中文PDF版本

    - **论坛**:[http://bbs.gameres.com/showforum.asp?forumid=99](http://bbs.gameres.com/showforum.asp?forumid=99) 以上资源为学习OGRE 3D提供了丰富的参考资料和技术支持。 #### 六、总结 《Pro.OGRE.3D....

Global site tag (gtag.js) - Google Analytics