OpenGL 立方贴图
Copyright NVIDIA Corporation, 1999.
Commercial publication in written, electronic, or other forms without expressed written permission is prohibited.
Electronic redistribution for educational or private use is permitted.
摘要
本网络教程解释了硬件立方贴图的目的。立方贴图可以用于环境贴图(
environment mapping)、高光曲线区(stable specular highlights)以及逐像素光照效果的凹凸贴图(bump map)。编程人员可以下载实例代码,了解OpenGL EXT_texture_cube_map扩展。
介绍
如今,硬件加速的纹理贴图已经非常普及。曾经只在那些高端的工作站和飞行模拟中才有的,如今成为了普通
PC机和家用视频游戏的一个标准特征。原理很简单,一个2D图像(纹理)上的一些点被附着到3D模型的顶点上,当渲染3D模型时,纹理图像%26ldquo;壁纸%26rdquo;一样贴在3D几何模型上,当对3D模型进行动画或改变形状时,纹理图%26ldquo;粘%26rdquo;住模型,下面是一个例子:
写着%26ldquo;
GeForce%26rdquo;的纹理被%26ldquo;包在%26rdquo;一个轮胎上(或者称之为%26ldquo;油炸饼%26rdquo;)。我们也可以使用其他不同的纹理使得轮胎呈现%26ldquo;木头%26rdquo;的样子。我们可以将任何的纹理作用于任何的几何形状。
通常的纹理映射几乎可以又在所有的
3D游戏中,但是它并不适合所有的情况,考虑一个像%26ldquo;银色烛台%26rdquo;这样一个物体,闪亮的表面反射环境光。由于表面是反射性的,没有一个纹理可以贴在这样的表面上。如果烛台或者视点位置移动,烛台上的反射也将改变位置。因此不能使烛台顶点和纹理图上固定的2D位置匹配,而是应该将反射性的纹理以合适的方向映射到物体表面。
不幸的是,通常的硬件纹理映射方法并不适合这种情况,因此不使用
2D纹理和表面位置进行映射,而是真正将表面位置在360度方向进行映射。(水平有限,很吃力!)
观察你的周围,仅仅旋转头部和眼睛,你能看到全部的方向,环境可以被理解为以头部为中心的全方位图。这和你观察
2D图像上的位置(X,Y)是根本不同的。如果你的环境能够被编码为一个纹理图,那么它能够通过一个3D的方向而不是2D位置进行访问。
使用立方贴图对环境进行编码
立方贴图是纹理贴图的一种形式,它使用一个
3D的向量(即方向)来对应一个纹理,6个方形的2D纹理组成了立方体的6个面。现在请再次考虑你的周围环境,你可以站在一个位置,并 %26ldquo;捕获%26rdquo;360度视角(每转90度拍下6张照片),下图为6张捕获的室外场景:
back bottom
front left
right top
下图描述如何将立方纹理和立法体的面进行映射。
一个立方贴图的例子
使用硬件加速的立方贴图使得渲染一个动态反射环境的物体成为可能。最酷的是它能实现%26ldquo;实时%26rdquo;。下面是个%26ldquo;
bubble%26rdquo;程序运行在带有NVIDIA GeForce 256图形处理器的PC机上的截图。
程序使用一个物理模型来实时的扭曲%26ldquo;泡泡%26rdquo;的形状,动荡的%26ldquo;泡泡%26rdquo;表面导致了动态的环境映射。
不同的环境映射方法
计算机图形研究人员称上述渲染技术为%26ldquo;环境映射%26rdquo;。这个技术并非新技术,
Blinn 和Newell在1976年首次发表了该想法,如电影%26ldquo;Abyss%26rdquo;和%26ldquo;Terminator 2%26rdquo;已经普及了该技术(但是这些特效都是通过离线渲染的,而不是实时的)。
立方贴图只是实现环境映射的方法之一。其他方法如%26ldquo;球体映射%26rdquo;和%26ldquo;双抛物面映射%26rdquo;(由
Heidrich %26amp; Seidel开发)能够产生相似的效果(使用通常的环境映射),但是他们都有严重的缺点而限制了用处。球体映射非常简单,并由OpenGL直接支持,但它是视点相关的,不同的视点需要不同的球体映射。另外,球体映射在边界上会产生%26ldquo;瑕疵%26rdquo;。如下图所示:
双抛物面映射能够克服这些问题,但是它需要两个纹理单元或者两遍渲染,因此代价比较高。双抛物面映射同时需要计算相关的纹理坐标(除非由
OpenGL扩展支持)。创作球体映射或者抛物面映射的纹理图也不直观,它需要专业的图形变形操作。如下图所示:
球体映射图
双抛物面映射图(前向) 双抛物面映射图(后向)
立方贴图消除了球体映射的%26ldquo;瑕疵%26rdquo;。和双抛物面映射不同,它仅仅需要一个纹理单元和一遍次渲染。由于立方贴图的纹理图仅仅是环境中的六个方形面,可以很方便的通过拍照获得并动态的进行渲染。立方贴图完全利用了纹理图的每个像素,而球体或抛物面映射可能只使用了
78%的纹理像素。同时它没有球体或双抛物面的变形操作。
因此,立方贴图是更直观的,但是立方贴图需要能够一次性访问
6张纹理图,这就要求更新的纹理硬件。而且立方贴图比通常的纹理贴图更加复杂。快速的半导体技术发展已经使得在PC机上实现了硬件立方贴图技术。NVIDIA's GeForce 256是第一个支持立方贴图的GPU。
硬件只有在具备了软件接口时才能发挥出它的威力。幸运的是,
Windows上的Microsoft's Direct3D API和OpenGL都支持立方贴图。OpenGL使用 EXT_texture_cube_map扩展支持立方贴图。
立方贴图的其他应用
镜面高光反射:
CAD程序在显示3D物体时会添加反射光照效果传达表面曲率。球体表面上的一个闪光点就是这种镜面高光的一个例子。由于人们非常熟悉真实物体上的镜面高光,因此它提供了关于表面曲率的非常重要的视觉提示。一个麻烦的问题是对这些镜面高光进行采样是非常困难的,因为CAD程序通常使用实体网格对3D物体进行渲染,只能在网格顶点上执行昂贵的镜面高光计算。然后对光照后的颜色在曲面上通过网格顶点进行插值。不幸的是,如果网格不够细致,镜面高光的采样会不足。当旋转物体时,将导致%26ldquo;摆动的%26rdquo;镜面高光,高光的亮度依赖于和网格内顶点的靠近程度。高光的重要视觉提示大打折扣。然而,更细的网格是不切实际的,这将带来更慢的速度。理想上,镜面高光应该足够明亮和稳定,而不受物体顶点网格密度的影响。
立方贴图提供了直观的方法来渲染稳定的镜面高光。多个镜面高光可以编码成一个立方贴图纹理,它们能够通过表面的反射向量获得。不必计算每个顶点的颜色并在表明上进行插值,硬件仅计算反射向量(比计算实际的镜面反射光贡献要快得多)计算纹理坐标。下图为一个使用立方贴图产生的稳定高光。左图是白色和桔红色的高光。右图使用通常的逐顶点光照
使用立方贴图来产生稳定的镜面高光的另一个优点是:镜面反射的光源数量(被编码到单一的立方纹理中)与绘制性能没有关系,而且不影响交互性能(在交换前已经编码)。缺点是
它只适合于光源位置比较远或者无限远的情况,幸运的是,
CAD软件通常使用无穷光源。
天窗照明
立方贴图的另一个应用是精确的建模室外照明。计算机图形程序员经常将太阳光视为无穷光源,这是一种粗略的近似,会导致不真实的户外照明。由于大部分室外光直接来自太阳和大气散射。通常会提及天窗照明:日子的变化、云层以及污染都会影响天窗的照明。
立方贴图可以捕获天窗照明中的散射成分。通过表面法向来访问立方纹理能够迅速得到天窗的散射光照明。为特定的天窗结构计算立方纹理是复杂的,但已经有些图形研究人员为此作出了努力并取得了客观的效果。具体参见
SIGGRAPH 99中的论文:"A Practical Analytic Model for Daylight"、Klassen的"Modeling the Effect of the Atmosphere on Light"(ACM Transactions on Graphics, July 1987 )以及Tadamura等的"Modeling of Skylight and Rendering of Outdoor Scenes" (EUROGRAPHICS '93)。
动态立方贴图反射
如上述%26ldquo;泡泡%26rdquo;的例子中,它可以移动和扭曲,观察者可以实时的改变视点位置。反射的环境本身不会改变,为了动态的反射环境,每一次改变,都要程序员重新产生立方贴图。
动态立方环境贴图真是处理这种情况。动态立方纹理是在第一次渲染场景时产生的,在
OpenGL中可以使用 glCopySubTexImage2D命令拷贝每个面到立方纹理中。然后使用立方贴图进行绘制物体。可以预知,这个过程比静态的立方贴图要耗时,因为需要更多的绘制。下面是一个例子:在%26ldquo;泡泡%26rdquo;移动的过程中,其上的环境映射将会动态的改变。
下图显示了立方纹理中动态渲染的面:
如果场景中有多个反射物体,该技术将变得更费时,每个反射物体都需要一个动态的环境映射,如果物体还能反射其他物体反射来的光,事情将变得更加复杂。通常可能需要考虑光线跟踪算法来解决问题。下图显示了两个反射的球体,仔细观察,可以发现下面的球体包含了上面球体的反射。可以通过递归在上面的球体中产生下面球体的反射,但是下图使用了
18遍的渲染,因此不适合用来实时交互。
奇特的逐像素光照
立方贴图为实现逐像素光照效果提供了支持。逐像素光照包括计算每个像素的散射、反射、环境光等成分(和逐顶点光照并进行插值对应)。前面讨论的环境映射、稳定的镜面高光以及天窗照明等立方贴图的应用只是通用的逐像素光照技术的特例。由于光照计算依赖于
3D方向向量,通过方向向量访问的立方贴图便自然而然的作为表达逐像素光照操作的方法。艺术级的逐像素光照需要硬件支持像素操作如矢量点乘和纹理合成等。
在下面的例子中,所有的图象通过
OpenGL程序产生(在GeForce 256 GPU上),
新的立方纹理Targets
现在
OpenGL中有1D 、 2D 、3D以及立方纹理。每个纹理Target有个相对应的枚举值,用来传递给 glBindTexture, glTexParameter和 glTexImage等纹理函数。2D 纹理为 GL_TEXTURE_2D. 1D 和 3D 纹理为 GL_TEXTURE_1D 和 GL_TEXTURE_3D。对于立方纹理为 GL_TEXTURE_CUBE_MAP_EXT。但和 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D等不同的是,它不能传递给glTexImage2D和glCopySubTexImage2D函数。因为立方纹理中有六个mipmap集合(每个面对应一个)六个纹理Target为:
GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT
GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT
GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT
GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT
GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT
方便的地方是,这些
Target之间有如下连续性:
GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT = GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + 2,
GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT = GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + 5
这些
Target会用在 glTexImage2D, glCopySubTexImage2D,glGetTexImage, glCopyTexImage2D, glTexSubImage2D以及 glGetTexLevelParameter等函数中。
设置立方纹理的图象
下例为如何装入立方纹理的六个面
GLubyte face[6][64][64][3];
for (i=0; i<6; i++) {
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT + i,
0, //level
GL_RGB8, //internal format
64, //width
64, //height
0, //border
GL_RGB, //format
GL_UNSIGNED_BYTE, //type
%26amp;face[i][0][0][0]); // pixel data
}
每个面为64x64的RGB图象。
和
2D纹理一样,可以使用 gluBuild2DMipmaps 函数来创建mipmap。如:
gluBuild2DMipmaps(GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT,
GL_RGB8, 64, 64, GL_RGB, GL_UNSIGNED_BYTE, %26amp;face[1][0][0][0]);
Enable and Disable立方纹理函数调用方法:
glEnable(GL_TEXTURE_CUBE_MAP_EXT);
glDisable(GL_TEXTURE_CUBE_MAP_EXT);
将纹理坐标映射到面上
方向
target sc tc ma
---------- --------------------------------- --- --- ---
+rx GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT -rz -ry rx
-rx GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT +rz -ry rx
+ry GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT +rx +rz ry
-ry GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT +rx -rz ry
+rz GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT +rx -ry rz
-rz GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT -rx -ry rz
s = ( sc/|ma| + 1 ) / 2
t = ( tc/|ma| + 1 ) / 2
glTexCoord3f(s,t,r); // user-supplied direction vector for cube map texturing
glVertex3f(x,y,z);
Reflection map example:
glTexGenfv(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT);
glTexGenfv(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT);
glTexGenfv(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
Normal map example:
glTexGenfv(GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
glTexGenfv(GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
glTexGenfv(GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT);
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
For these two modes to operate correctly, correct per-vertex normals must be supplied.
在使用前使用 glGetString(GL_EXTENSIONS) ,确保扩展可用。如果OpenGL'头文件没有包括 EXT_texture_cube_map扩展中的最新的枚举值
, 可以将它定义在自己的头文件中:
#ifndef GL_EXT_texture_cube_map
/* EXT_texture_cube_map */
#define GL_EXT_texture_cube_map 1
#define GL_NORMAL_MAP_EXT 0x8511
#define GL_REFLECTION_MAP_EXT 0x8512
#define GL_TEXTURE_CUBE_MAP_EXT 0x8513
#define GL_TEXTURE_BINDING_CUBE_MAP_EXT 0x8514
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT 0x8515
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT 0x8516
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT 0x8517
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT 0x8518
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT 0x8519
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT 0x851A
#define GL_PROXY_TEXTURE_CUBE_MAP_EXT 0x851B
#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_EXT 0x851C
#endif