对于一个3D场景光照是加强真实感的重要因素, 目前来说大概可以分为静态光照和动态光照.在这里我们主要讨论的是静态光照. 静态光照的好处很多, 比如: 渲染速度快, 效果好, 可以逐象素的计算光照和阴影.缺点就是光照的效果是静态的, 并且其需要一定的预处理时间. 我把这篇文章分为两部分, 上部分为计算光照贴图, 下部分为计算辐照度.
首先我们来看上半部分,也就是如何计算光照贴图, 这里有一篇文章讲的很清楚,大家可以去看. http://www.flipcode.com/articles/article_lightmapping.shtml.具体的算法我就不细说了.
其大概可以分为以下几个步骤:
1. 生成光照贴图的UV坐标. 这本身就是一个很复杂的问题, 但实际上我们可以跳过这个问题, 我们可以用3dsmax来展UV, 这是一个手工活, 虽然加大了一些工作量,但是它可以保证贴图的利用率, 并且不用写一行代码.
2. 我们需要根据光照贴图的所有有效的象素的UV坐标, 得到其在世界空间的坐标.这个算法在上面的文章里有, 但并不是很完整, 在我的源码(LightMapping.cpp)里大家可以看到这个算法的完整实现.
3. 为每个有效象素计算光照, 这个可以参照Direct3D里的光照公式. 至于阴影, 我用的是射线追踪, 从光源对目的点引一条直线判断其是否被遮挡, 如果是则其为阴影, 否则其为计算出来的光照值.
4. 填充无效的象素. 由于我们对光照贴图的采样方式为线性采样(D3DTEXF_LINEAR), 因此其会读取到周围8个邻居象素的值, 因此我们把所有效象素的8个邻居的象素值做一下线性采样, 这样最后就不会出现黑边.
5. 柔化光照贴图, 因为上面计算出来的光照图会产生很硬的边, 如果对其做一下线性采样, 这样就不会太硬.最后就是渲染出来啦.
下面是计算光照图后的效果图:
下面是经过柔化的效果图:
我们看上面的场景, 大家会发现除了受光照的地方其它地方都太黑, 一般来说我们可以给它加上环境光来解决这个问题, 但是其带来的结果就是颜色太平, 因为所有黑的地方都是一个颜色, 而真实世界不可能是这个样子, 对于真实世界中不仅仅只有光源能发光, 每一个表面都能散射光, 对于上面的场景被照亮的区域再向周围散射光能, 这样一次次进行下去直到每整个屋子都被照亮,这就是这篇文章的下部分: 辐照度.
关于辐照度这里也有一篇文章供大家参考: http://freespace.virgin.net/hugo.elias/radiosity/radiosity.htm. 具的算法我也不细说了,.其太致的思想就是, 把场景的的表面分为一个个小patch, 而对于我们来说这个小patch对应的就是光照图的一个象素. 然后把眼睛放到这个小patch上, 在从这个patch表面的法向方向观看场景. 这个过程我们可以通过D3D9的ID3DXRenderToEnvMap这个接口的BeginHemiSphere来实现,.它可以把场景渲染到一个半球体上.(这里我有一个疑惑, BeginHemiSphere
这个函数是接收两个参数, 第一个为: IDirect3DTexture9* pTexZPos,
第二个为: IDirect3DTexture9* pTexZNeg.
但最后我渲出的前半球的场景在pTexZNeg内里?).
因为越面向法向的象素其接收的光能应该越大, 并且要修正投影变换带来的负面影响, 我们需要把渲染出来的场景图乘上光能衰减图来纠正最后的结果.最后, 我们把场景图的颜色值加起来并归一化, 这就是这个象素的接收的辐射光能, 把它加上原来的颜色值就可以了, 当然这个辐射过程可能需要很多次才能达到比较好的效果.
关于这个算法它有一个缺点就是太慢, 我要根据上面那篇介绍辐射度的文章介绍的方法对其进行了优化, 具体的可以参考原文和我的源码.
对上面场景应用了12次辐射后的效果图如下:
对上面场景应用了16次辐射后的效果图如下:
如果大家有更好的想法或者什么疑惑可以给我发邮件. godmangj@126.com .
该文章的源代码:
http://www.ogdev.net/download/2006/radiosity_src.rar
(需要DXSDK_Feb_2006, 工程项目为Visual Studio.Net 2003)
动行程序: http://www.ogdev.net/download/2006/radisoity_bin.rar
注意: 以上代码仅供大家个人学习或研究使用, 未经允许不能用于商业用途.
首先我们来看上半部分,也就是如何计算光照贴图, 这里有一篇文章讲的很清楚,大家可以去看. http://www.flipcode.com/articles/article_lightmapping.shtml.具体的算法我就不细说了.
其大概可以分为以下几个步骤:
1. 生成光照贴图的UV坐标. 这本身就是一个很复杂的问题, 但实际上我们可以跳过这个问题, 我们可以用3dsmax来展UV, 这是一个手工活, 虽然加大了一些工作量,但是它可以保证贴图的利用率, 并且不用写一行代码.
2. 我们需要根据光照贴图的所有有效的象素的UV坐标, 得到其在世界空间的坐标.这个算法在上面的文章里有, 但并不是很完整, 在我的源码(LightMapping.cpp)里大家可以看到这个算法的完整实现.
3. 为每个有效象素计算光照, 这个可以参照Direct3D里的光照公式. 至于阴影, 我用的是射线追踪, 从光源对目的点引一条直线判断其是否被遮挡, 如果是则其为阴影, 否则其为计算出来的光照值.
4. 填充无效的象素. 由于我们对光照贴图的采样方式为线性采样(D3DTEXF_LINEAR), 因此其会读取到周围8个邻居象素的值, 因此我们把所有效象素的8个邻居的象素值做一下线性采样, 这样最后就不会出现黑边.
5. 柔化光照贴图, 因为上面计算出来的光照图会产生很硬的边, 如果对其做一下线性采样, 这样就不会太硬.最后就是渲染出来啦.
下面是计算光照图后的效果图:
下面是经过柔化的效果图:
我们看上面的场景, 大家会发现除了受光照的地方其它地方都太黑, 一般来说我们可以给它加上环境光来解决这个问题, 但是其带来的结果就是颜色太平, 因为所有黑的地方都是一个颜色, 而真实世界不可能是这个样子, 对于真实世界中不仅仅只有光源能发光, 每一个表面都能散射光, 对于上面的场景被照亮的区域再向周围散射光能, 这样一次次进行下去直到每整个屋子都被照亮,这就是这篇文章的下部分: 辐照度.
关于辐照度这里也有一篇文章供大家参考: http://freespace.virgin.net/hugo.elias/radiosity/radiosity.htm. 具的算法我也不细说了,.其太致的思想就是, 把场景的的表面分为一个个小patch, 而对于我们来说这个小patch对应的就是光照图的一个象素. 然后把眼睛放到这个小patch上, 在从这个patch表面的法向方向观看场景. 这个过程我们可以通过D3D9的ID3DXRenderToEnvMap这个接口的BeginHemiSphere来实现,.它可以把场景渲染到一个半球体上.(这里我有一个疑惑, BeginHemiSphere
这个函数是接收两个参数, 第一个为: IDirect3DTexture9* pTexZPos,
第二个为: IDirect3DTexture9* pTexZNeg.
但最后我渲出的前半球的场景在pTexZNeg内里?).
因为越面向法向的象素其接收的光能应该越大, 并且要修正投影变换带来的负面影响, 我们需要把渲染出来的场景图乘上光能衰减图来纠正最后的结果.最后, 我们把场景图的颜色值加起来并归一化, 这就是这个象素的接收的辐射光能, 把它加上原来的颜色值就可以了, 当然这个辐射过程可能需要很多次才能达到比较好的效果.
关于这个算法它有一个缺点就是太慢, 我要根据上面那篇介绍辐射度的文章介绍的方法对其进行了优化, 具体的可以参考原文和我的源码.
对上面场景应用了12次辐射后的效果图如下:
对上面场景应用了16次辐射后的效果图如下:
如果大家有更好的想法或者什么疑惑可以给我发邮件. godmangj@126.com .
该文章的源代码:
http://www.ogdev.net/download/2006/radiosity_src.rar
(需要DXSDK_Feb_2006, 工程项目为Visual Studio.Net 2003)
动行程序: http://www.ogdev.net/download/2006/radisoity_bin.rar
注意: 以上代码仅供大家个人学习或研究使用, 未经允许不能用于商业用途.