openGL CG 系列教程5 – Environment Mapping ( 环境贴图 )

原创 2010年05月08日 13:38:00

*原创教程,转载请注明出处*

openGL CG 系列教程5 – Environment Mapping (环境贴图)

 

利用物体材质specular属性来模拟高反光的物体是不够的。高反光的物体通常可以在表面反射出周围的物体,这样的效果需要通过环境贴图来实现。这篇教程将介绍如何利用Cg进行环境贴图。环境反射的原理很简单,一个光滑的物体表面可以根据我们观察的不同角度反射出不同位置的环境。即物体表面一点反射的颜色和该点的法线,观察视线和反射视线有关系。

 

 

 

Fig1 观察视线和反射

 

Fig1中显示了它们的关系,其中I为观察视线,N为物体上一点p的法线,RI的反射视线。P点的颜色取决于反射视线R,只要R达到周围环境一点p1,那么p1的颜色就会显示到p点上。向量R可以根据公式

 

R = 2(IN)N-I

 

简单计算,或者使用Cg的内置函数reflect(),该函数接受参数向量I和法线N,返回I关于N的反射向量。

 

float3 R = reflect(I,N);

 

有了反射向量R,现在只要创建好环境贴图后,再根据R在环境贴图中查询出对应的颜色即可。环境贴图就是一个每个面都带有贴图的立方体,六个面的贴图刚好可以并接在一起,看起来就像周围环境一样。

 

 

Fig2 环境贴图

 

Fig2展示了一个环境贴图的六个面,在创建环境贴图的时候,只需要将这六个面贴到立方体对应面上即可。要创建环境贴图,可以简单的渲染六个正方形,然后分别贴上不同的贴图。下面是使用环境贴图渲染茶壶的例子。

 

 

Fig3 茶壶的环境贴图

 

OpenGL的扩展glew中提供了对环境贴图的支持,要开启对环境贴图的支持,使用下面代码即可。

 

glEnable(GL_TEXTURE_CUBE_MAP);

 

当然要在使用环境贴图之间,要创建一个环境贴图。下面给出创建环境贴图的代码。

 

void LoadCubeMapFromBMP()

{

      AUX_RGBImageRec *x_pos_map = auxDIBImageLoad("img/pos_x.bmp");

      AUX_RGBImageRec *x_neg_map = auxDIBImageLoad("img/neg_x.bmp");

      AUX_RGBImageRec *y_pos_map = auxDIBImageLoad("img/pos_y.bmp");

      AUX_RGBImageRec *y_neg_map = auxDIBImageLoad("img/neg_y.bmp");

      AUX_RGBImageRec *z_pos_map = auxDIBImageLoad("img/pos_z.bmp");

      AUX_RGBImageRec *z_neg_map = auxDIBImageLoad("img/neg_z.bmp");

 

      glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 3, x_pos_map->sizeX, x_pos_map->sizeY,

                    0, GL_RGB, GL_UNSIGNED_BYTE, x_pos_map->data);

 

      glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, 3, x_neg_map->sizeX, x_neg_map->sizeY,

           0, GL_RGB, GL_UNSIGNED_BYTE, x_neg_map->data);

 

      glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, 3, y_pos_map->sizeX, y_pos_map->sizeY,

           0, GL_RGB, GL_UNSIGNED_BYTE,  y_pos_map->data);

 

      glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, 3, y_neg_map->sizeX, y_neg_map->sizeY,

           0, GL_RGB, GL_UNSIGNED_BYTE, y_neg_map->data);

 

      glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, 3, z_pos_map->sizeX, z_pos_map->sizeY,

           0, GL_RGB, GL_UNSIGNED_BYTE, z_pos_map->data);

 

      glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, 3, z_neg_map->sizeX, z_neg_map->sizeY,

           0, GL_RGB, GL_UNSIGNED_BYTE, z_neg_map->data);

 

      glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

      glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

      glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

      glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

}

 

从代码中可以看到,首先使用glaux扩展读取六个作为环境贴图的纹理数据。使用glTexImage2D创建纹理的时候使用了

 

 GL_TEXTURE_CUBE_MAP_POSITIVE_X

 .

.

.

 GL_TEXTURE_CUBE_MAP_NEGATIVE_Z

 

它们分别表示环境贴图的不同方向的纹理,然后glTexParameteri设置贴图参数的时候使用GL_TEXTURE_CUBE_MAP表示对环境贴图设置参数。创建好环境贴图后,就可以直接在openGL中渲染。

 

glEnable(GL_TEXTURE_CUBE_MAP);

glBindTexture(GL_TEXTURE_CUBE_MAP, ENVIRONMENT_MAP);

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

     

glBegin(GL_QUADS);

for (int i=0; i<4*6; i++)

{

      glTexCoord3fv(vertex[i]);

      glVertex3fv(vertex[i]);

}

glEnd();

 

glDisable(GL_TEXTURE_CUBE_MAP);

 

上面的代码就可以渲染整个环境贴图了,vertex表示立方体的顶点,它的定义如下。

 

static const GLfloat vertex[4*6][3] = {

      /* Positive X face. */

      { 1, -1, -1 },  { 1, 1, -1 },  { 1, 1, 1 },  { 1, -1, 1 },

      /* Negative X face. */

      { -1, -1, -1 },  { -1, 1, -1 },  { -1, 1, 1 },  { -1, -1, 1 },

      /* Positive Y face. */

      { -1, 1, -1 },  { 1, 1, -1 },  { 1, 1, 1 },  { -1, 1, 1 },

      /* Negative Y face. */

      { -1, -1, -1 },  { 1, -1, -1 },  { 1, -1, 1 },  { -1, -1, 1 },

      /* Positive Z face. */

      { -1, -1, 1 },  { 1, -1, 1 },  { 1, 1, 1 },  { -1, 1, 1 },

      /* Negative Z face. */

      { -1, -1, -1 },  { 1, -1, -1 },  { 1, 1, -1 },  { -1, 1, -1 },

};

 

立方体的六个面这里用了24个顶点来表示,这样可以方便的对每个顶点设置纹理坐标。就像下面的代码:

 

glTexCoord3fv(vertex[i]);

glVertex3fv(vertex[i]);

 

到现在为止,已经做好了渲染环境贴图的准备工作了,下面就是完成shader的内容。利用Cg来进行环境贴图很方便,也很简单。Cg中提供了内置函数来为我们做这些工作,先来看看vertex shader的内容。

 

vs05.cg

void vs_main(   float4 position : POSITION,     // 顶点坐标

                  float2 texCoord : TEXCOORD0,   // 纹理坐标

                  float3 normal   : NORMAL,      // 顶点法线

                  

                  out float4 oPosition : POSITION, 

                  out float2 oTexCoord : TEXCOORD0,

                  out float3 R         : TEXCOORD1,  //反射向量R,保存在纹理坐标中

                  

                  uniform float3   eyePositionW,

                  uniform float4x4 MVP)

{

      oPosition = mul(MVP, position);

      oTexCoord = texCoord;

     

      float3 N = normalize(normal);

      float3 I = position-eyePositionW;  //计算观察向量

 

      R = reflect(I,N);  //利用Cg内置函数计算I关于N的反射向量

}

 

可以看到vertex shader的内容很简单,主要就是计算反射向量R,并将它传到fragment shader 而在fragment shader中,我们要用Cg函数texCUBE(cubemapR),该函数接受两个参数,一个是刚才我们创建的环境贴图纹理数据,一个是反射向量。该函数会自动根据传入的反射向量,然后计算该反射向量和这个环境贴图的交点,并且插值计算出该点对应的像素颜色并返回。整个fragment shader代码如下。

 

05fs.cg

void fs_main(   float2 texCoord : TEXCOORD0,

                  float3 R        : TEXCOORD1,

                  

                  out float4 color :COLOR,

                  

                  uniform float reflectivity,

                  uniform samplerCUBE cubemap)

{

      float4 reflectedColor = texCUBE(cubemap, R);

      color = reflectedColor;

}

 

fragment shader代码很简洁,就是利用函数texCUBE在环境贴图中查询向量R对应的像素颜色。这样就可得到Fig3中的效果。为了真实的模拟反光的物体,有的时候可以对反光的物体再加上纹理,因为在生活中完全反光的物体几乎是没有的,加上其他纹理可以是反光效果真实一些。

 

 

完全反光的茶壶

 

 

带纹理反光的茶壶

Fig4 环境贴图对比

 

要让环境反射的颜色和纹理颜色混合,只需要向alpha混合一样即可。如果反射的颜色为Cr,纹理颜色为Ct,那么混合后的颜色C

 

C = (1 - factor)Ct + factorCr

 

这里factor就是混合系数了。现在只与需要对fragment进行很少的修改就可以实现这个效果。

 

05fs.cg

void fs_main(     float2 texCoord : TEXCOORD0,

                  float3 R        : TEXCOORD1,

                  

                  out float4 color :COLOR,

                  

                  uniform float reflectivity,

                  uniform sampler2D decalMAP, // 只需增加传入一个纹理

                  uniform samplerCUBE cubemap)

{

      float4 reflectedColor = texCUBE(cubemap, R);  //计算反射颜色

      float4 decalColor = tex2D(decalMAP, texCoord); //计算纹理颜色

 

      color = lerp(decalColor, reflectedColor, reflectivity); //混合反射颜色和纹理颜色,reflectivity为混合系数

}

 

这里使用了Cg另一个内置函数lerp,该函数根据传入的混合系数,进行线性插值计算。一般混合系数在01之间。

 

整个环境贴图还是比较简单,也比较容易实现。但是这中环境贴图也有很多不利的地方,比如模拟环境的贴图一般是静态图片,所以环境不能发生变化。还有如果同时渲染多个物体,每个物体不能其他的物体反射到表面,想要达到这种效果要使用ray tracing(光线跟踪)或 render to texture(渲染到纹理)技术。

 

 

 

 

*原创教程,转载请注明出处*

相关文章推荐

立方体纹理(cube map)

概念:          立方体纹理是一种特殊的纹理技术,它用6幅二维纹理图像构成一个以原点为中心的纹理立方体。对于每个片段,纹理坐标(s, t, r)被当作方向向量看待,每个纹理单元都表示从原点所看...

立方体环境贴图(Cube Mapping)之OpenGL原理

立方体纹理是一种特殊的纹理技术,它用6幅二维纹理图像构成一个以原点为中心的纹理立方体。对于每个片段,纹理坐标(s, t, r)被当作方向向量看待,每个纹理单元都表示从原点所看到的纹理立方体上的图像。 ...

OpenGL Cube Map Texturing

都是非纯2D的texture cube map  texture可以理解为6个面的纸盒, sample的时候使用vector射线型的sample. volume texture可以理解是一...

OpenGL学习脚印: 环境纹理映射(environment mapping)

写在前面 上一节初步学习了使用cubeMap创建天空包围盒,本节继续深入Cubemap这个主题,学习环境纹理贴图。本节示例程序均可以从我的github下载。 本节内容整理自: 1....

openGL CG 系列教程5 – Environment Mapping ( 环境贴图 )

openGL CG 系列教程5 – Environment Mapping ( 环境贴图 ) 分类: openGL CG系列教程2010-05-08 13:38 3676人阅读 评论(2) 收藏...

OpenGL学习笔记——纹理贴图

简单地说,纹理就是矩形的数据数组。例如,颜色数据、亮度数据、颜色和alpha数据。纹理数组中的单个值常常称为纹理单元(texel)。纹理贴图之所以复杂,是因为矩形的纹理可以映射到非矩形的区域,并且必须...

OpenGL cube map方式实现的环境贴图●如何设置相机

对cube map有一些了解的朋友都知道,如果要在物体表面实现镜面反射的效果,需要在物体的中心设置一个相机,沿+X,-X,+Y,-Y,+Z,-Z六个方向分别取景,渲染到cube map的六个子text...

OpenGL 4.0 GLSL 用立方体贴图和 环境贴图 模拟反射效果

我们可以用纹理代表物体周围的环境,然后把纹理贴到物体上,实现映射周围的场景目的,这项技术也被乘坐环境贴图(EnvironmentMapping)。环境贴图通常来模拟反射或者折射效果。 最常见的环境贴图...

机器学习实践系列之12 - OpenCV之三维重建

又一次提到了 三维重建,许是三维重建的应用面太广,从 PrimSense 到 Kinnect 再到RealSense,还有一堆国内的山寨机不表。        从 Kinnect Fusion 到 P...

openGL CG 系列教程2 - Vertex Lighting

*原创文章,转载请注明出处*openGL CG 系列教程2vertex lighting 之前的一篇教程HelloCG介绍了Cg的一些最基本的东西。这篇教程将介绍利用可编程渲染管线来实现光照。光照模型...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:openGL CG 系列教程5 – Environment Mapping ( 环境贴图 )
举报原因:
原因补充:

(最多只允许输入30个字)