日历
网志分类
· 所有网志 (17)
· 私人 (5)
· 未分类 (12)
最新的评论
站内搜索
友情链接
· 我的歪酷 非非共享界

订阅 RSS

0005376

歪酷博客

xiaoyang的技术日志


Xiaoyang @ 2007-11-23 13:59

step 1: path tracing to estimate light contributions.
选择eye path tracer进行低采样率采样。
the path tracer必须使用shadow ray来sample所有的光源,因为它们事先不知道哪些光源更重要。这些shadow rays要么会交到光源(在这里就是那些重要的光源),要么交到当前场景的visually important part,所以总体来讲path tracer接触到的actual footprint of data刚好对应了模型真正重要的部分。
总结: 等于没说!
用pure path tracing算出来的图象会存在噪声,但因为这里的图象不是用来显示,而是用来计算estimate,所以噪声的影响一般不会很明显。但是有些噪声还是会以estimate的variance形式存在,而如果estimate的variance很强,就会在帧与帧之间出现明显的temporal artifacts,例如flickering(闪烁)。

step 2: constructing am importance sampling PDF
如果要得到一个无偏(unbiased)的estimate,对每个作出贡献的光源,它的PDF都应该大于0。为了达到这样的效果,需要为每个光源赋一个minimum probability,这就会导致很多光源被采样,而这些光源周围的geometry也会被touch。
如果可以忍受稍微有偏(biased)的结果,就可以为光源的重要性设置一个阈值,这样贡献值很小的光源就会被忽略。这样就可以省去大部分光源。
文章用的应该是第二种。

step 3: importance sampling during rendering
在每帧替换光源的PDF,然后用标准的importance sampling就可以了。

only the PDF for sampling the start point for the light ray had to be modified.
PDF是对每个sample point计算的,这里的start point可能指的是在bidirectional path traciing中的交点,这个交点刚好是光线的起点(只是猜测)。


上面的三个步骤可以理解为一个基本步骤,但是可以从另一个角度看这一问题。
Instant Global Illumination
Instant Global Illumination是instant radiosity的变种,它结合了interleaved sampling和a filtering step.
Instant Global Illumination用了一系列的virtual point light sources(VPLs),这些VPLs是由photon map之类的算法产生。这些VPLs被当成是normal point light sources.

对于一个复杂场景来讲,生成的VPLs只有一小部分会对当前成像起作用。前面PDF的作用也就是估算出哪些VPLs对当前成像是重要的。

因为前面path tracer用的是非常低的采样率,所以会出现三个问题。
1. estimation和importance sampling如何配置到底层的分布式计算环境下.
2. 当用非常的小sampling rates构造PDF时需要非常小心。必须要保证只有很小重要性的光源最终也能获得一些采样.
3. 需要处理temporal artifacts.

问题1:
因为分布式计算环境下是每台client计算图像的一块,为了保证所有clients合起来是一幅完整的image,每个clients上的PDF和VDLs都应该是一样的。VDLs的生成与随机数的生成是

相关的,只要保证大家的随机数是一样的,VPLs也就一样了。但PDF就有所不同,因为PDF的生成是整个光源、场景、pixels相关的,让每个client都计算一个全局的PDF是不现实的,因为要在estimation step阶段发射hundred thousand rays.所以文中采用的算法是每个client只对它当前负责的tile执行estimation,然后把estimation结果发送到一个server,由server执行对结果的combination,把全局的estimation结果发还给每个client,每个client用这个全局的结果构造PDF并用于下一帧。
这样会产生一个视点与PDF的一帧的偏差。

问题2:
没有讲述,但是讲到了如何利用temporal coherence.
1. 可以利用之前连续的几帧的importance estimation或PDF.
2. 可以用上一帧的PDF指导这一帧的importance sampling过程.
3. 可以重用之前trace过的eye rays.

问题3:
需要保证连续帧之间的VPLs是在同一位置上的。这在view-driven importance sampling的条件下仅用相同的随机数序列是无法保证的。
它这里的算法很诡异,它不是对所有的光源进行遍历,而是对每个光源计算它所特有的VPLs,然后用一个对该光源unique的随机种子计算是否放置VPL,这样一个光源的VPL是否存在只与它自身有关,而与其它光源的PDF变化无关。
但这种方法在活跃光源过多的情况下会失效。

 

 


 
Xiaoyang @ 2007-10-15 21:08

vertex shader
gl_TexCoord[0] =gl_TextureMatrix[0] * gl_MultiTexCoord0;

fragment shader
process gl_TexCoord[0]


 
Xiaoyang @ 2007-10-15 19:58

struct gl_MaterialParameters

    vec4 emission;
    vec4 ambient;
    vec4 specular;
     float shininess;
};

uniform gl_MaterialParameters gl_FrontMaterial;
uniform gl_MaterialParameters gl_BackMaterial;

in vertex shader or in fragment shader
直接访问gl_FrontMaterial和gl_BackMaterial.


 
Xiaoyang @ 2007-10-15 19:55

vertex shader
varying vec3 normal;
{
normal = normalize(gl_Normal);
}

fragment shader
varying vec3 normal;
{
// process normal;
}


 
Xiaoyang @ 2007-10-15 19:36

vertex shader
输入: gl_Color 就是用glColor3f指定的颜色
可以在vertex shader中定义四个颜色值
gl_FrontColor
gl_FrontSecondaryColor
gl_BackColor
gl_BackSecondaryColor

fragment sahder
输入: gl_Color 和 gl_SecondaryColor,分别对应前面的Front和Back对应的那两个Color和SecondaryColor.
Front和Back由片元是正面还是反面决定。

所以正确的流程是:
在vertex shader中
gl_FrontColor = gl_Color.
在fragment shader中
gl_FragColor = gl_Color.


 
Xiaoyang @ 2007-10-05 13:45

 

Accurate Direct Illumination Using Iterative Adaptive Sampling

底子为蒙特卡罗方法,外加Adaptive Sampling。
对于多光源而言,似乎现在只能做Direct Illumination,而且耗时特别长。而造成耗时长的原因可归结为大量shadow rays的计算和大量光源的illumination计算。而蒙特卡罗的优势在于只要准确估算出光源的概率函数(相对于每个待着色点),只需要用少量的光源就可以模拟出全局的光照(当然是指direct illumination所能产生的效果)。

执行步骤
1. 将最终绘制图像分为一个个8x8的小块(Block),其它仍然可按光线跟踪的方法发射投影光线,如果需要反走样,就向每个pixel发射多条射线(事实上也就是这样子的)。因为是direct illumination,所以最终需要绘制的点集是确定的,记为X.

2. 将一个Block转换为点集X,初始化一个均匀PDF(Probability Density Function)。对X中的每个点,计算光源上的某些采样点在它上面产生的光强(用到了先前估算的PDF,第一次为均匀PDF),然后就可以出现一系列的统计结果。例如,每个光源上的采样点与点集A包含的点之间的平均光强(点集A可以是每个Block的点集,也可以是每个pixel的点集);每个光源相对于点集A的可见性(可见的光线数/总光线数)。根据上面的两类统计数据就可以得到各个光源在block和pixel层次上的PDFs,然后按照一次的比例将均匀PDF,Block PDF和Pixel PDF就可能得到新一轮的PDF。

3. 估算每个像素的光强和variance(error estimate),如果error estimate符合要求,就将该像素置为已完成,光强就是求得的光强;否则,继续步骤2(初始化步骤省略)。如果所以像素都已标记为已完成,则整个Block已完成,处理下一个Block.

优化措施
1) 可见性的结果是可以重用的,每个Pass会计算一些新的samples(光源上的),这些新计算得到的可见性可以与老的可见性累加。
2) 可以将光源根据朝向分类,也可以将交点根据法向量分类,这样就只要处理对应类就可以了。
3) 可以构建光源的clusters来减少光源的数目,同时也保证对每个步骤2中的光源(实际上是光源的cluster)有足够多的可采样点。
4) 对每个pixel可以选出一些代表点来发射光线,这些点的确定可按如下步骤。一开始为每个像素选取足够多的点,然后根据一定的规则剔除掉一些点。最后的代表点包含一个坐标位置和它代表的区域面积。这个区域面积控制了采样过程中点的选取(从这可以看出,每个Pass新的采样不仅包含对光源上点的采样,也包括对点集X中点的采样)。



 
Xiaoyang @ 2007-09-27 18:24

阴影Z缓冲算法
阴影体算法

结合----->多路阴影体

使用模板缓冲作为像素计数器的阵列,4通路渲染
1) 将scene看作整个位于场景中,并用ambient light进行渲染。
2) 渲染front-facing的阴影体多边形,决定是否有物体多边形在其后面。
3) 渲染back-facing的阴影体多边形,决定是否有物体多边形在其前面。
4) 再次渲染scene,用不在阴影中的多边形覆盖第一步渲染的结果。

详细算法
1) 开启frame buffer、Z buffer与depth test.
2) 关闭光源,用ambient light渲染scene。该通路将形成最终scene中的阴影区域,而不在阴影中的区域将被第二渲染通路所覆盖.
3) 计算阴影体多边形.
4) 关闭frame buffer以及用于写入的Z buffer, 开启depth test.
5) 根据视点位于阴影体内或外将stencil buffer初始化为1或0.
6) 设置stencil function为"alwary pass",设置stencil op为"add if depth test passes".
7) 开启back facing culling
8) 渲染阴影体。该通路的作用是:如果有一个物体多边形在阴影多边形的后面,则对于每个像素,模板缓冲中的值增加。
9) 开启front facing culling
10) 设置stencil op为"decrease if depth test passes".
11) 渲染阴影体。该通路的作用是:如果有一个物体多边形在阴影多边形的前面,则对于每一个像素,模板缓冲区的值减少。当前stencil buffer对于所有位于阴影体中的像素都保持一个正值。
12) 设置stencil op为"equl to zero".
13) 渲染scene, 不在阴影中的物体就能正常照明了。

阴影算法简述:
大多是能快速计算的shadow,比较少的soft shadow。
需要较正一下以前的错误观念。以前以为点光源是无法产生soft shadow的,但事实上只要有多个点光源,一样可以生成soft shadow。而且在实际实现中,还可以用texture filtering来做平滑。

先来个简单的
1. 地面上的简单阴影
因为地面的Z轴坐标是0(假设Z轴是朝上),假设光源在无限远,那么光线方向为L = (x, y1, z1), 那么物体上的点P =  (Xp, Yp, Zp)在地面上的投影S = (Xsw, Ysw, 0)。这里面存在了一个线性变换(齐次变换)。
[Xsw, Ysw, 0, 1]T = [1, 0, 0, 0, 0, 0, 0, 0, -x1/z1, -y1/z1, 0, 0, 0, 0, 0, 1]T * [xp, yp, zp, 1]T
有了这个变换,就可以对物体的轮廓点进行投影,从而在地面上获得投影的轮廓。

经典算法
2. 多边形投影/扫描线
需要预先建立一辅助数据结构,为每个多边形确定会对其产生阴影的多边形,从而形成一组阴影对。
可以通过以点光源为球心来检测阴影对。

该算法使用一个标准扫描对话过程处理辅助数据结构,以此来确定是否有阴影投影在了扫描线找到的多边形区域。
如果没有阴影多边形(与扫描到的多边形形成了阴影对),扫描线继续。
如果存在阴影多边形,以光源为投影中心,将阴影多边形投影到当前扫描到的多边形平面,然后继续标准扫描过程,然后决定扫描到的像素点是否在阴影内。可分为三种情况:
1) 阴影多边形不覆盖生成的扫描区域(像素点的集合,扫描过程中可以得到),无阴影,扫描继续
2) 阴影多边形覆盖了扫描区域,像素在阴影中,扫描继续
3) 阴影多边形部分覆盖扫描区域,划分区域,迭代执行该过程(是指1、2、3)


3. 阴影体
阴影体是指物体阴影所掠过的不可见的空间体积,它是一个无限体状结构,由从点光源发射并穿过物体顶点的线所定义。
当上述无限体与视见体相交时,就得到了一个有限的阴影体。
计算一个物体对应的阴影体时,需要找到从光源出发得到的该物体的边界或轮廓边。轮廓边是物体上的多边形所连接的一条或更多的边界共同组成,它(们)将物体分为可割成可见与不可见两部分。简言之,它构成了物体的轮廓。
轮廓边的确定: 预处理策略来存储连接性数据。 对每一个多边形,存储所有相连的面的列表。然后将光源作为参照的视点,通过对阴影体的背面进行culling,找到那些连接被culling面和未被culling面的边界,由此确定物体的轮廓边。
有了轮廓边,根据上面的每一个顶点,就可以生成阴影体的边。
有了阴影体,我们就可以很简单地确认一个将要绘制的像素是否在阴影中。假定阴影体分为正面和背面,如果像素点在正面之前或背面之后,则它不在阴影中,如果像素点刚好落在阴影体内,那么它就在阴影中。
阴影体的使用简单来讲就是计数,计算从视点出发的射线到交点经过了多少阴影体,如果从正面进入,则计数加一;从背面出来,则计数减一。如果视点本射在阴影体内,则初始值为一,否则为零。最终,如果计数为零,则交点在阴影外,否则交点在阴影内。

阴影体可以与Z缓冲结合起来使用,也就是一开始的算法(多通道版本)。


4. 光源变换到阴影多边形的来历
以光源的位置观察物体或场景视图,因为用了隐面消除,所以绘制得到的视图都是从光源可见的多边形。然后将这些可见的多边形(可能已经被裁剪)转换回物体坐标系,与原物体结合就可以得到物体的阴影区域,存储起来就可以用了。

5. 阴影Z缓冲
就是暑假做过的那种,也叫做阴影深度图。就是保存了与光源最近的交点的深度值。真正渲染场景时比较这个深度值就可以了。

游戏中的阴影
6. 在光照贴图中加入阴影
在原有的光照贴图上,加上阴影。假定有了一个光照帖图,现在以场景中的点光源为中心构建一作用球,用以计算从点光源到光照贴图上的每个点(x,y,z)之间是否有遮挡。如有,相应点在阴影中。
实际使用时可用纹理滤波。


7. 柔和阴影
随机采样点光源就可以了。


8. 动态物体的快速阴影
将纹理贴图与帧缓冲区中的现有图像混合使用,用的是形状近似,似乎效果不是很好。


 
Xiaoyang @ 2007-09-25 23:06

白天调完代码,晚上看了部《赫兰道》,不愧是8.3分的片子,片子很好看,中间那两段唱歌真的很不错,只是一遍下来愣是没看懂。不过上网搜了一下评论,发觉也没几个人能一遍看懂的。不过真是一部好片,强烈推荐看,而且里面有长时间色情镜头!

回到宿舍才想起heroes终于出新的一季了,找到一下hdtv的版本,正在下。不过要叹一下网速,还真是慢啊。

这几天一直在coding,终于把冲突检测给加上去了。代码倒是没写几天,只是调了很长时间。还是要总结一下,以免将来犯类似错误。

1. 概念不清。
基本想法还是对的,但实现起来的细节不清楚,结果有很多细节出错。如两个box(A&&B,对应矩阵为MA&&MB)判断是否相交时,MA和MB应该是不含缩放变量的。还有,用来测试的Axis,在求A和B在这条Axis上的最大投影矩离时应处处求绝对值, A与B的中心在Axis的投影矩离只要正常算就可以了,最后才求绝对值。

这些概念模糊也为程序的架构带来了影响,好在现在都修正过来了。

2. 笔误
有时候mincorner写成了maxcorner,结果出错,后来观察数据才发现。

这边要赞一下我用的调试方法,就是把里面的关键数据导出来,然后在另一个程序中用来画图。 而且数据导出的格式要跟写程序时的格式一样,这样就只要简单的copy&&paste就可以很轻松地搞定了。

ps: 平移是用的移动路径检测,旋转就只是简单地测旋转后的bounding box.

 



 
Xiaoyang @ 2007-09-17 19:18

是一篇Lecture,但改SlimShady耽误了不少时间,今天才算看完一遍,也不会再看第二遍。
开始尝试学习如何看paper,试着自已提出问题、在文中寻找解答。
这里面从标题上来关键字有Practical Guide,说明是一篇偏实用性的paper;global illumination,全局光照,会产生soft shadow, caustic, color bleeding等;Ray Tracing,光线跟踪,决不应该是最土的那种bounce很多次的那种;Photon Mapping,吕波以前做过的,光子图,属于辐射度领域,保存的应该是光能,与Ray Tracing合用可以减少ray bounce的次数,想像中好像Ray Tracing的作用只退化为找寻第一个交点,不知道文中是不是这样子。
问题:
photon map是什么?怎么用?
辐射度又是什么?辐射度算法又怎么用?
初步印象:photon map保存了光能在空间中各个位置的能量大小,因些确定空间中某一点的光强会变得十分简单,程序执行的效率也会比存在多次ray bounce的ray tracing要高。

什么是photon map?
The global illumination algorithm based on photon maps is a two-pass method.
基于photon map的全局照明是一个分两步走的方法,(白痴都知道,第一步建photon map,第二步用photon map)。
第一步,建光子图。 从光源向场景发射光子,这些光子在场景中乱跑(反射和折射);当它们最终到达一个non-specular(非镜面反射)的表面时,将这些到达的光子存在photon map中(可以用一棵kd-tree来保存)。(光子可以经过多次diffuse surfaces的反射吗?)

第二步,用光子图。在绘图过程中,对于场景中给定的任一点(用ray tracing求得的光线与surface的交点),合用statistical techniques(统计技术)将这一点的incoming flux以及reflected radiance从photon map中释放出来。(incoming flux和reflected radiance又是指什么?)

光子图的作用就是用一份光子图就可以替代整个复杂的场景。
光子图相对于ray tracing(Monte Carlo ray tracing methods)的好处在于高效,坏处在于存储量大(需要存光子图)。还有一点是光子图没人申请专利,是人就能用。

photon map怎么用?
分两部分,怎么存,怎么用?
1. 怎么存?
1.1 一开始,要从光源发射光子,分两种情况,单光源,多光源。
1.1.1 单光源
光子的发射需要依照光源的能量分布,这样可以保证每个发出的光子都携带相同的flux(流量、通量).对点光源,在各个方向均匀发射;对方向光源,发射方向相同,但起点不同(保证都在场景外);对面光源,起点在光源表面随便取,但方向限制在半球内,且按余弦分布。大体上讲,光子的发射随着光源的位置、形状、方向等特性而改变。
假设光源的光能(单位为瓦特)为Plight, 发射光子的数目为ne,那么每个光子的能量就为 Pphoton = Plight / ne.
To further reduce variation in the computed indirect illumination (during rendering), it is desirable that the photons are emitted as evenly as possible.
为了进一步降低计算得到的间接照明的变化率,需要光子的发射尽可能均匀。(可考虑stratification或low-discrepancy quasi-random sampling)。
1.1.2 多光源
为了保证所有光源发出的光子所携掉的flux尽量相等,所以明亮的光源发出的光子数会多一些,而暗的光源发出的光子数会少一些。总体来讲,总发出的光子数比单光源发出的光子数并不是多很多。
光子图的作用有时是生成单幅图像,这样对观察者感兴趣的区域的光源就可以进行importance sampling(就是在兴趣范围内的光源采样高些,在其它地方就采样少些甚至不采样)。
1.1.3 Projection Map
感觉它的作用与Depth Map相似,保存了哪些cells包含场景信息,然后只需要向这些cell发射photons就可以了。发射photons时可以有两种基本方法,找到一个有objects的cell,朝这个cell代表的方向(实际上是一个frustum)发射一个random photon;另一种是发射一个random photon,然后再检查这个photon对应的cell有没有objects。最终每个photon的能量为
Pphoton = (Plight / ne) * (number of cells with object / number of total cells).

1.2 追踪发射出去的photons(Photon Tracing)
Photon tracing works in exactly the same way as ray tracing except for the fact that photons propagate flux whereas rays gather radiance.
Photon tracing和光线跟踪基本一样,只是Photon tracing是传递光能(flux),而光线跟踪是聚集光能(radiance)。
Photon tracing与material相交时的表现与光线跟踪中光线与material相交时的表现是不同的。当一个photon与物体相交时,它会在反射、传递(折射)与吸收三者中选取一种继续。而这种选择一般是一种俄罗斯轮盘式的选择。假设对于一个reflective surface,它的漫反射系数为d,镜面反射系数为s,d + s <= 1。那么我们就在[0, 1]中随机选取一个数a,当a在[0,d]中时选择漫反射,当a在[d, s+d]时选择镜面反射,当a在[d+s,1]中时选择被吸收。同时,被反射出去的光子的能量是full的。这在统计意义上与部分漫反射、部分镜面反射的作用是一样的。
上面是关于单色的情况,在多色的情况下(RGB),d和s的选择会有所不同(变为PdPs),Pd = max(drPr, dgPg,dbPb) / max(Pr, Pg, Pb);Ps = max(srPr, sgPg,sbPb) / max(Pr, Pg, Pb)。(dr,dg,db)和(sr,sg,sb)是surface的漫反射和镜面反射系数,而(Pr,Pg,Pb)是入射photon的能量(以RGB的形式表示,我们最为熟悉的形式)。最后如果photon被反射,它的能量也会调整,例如镜面反射后,光子的能量变为(Pr * sr / Ps, Pg * sg / Ps, Pb * sb / Ps)。

1.3 保存光子(Photon Storing)
哪些photons会被保存、怎样保存(数据结构是什么)?
which photon-surface interactions are stored?
这里强调保存的是photon与surface的相交情况,而不是光子本身。
Photons are only stored when they hit diffuse surfaces (or, more precisely, non-specular surfaces)。
光子只有在它击中漫反射表面(更准确地,非镜面反射表面)时才会被保存。这是需要好好体会的,有些表面是即有漫反射成分又有镜面反射成分,所谓的diffuse surface或non-specular surface应该是按光子反射的选择来算的,光子选择漫反射就表示击中了漫反射表面,光子选择镜面反射就表示击中了镜面反射表面。
为什么不存与镜面反射表面的相交情况?因为存储这类相交情况不能提供有用的信息。一个光子正好在某个射线的反射方向上的概率基本为0,因此直接发射反射光线进而与其它表面求交更有意义。
For all other photon-surface interactions, data is stored in a global data structure, the photon map.
对于其它的那些光子与表面相交的数据被存在一个全局的数据结构中,称为光子图。因此,光子图是存的相交信息。
 Note that each emitted photon can be stored serveral times along its path.
一个光子在它的path上可能被存储多次(它与多个surfaces的相交情况)。
Also, information about a photon is stored at the surface where it is absorted if that surface is diffuse.
一个光子的信息(光子的基本信息,例如能量?)被存在它最后被吸收的那个漫反射表面上(它与那个表面的相交情况)。如果那个光子没被吸收,而是射向了环境中,这类光子的基本信息又如何保存呢,是另作一个数据结构来保存吗?文中没有提到。
保存的光子与表面的相交情况包括:光子的能量、光子的入射方向、相交的位置,以及一个flag(there is also space reserved for a flag with each set of photon data)。flag用于photon map的存储和搜索。
struct photon {
float x,y,z;               // position
char p[4];               // power packed as 4 chars
char phi, theta;     // compressed incident direction
short flag;             // flag used in kdtree
};
这个photon是指的photon-surface interaction.  其中的flag是short型,但只有2位用到,用来标记kd-tree中的splitting plane.

During the photon tracing pass the photon map is arranged as a flat array of photons. For efficiency reasons this array is re-organized into a balanced kd-tree before rendering.
在photon tracing过程中photon map是以数组形式存储的,出于效率的考虑,这个数组会被重组为一棵平衡kd-tree。

1.4 Photon map的分类(不考虑Participating Media)
1.4.1 Caustic photon map
一路上都是specular反射(至少一次),并交到Diffuse Surface。 L{S}+D
1.4.2 Global photon map
一路上有specular和diffuse反射(可以没有),并交到Diffuse Surface。 L{S|D}*D (注:印象中第一次相交是不存的,所以应该是L(S|D)+D )
L表示光源,S表示Specular reflection or transmitting,D表示Diffuse reflection or transmitting。
从理论上讲Global photon map应该包含Caustic photon map。但事实是这两个map是分开采样的,对caustic photon map,因为它针对的是一系列specular reflection,所以它更多的是考虑reflection surfaces,因而采样的密度要比通常高很多,尽管从能量的角度来讲并不多多少。而global photon map因为global,所以光子发射的方向要全一些,相对而言光子要稀疏一些。

1.5 Balanced Kd-Tree
Kd-tree一开始不会是平衡的,需要做平衡处理。平衡后的树可以线性表示(不需要pointers)。
1.5.1 如何构造平衡的kd-tree
photon-surface interaction是以点的形式存在的,构建kd-tree时,选取一个最大轴,再在这个轴方向选取一个最中间的点创建splitting plane,这样就保证了左右的photons数相等或差1。这样构建出来的树就是平衡的。

2. 怎么用?
直观的想法:用ray tracing找到交点,用光子图估计光亮度;因为光子图不保存镜面反射,所以有光镜面反射的地方还需要另外计算。
从文章讲述的角度讲,分为如何估算surface上某点x在某个方向w上反射的光能和如何绘制。
2.1 如何估计surface上某点x在某个方向w上反射的光能?
因为是反射,所以就不包括自身发出的光。因为是反射,所以就需要乘以自身材质的反射系数(漫反射和镜面反射)。因为是某个点x上的反射,所以要考虑到所有的入射光。总体来说某点x在w上反射的光能等于所以在以x为中心的一定范围内(如一个球形)的所有光子的能量与反射系数的乘积之和再除了这个范围在x所在平面上的投影面积。
分枝问题:上面的光子图存的光子最终都是到达漫反射平面,那是不是上面的反射系数也只有漫反射系数呢?
Since a sphere is used to locate the photons one might easily include wrong photons in the estimate in particular in corners and at sharp edges of objects. Edges and corners also causes the area estimate to be wrong.
用球形作为范围的话会在edge和corner处将不应该出现的光子包含进来,而且最后相交面积的计算也不会准确。更为光键的,这两者还不能抵消。
除球形之外还有其它形状可以作选择,如cube、disc。cube在判定最近的光子时比较方便,但求相交面积比较麻烦。disc可以避免球形在edge和corner处对光子的误判,但仍然会错误地计算面积。这个问题可以通过Filtering解决。
To reduce the amount of blur at edges, the radiance estimate is filtered. The idea behind filtering is to increase the weight of photons that are close to the point of interest, x.
在光子数不足的情况下,edge处的光能估计会比较blur,为了得到清淅的edge,可以用filter的方式,为靠近x的光子赋上较高的权值。Filter的方式有很多种,也有现成的公式,这里就不列出了,但很多权值的确定都与光子与x的距离有关联。有一种filter叫differential checking,它会检测x是否靠近edge,如果靠近edge,则用较少的光子计算光能,这样就能在edge处形在blurry的效果。
we stop adding photons and use the estimate available if we observe that the estimate is either constantly increasing or decreasing as more photons are added.
因为在一条边的附近,当不停向估算增加光子时,估算值的变化是单调的(单调增,如果在caustic范围外;否则单调减[感觉好像反了一样])。当我们观察到当不断增加光子时光能估算的变化是单调增或单调减时我们就停止。
如何Locate nearest photons?
Locating the nearest neighbors in a kd-tree is similar to range searching in the scene that we want to locate photons within a given volume. For the photon map it makes sense to restrict the size of the initial search range.
在kd-tree中查找最近的neighbors与range searching很类似,对光子图而言最好先限制初始search range的大小。
搜索过程中可以用max_heap保存candinate photons。因为max_heap的root element拥有最大的distance,所以当mx_heap满时,可以用root element的distance代替初始的distance去遍历余下的kd-tree,这样可以更快速的判断,而且最终area的计算也可以更精确。
2.2 如何绘制?
用ray tracing(可以用各种ray tracing,包括distributed ray tracing,它会对每个pixel随机发射多条射线)找到第一个交点x,这个交点的亮度值等于它在eye方向发射的光强加上它在eye方向反射的光强。发射的光强是独立的,与photon无关,反射的光强与phton map有关。此时的反射光强分为几部分:
Direct Illumination、Specular and glossy reflection、Caustic、Multiple diffuse reflections
Direct Illumination
就是交点x直接与光源计算光强,可以用各种ray tracing(包括Mento Carlo方法)计算由光源直接产生的漫反射和镜面反射值。这时会产生shadow rays。
用一种photon map的推导形式shadow photons可以更快地计算shadows,但可能不是非常精确。
这一部分的计算跟前面的photon map是无关的。
Specular and glossy reflection
这一部分的计算也是跟光子图无关的,因为specular的能量分布比较窄,所以用光子模拟不合算。这一部分是用ray tracing计算,即发射secondary ray(反射方向),可以选用Mento Carlo方法。
Caustics
是用光子图计算的,精确的话用Caustic photon map,粗略的话用Global photon map。但决不能用Mento Carlo Ray Tracing,因为太低效。
Multiple diffuse reflections
粗略的是用global photon map估算,精确的是用Monte Carlo ray tracing与estimate of flux相结合。
自身发射光强+ 四部分合起来就是想要的光强了。

从本质上看,一个点对eye产生的光能包括五个部分:
1. 自身光强
2. 从光源射来的光线的specular reflect
3. 从光源射来的光线的diffuse reflect
4. 从环境(其它地方)到来的光线的specular reflect (用secondary specular ray估算)
5. 从环境(其它地方)到来的光线的diffuse reflect(用光子图计算)。
也就是说光子图只占了illumination的1/5,而其它3/5能直接算,最后1/5需要递归而且只是估算(因为反射方向很多[是一个锥形],所以可以考虑用Mento Carlo方法选取众多方向的一个)。


 


 
Xiaoyang @ 2007-09-13 22:23

终于弄懂了yaw, pitch, roll的意思了。yaw是绕Y轴转,pitch是绕X轴转,roll是绕Z轴转。坐标系是左手坐标系,Y轴朝上,Z轴朝里。

原来的程序只用了一个Quaternion记录三者的旋转信息(事实上是yaw和pitch, roll一直没用到),因为是直接累加,所以镜头旋得特别厉害。本想用两个变量分别代表yaw和pitch的旋转量,但出现了问题,感觉上出现了roll的效果,理论上是不应该出现的。但实在查不出原因,就只能放弃这种方法。

现在用一个mode tag来限定同一时间段内只能执行一种变换,而且当从pitch转换到yaw时,需要把先前pitch旋转的量逆向转回,这样保证视线在水平方向。目前来讲是可行了。

ps: 今天竟然推算出了先前看的文章中的一个kd tree遍历算法,赞一个先!