(转)光照图的理论和实践
转自:http://www.cnblogs.com/minggoddess/archive/2012/12/03/2800133.html
光照图的理论和实践
这是我翻译的一篇文章,不知道哪年翻译的,没译完。这几天看见了,又拿出来接着翻译。可是还有那么多,不知道那天能弄出来。所以我就先把翻译的贴出来吧
原文链接http://www.flipcode.com/archives/Light_Mapping_Theory_and_Implementation.shtml
Light Mapping - Theory and Implementation
by Keshav Channa (21 July 2003)
说明
自从quake时代开始,程序员们广泛的使用光照贴图作为最接近现实光照的方法。(现在真正的实时逐像素光照正在缓慢替代光照图)。
本文阐述了一个简单但是快速的光照贴图生成器。它是2001年在Dhruva Interactive(译者注:印度游戏公司)的内部项目中使用。此算法不像辐射度算法那样精确,但是仍然产生了有效的实用的结果。
此文对于那些还没有在自己的引擎中使用光照贴图,并且正在寻找相关资源的人有帮助。
总揽
此文解释了为游戏或者其它图形应用创建光照图的过程。
目标
此文档目标是解释光照图的产生过程。它描述了光照图的光元(lumels--lumination elements) 的计算方法和像素颜色的产生。
然而,此文档不包含任何最新的与当今显卡适配的逐像素渲染技术。
假设
我们假设读者熟知3D游戏编程技术和3D图形的基本技术,尤其是光照,材质,3D几何,多边形,平面,纹理坐标等等。
当然,本文只解释了光照图的生成。并没有解释光照图的纹理坐标的计算。如果你的网格里还没用过光照图的纹理坐标,这里有个简单的方法来测试光照图的产生。
点击这里了解更多。
如果在你了解此文之前,你非常想知道基于光照技术的光照贴图的结果,你可以点这里去demo部分。在demo那里你可以下载一个交互的演示程序。
光照基础
你已经看到看起来非常接近现实环境的游戏(我是指,只是看起来合理)。这种效果的原因是它使用了光照。如果游戏没有被“照亮”,它会看起来不真实。是光照让玩家一遍一遍的看游戏。看看下面两张图的不同,是光照造成的:
图1
有光照图照亮的世界。白色的菱形(右边)代表光源
图2
没有光的世界
对比明显,不是吗?
现在你看到结果了,让我们看下实际操作中(有关静态世界)不同类型的光照
1.逐顶点照亮
o 基于光对顶点的影响,每个顶点计算一个颜色值。
o 颜色值在三角形内进行插值。
o 三角形、多边形不能非常大,否则会产生毛刺。
o 为了显示效果好些,多边形需要被细分到合适的等级。
o 如果顶点数量增加,计算时间就会更长。因为是逐顶点计算的。
o 不会引起从显存加载(光照贴图需要)。
o 所有计算实时进行,因此实现实时光照是可行的。
o 影子错误。
o 得到非常好的照明效果。
2.逐像素光照(实时):此文所述的逐像素光照方法
并不包含现今如NVDIA GEForce 3/4 ,ATI Radeon 8500/9700 甚至更新的显卡所能达到的效果。
o 基于光的影响,计算每个要被绘制的像素的颜色。
o 导致引擎的巨大加载。
o 实时渲染的游戏要使用是不现实的。
o 精确的阴影得以实现(很可能产生碰撞检测的额外开销)。
o 可以得到更为真实的光照效果,但是对于实时渲染来说太慢了。
3.逐像素光照(光照图):
o 实时光照得以实现。
o 动态光照需要更多工作。
o 可以与逐顶点光照结合实现实时动态光照。
o 每一位的耗时的光照计算在预处理阶段已经完成。
o 因此没有运行时的开销。
o 运行时,所以的计算(颜色计算)是由硬件完成,因此很快。
o 视觉显示在光照方面的质量直接依赖光照图纹理的大小。
o 这是我们用这么少的开销所能达到的最接近的逐像素光照。
o 对每个三角形,先用一个漫反射贴图,再把光照贴图乘上去。
用光照图进行逐像素光照
在本文剩下的部分我们要讨论基于光照图的光照。一个光照贴图其实只是又一张纹理贴图。光照贴图和漫反射贴图唯一的不同在于,漫反射贴图装的是颜色值就是颜色值,而光照贴图的颜色值其实是光照值。是应用了这张贴图的里相应的多边形的光照值。其它都是一样的。
o 光照贴图用单独的纹理坐标映射到一个三角形/多边形上,这个纹理坐标被成为光照图纹理坐标
o 光照图的资源和漫反射图的资源的加载方式相同。
o 漫反射贴图的每个像素通常叫做texel,而光照图的每个像素叫做lumel(光元)。我们都喜欢有意思的名字,不是吗?
在钻研光照图的计算过程之前,让我们先看下2D的纹理是怎样映射到3D的三角形上的。
图3
左边的图是一个2D的纹理(你随便在什么图形编辑工具里都能看见)是一个(N*N)的尺寸。图中显示的是规范化的维度。
右边的是个3D的多边形,正如你在游戏中会看到的那样,假设背景消失。纹理坐标被每个顶点使用。正如你所见到的那样,纹理以1:1的比率映射到多边形上面。例如,整个纹理以1:1的比率映射在多边形上。
现在考虑下面的图表。图表展现了一个映射或者粘贴在2D的纹理上的多边形。这个多边形只用了纹理的一部分。所以映射比率不是1:1。我们可以看到多边形只包含了纹理的一部分。这个多边形有纹理贴图的一部分。因此,这个多边形有越多的像素,它看起来就越好。(这是在3D的视角,假设相机距离多边形距离适中。在这次讨论中我们忽略MIPMAP)
图四
让我们仔细看下上面的图并把光照图考虑进去。漫反射贴图,可能或者不能被多个多边形共享。就是说漫反射贴图上的一个像素可以属于多个多边形。而对光照图来说,一个像素只能属于一个多边形。
光照图里的每个像素在现实世界中有个与他所属多边形相关的对应位置。你必须非常清楚这一点。因为每个三角形的顶点有一个物理世界中的位置,所以这个三角形中的每个像素都有个物理世界中的位置。根据三角形的边缘来算,这个位置是非常均匀的。
同样,有个像素中心的定义。每当我们提及像素,它是指像素中心。像素不是一个点,而是一个盒子。可以比对下图。
图五
基于上面的标准,当我们需要一个像素的uv坐标时,计算方法如下:
x=(w+0.5)/Width
y=(h+0.5)/Height
上面的方程中,w和h是我们正在计算的像素的偏移。
Width是光照图的宽。
Height是光照图的高。
具体内容请看下图。
图六
现在,我试着用图表解释下我说的。请看下图
图七
在上面的图表中,我假设性的描述了三角形和光照图像素之间的关系。
在此图中,
o 三角形由三条实线(边)定义。
o 三角形的三个顶点是(0,0,0)(0,100,0)(100,0,0)
o 既然三个点的z值相同,我们就可以忽略计算过程中的z的部分。
o 每个在三角形内部(或者一小部分在外面)的盒子定义了唯一的(光照图的)像素。
o 记住一个像素是个盒子(一个区域)不是一个点。
o 绿色盒子的意思是,像素基本在三角形内部,这样光照图的这个像素就是属于这个三角形的。
o 粉色盒子意思是,像素的中心在三角形外部,因此光照图的这个像素是不属于这个三角形的。
o 每个属于此三角形的像素中心都有一个编号。
o 这个三角形有15个像素。
o 我们接下来练习通过观察三角形和像素的位置来确定大致的理论上的每个像素的世界坐标位置。
记住,我们正在做的是基于眼睛的测量。一点都不准确。
这个练习只是为了让你理解顶点,光照图纹理坐标和光照图像素之间的关系。它会告诉你一个像素的世界坐标位置是什么意思。
看下面的那行,有5个像素并且每个像素的宽是x轴上的100个单位。
o 这意味着每个像素20个单位。
o 并且边缘的位置只是在x范围变化。所以Y和Z都是常量。
o 因此第一个像素的x值是20(不怎么准确)。
o 第一个像素点的位置是(20,0,0)
o 第二个位置是(40,0,0),第三个是(60,0,0),第四个是(80,0,0),第五个是(100,0,0)
同样的,算一下别的像素的位置。
再次提醒,上面的计算是不准确的。上面的练习是为了让你理解每个像素的世界坐标的意思。
从上面的图表中,你可以分析出一个三角形有越多的(光照图)像素,像素在世界坐标间的距离就越小,因此输出结果就越平滑。试着指出这是为什么。
如果你仍然不能理解像素中心和世界坐标的概念,那么就再看一遍前面的文档。如果不清楚请不要继续。
这是用光照图得到的一副图片。
图八
基于光照技术的简单光照图
现在让我们看下光照映射技术的实际处理过程。完整的计算光照图的过程被分成了三步。他们是:
o 1.计算/寻找光照图纹理坐标。
o 2.为光照图的每个像素计算世界坐标和法线。
o 3.计算每个像素最后的颜色。
a.计算/寻找光照图纹理坐标:
这是非常重要和基础的处理。它包含把每个多边形安放到光照图的对应区域里。这个话题可以变成一个很长的文章。因此,我们跳过这个话题继续到下一步。
然而,如果你想得到解释这个问题的链接,那么,在这篇文章的末尾有相关链接。
这是最重要的一个处理,因为它决定了使用这个纹理空间的效率。它可以被自动处理或者用3DMax或者Maya的编辑工具里手动进行。
o 创建一个空的立方体,很大的(空的,这样就不需要碰撞检测了)
o 设置漫反射和光照纹理坐标
o 更好的做法是,对立方体的立方体的所有表面用一个漫反射对六个不同的面(多边形)用不同的光照图。
o 这样你就可以映射光照图到立方体的多边形用1:1的比率了。
o 在立方体的上面创建一个或者两个光源。
o 用这个方案来测试你的光照图生成。
o 下一步,为了测试影子的生成,你可以在立方体的下面的中心加一个盒子,添加碰撞检测相关。
b.为光照图的每个像素计算世界坐标和法线:
这是光照图计算的预处理阶段。如你所知,光照图的每个像素可以映射一个世界坐标系中的位置。这正是为什么我们要计算的原因。只要世界的几何结构和光照图纹理的大小不变,此数据就是静止的,也就是说,只需要计算一次并且可以被重用。
这就是实现方法。现在考虑一个和此图差不多的三角形。为什么我们对于下图的三角形的顶点坐标我们只有二维的内容将在下面的章节里解释。
图九
让我们看一下我们都从这里知道了什么:
a. 我们知道了三角形的顶点(二维)。
b. 我们知道了所有三个顶点的(光照图)纹理坐标。
这里我们需要计算的是:对于给定纹理坐标值(在二维三角形的边缘或内部),取得二维三角形边缘活着内部的二维坐标。我们不得不对属于这个多边形的光照图的每个像素这么做。(记住,一个光照图的像素属于并且只能属于一个三角形/多边形)下面让我们看看是怎么得出来的。
注意:下面的几个章节中,我使用的某些公式,注释并且引用与 链接 Chris Hecker 的文章 《投影纹理映射》。请查看上述链接以获取更多内容。
图十
一段支持光照贴图的shader代码:
Shader "Legacy Shaders/Lightmapped/Diffuse" { Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex ("Base (RGB)", 2D) = "white" {} _LightMap ("Lightmap (RGB)", 2D) = "black" {} } SubShader { LOD 200 Tags { "RenderType" = "Opaque" } CGPROGRAM #pragma surface surf Lambert struct Input { float2 uv_MainTex; float2 uv2_LightMap; }; sampler2D _MainTex; sampler2D _LightMap; fixed4 _Color; void surf (Input IN, inout SurfaceOutput o) { o.Albedo = tex2D (_MainTex, IN.uv_MainTex).rgb * _Color; half4 lm = tex2D (_LightMap, IN.uv2_LightMap); o.Emission = lm.rgb*o.Albedo.rgb; o.Alpha = lm.a * _Color.a; } ENDCG } FallBack "Legacy Shaders/Lightmapped/VertexLit" }