OpenGl 游戏编程笔记 第四章: 变换和矩阵(二)

OpenGl 游戏编程笔记 第七章:纹理映射 (texture mapping) (一)

皮贝贝 posted @ 2010年6月24日 06:14 in opengl with tags OpenGL game , 31201 阅读

 

OpenGl 游戏编程笔记 第七章:纹理映射 (texture mapping) (一)

本章你将学到

  • 纹理映射基础知识
  • 纹理坐标
  • 纹理对象及绑定
  • 纹理过滤
  • mipmap和自动生成mipmap
  • 纹理参数, 外包模式, 细节级别
  • 纹理环境和纹理函数

1 概述

     概括的说, 纹理映射机制允许你将一个图像关联到一个多边形上,从而呈现出真实视觉效果。例如, 你可以将书的封面图像应用到一个方形上, 这样这个方形看起来就像是一本书了。 你可以将地球的地图通过纹理映射应用到一个球体上, 那么这个球体就是一个3D的具真实感的地球了。纹理映射在当今的3D图形应用上处处皆是。今天的游戏都是通过纹理映射来作为虚拟真实的第一个步骤。

    纹理映射是一个二维的数组。数组中的每一项称之为纹理点( texel )。 虽然这个数组是二维的, 但是可以映射到非二维的对象上, 如球体或者其他的 3D 对象模型上。

    比较常见的是, 开发者在他们的图形应用中运用二维纹理, 当然一维或者三维的纹理也并非未闻。二维纹理有宽度和宽度决定二维。一维纹理也有宽度和高度, 只是高度被设为值 1(单位:像素 pixel). 而三维纹理不仅具有宽度和高度, 还有深度, 所以三维为纹理又称为立体纹理。我们讨论的主要是二维纹理。

2 预备知识: 纹理坐标

    在 OpenGl 中是通过指定纹理坐标来将纹理映射到多边形上去的. 在纹理坐标系中, 左下角是 (0,0), 右上角是 (1,1). 2D 纹理的坐标中通过指定 (s,t) (s为x轴上,t为y轴上, 取值0~1). 1D, 3D, 4D纹理坐标系中对应的需要指定 (s), (s,t,r), (s,t, r,q).

    纹理坐标需要通过函数 glTexCoord() 来设置, 此函数:

 
void glTexCoord{1234}{sifd}(TYPE coords);
void glTexCoord{1234}{sifd}v(TYPE coords);

    如将 2D 纹理坐标设为 (0.2, 0.4):

glTexCoord2f(0.2f, 0.4f);

    每次通过 glVertex() 指定一个顶点时, 当前的纹理坐标会被应用到这个点上. 所以每指定一个新的顶点, 需要同时修改纹理坐标:

glBegin(GL_POLYGON);
        glTexCoord2f(0.0f, 0.0f); glVertex3f(-0.5f, 0.5f, 0.5f);//左下角
        glTexCoord2f(1.0f, 0.0f); glVertex3f(0.5f, 0.5f, 0.5f); // 右下角
        glTexCoord2f(1.0f, 1.0f); glVertex3f(0.5f, 0.5f, -0.5f);// 右上角
        glTexCoord2f(0.0f, 1.0f); glVertex3f(-0.5f, 0.5f, -0.5f);// 左上角
glEnd();

    至此, 我们知道了纹理坐标如何赋值.且看如何创建纹理:

3 使用纹理映射

    纹理就是应用到多边形上的图像. 这些图像可以从文件中加载, 或是在内存中生成. 一旦你将图像数据加载到了内存中, 你需要指定其为纹理映射来使用它. 指定其为纹理映射, 首先需要生成一个纹理对象, 其中存储着纹理的诸如图像数据, 如何应用等信息.

    纹理是一个OpenGL状态, 因而通过 glEnable()  glDisable() 来开闭, 参数是 GL_TEXTURE_1D,  GL_TEXTURE_2D,  GL_TEXTURE_3D,  GL_TEXTURE_CUBE_MAP.

3.1 纹理对象

    纹理对象是内部数据类型, 存储着纹理数据和选项等. 你不能直接访问它, 但是可以通过一个整数的 ID 来作为其句柄 (handler) 来跟踪之. 为了分配到一个唯一的 ID, OpenGL 提供了glGenTextures() 函数来获取一个有效的 ID 标识值:

 
void glGenTexture(Glsizei n, GLuint *texture);

    texture 是一个数组, 用于存储分配到的n个ID值. 在调用一次 glGenTextures() 后, 会将分配到的 ID 标识为'已用', 虽然直到绑定后才真正为'已用'.

    分配3个纹理对象 ID:

unsigned int textureObjects[3];
glGenTexture(3, textureObjects);

3.2 纹理绑定

    在第一次绑定一个纹理对象时, 会将一系列初始值来适应你的应用. 函数 glBindTexture() 用于绑定操作:

 
void glBindTexture(GLenum target, GLuint texture);

    target 指定了纹理类型: GL_TEXTURE_1D,  GL_TEXTURE_2D,  GL_TEXTURE_3D,  GL_TEXTURE_CUBE_MAP.  texure 是你希望绑定的纹理对象的 ID.

    一个被绑定的纹理对象直到被删除,或被另外的纹理对象绑定到 target 上才被解除绑定. 当一个纹理对象绑定到 target 上后, OpenGL 的后续的纹理操作都是基于这个纹理对象的。

glBindTexture (GL_TEXTURE_2D, textureObject[0]);
// 后面的对 GL_TEXTURE_2D 的纹理操作影响 textureObject[0]

glBindTexture (GL_TEXTURE_3D, textureObject[1]);
// 后面的对 GL_TEXTURE_3D 的纹理操作影响 textureObject[1]
// 对 GL_TEXTURE_2D 的纹理操作依然影响 textureObject[0]

glBindTexture (GL_TEXTURE_2D, textureObject[2]);
// 后面的对 GL_TEXTURE_2D 的纹理操作影响 textureObject[2]
// 对 GL_TEXTURE_3D 的纹理操作依然影响 textureObject[1]

3.3 删除纹理对象

    创建一个纹理对象后, OpenGL为其分配内存, 所以当不再使用一个纹理对象时, 为防止内存泄露, 必须删除. 删除纹理对象的函数: glDeleteTexture() :

 
void glDeleteTexure(Glsizei n, Gluint *texture);

    texture 指定了要删除的纹理对象的 ID (n个). 在删除后, texture 中的各个对象 ID 会置为0.

3.4 驻留纹理

    显卡有一块固定大小的内存区域专门用于存储纹理数据。当数据超量时,会将一部分纹理数据移除到系统内存中(通常是最近最少使用的纹理数据). 当这些移除的纹理被再次使用时,会影响击中率, 因为它们会被再次移入显卡的内存中。你可以查看一个纹理对象是否驻留在显卡内存中未被移出, 通过函数 glAreTexturesResident() :

 
GLboolean glAreTexturesResident (GLsizei n, GLuint *textures, GLboolean *residents);

    texture 中每一项纹理对象的驻留情况会存储在 resident 参数中返回。 若 textures 中有一项纹理对象不在内存驻留内存, 函数会返回 GL_FALSE.

3.5 纹理优先级

    纹理的优先级是针对驻留显卡内存而言。优先级设置函数 glPrioritizeTextures() :

 
void glPrioritizeTextures (GLsizei n, GLuint *textures, GLclampf *priorities)

    前两个参数 textures 和 n 指定了要设置优先级的纹理对象数组。 priorities 是 textures 中每一项纹理对象对应的优先级, priorities 中每一项的优先级取值区间是 [0,1], 0为优先级最低, 1 为最高。 glPrioritizeTextures() 函数会忽略掉那些未使用的和优先级要设为 0 的纹理对象。

4 指定纹理

    OpenGL 提供了三个函数来指定纹理: glTexImage1D(), glTexImage2D(), glTexImage3D(). 这三个版本用于相应维数的纹理, 例如如果纹理是3D纹理,则需要有 glTexImage3D() 来指定。

4.1 2D 纹理

 
void glTexImage2D (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* texels);

    参数 target 是 GL_TEXTURE_2D (二维纹理) 或 GL_PROXY_TEXTURE_2D (二维代理纹理), 代理纹理暂且不提。

    参数 level 指定了纹理映射细节的级别,用在mipmap中。 基本的纹理图像级别为0, 在后面的mipmap部分讲解。

    参数 internalFormat 指定了纹理存储在显存中的内部格式, 取值在下表, 为兼容 OpenGL1.0 internalFormat 可以取值 1,2,3,4 分别对应常量 LUMINANCE, LUMINANCE_ALPHA,  RGB,  RGBA.

纹理内部格式
格式 注解
GL_ALPHA Alpha 值
GL_DEPTH_COMPONENT 深度值
GL_LUMINCE 灰度值
GL_LUMINANCE_ALPHA 灰度值和 Alpha 值
GL_INTENSITY 亮度值
GL_RGB Red, Green, Blue三原色值
GL_RGBA Red, Green, Blue 和 Alpha 值

    参数 width 和 height 定义了纹理映射的大小,前面已经说过纹理映射就是一个二维数组。 和 glDrawPixels() 一样, 纹理映射的宽度和高度必须是 2 的整数次幂。

    参数 border 注明了纹理是否有边框。无边框取值为 0, 有边框取值为 1, 边框的颜色由 GL_TEXTURE_BORDER_COLOR 选项设置。

    接下来的三个参数主要定义了图像数据的格式。

    参数 format 定义了图像数据数组 texels 中的格式。可以取值如下:

图像数据数组 texels 格式
格式 注解
GL_COLOR_INDEX 颜色索引值
GL_DEPTH_COMPONENT 深度值
GL_RED 红色像素值
GL_GREEN 绿色像素值
GL_BLUE 蓝色像素值
GL_ALPHA Alpha 值
GL_RGB Red, Green, Blue 三原色值
GL_RGBA Red, Green, Blue 和 Alpha 值
GL_BGR Blue, Green, Red 值
GL_BGRA Blue, Green, Red 和 Alpha 值
GL_LUMINANCE 灰度值
GL_LUMINANCE_ALPHA 灰度值和 Alpha 值

    参数 type 定义了图像数据数组 texels 中的数据类型。可取值如下

图像数据数组 texels 中数据类型
数据类型 注解
GL_BITMAP 一位(0或1)
GL_BYTE 带符号8位整形值(一个字节)
GL_UNSIGNED_BYTE 不带符号8位整形值(一个字节)
GL_SHORT 带符号16位整形值(2个字节)
GL_UNSIGNED_SHORT 不带符号16未整形值(2个字节)
GL_INT 带符号32位整形值(4个字节)
GL_UNSIGNED_INT 不带符号32位整形值(4个字节)
GL_FLOAT 单精度浮点型(4个字节)
GL_UNSIGNED_BYTE_3_3_2 压缩到不带符号8位整形:R3,G3,B2
GL_UNSIGNED_BYTE_2__3_REV 压缩到不带符号8位整形:B2,G3,R3
GL_UNSIGNED_SHORT_5_6_5 压缩到不带符号16位整形:R5,G6,B5
GL_UNSIGNED_SHORT_5_6_5_REV 压缩到不带符号16位整形:B5,G6,R5
GL_UNSIGNED_SHORT_4_4_4_4 压缩到不带符号16位整形:R4,G4,B4,A4
GL_UNSIGNED_SHORT_4_4_4_4_REV 压缩到不带符号16位整形:A4,B4,G4,R4
GL_UNSIGNED_SHORT_5_5_5_1 压缩到不带符号16位整形:R5,G5,B5,A1
GL_UNSIGNED_SHORT_1_5_5_5_REV 压缩到不带符号16位整形:A1,B5,G5,R5
GL_UNSIGNED_INT_8_8_8_8 压缩到不带符号32位整形:R8,G8,B8,A8
GL_UNSIGNED_INT_8_8_8_8_REV 压缩到不带符号32位整形:A8,B8,G8,R8
GL_UNSIGNED_INT_10_10_10_2 压缩到32位整形:R10,G10,B10,A2
GL_UNSIGNED_INT_2_10_10_10_REV 压缩到32位整形:A2,B10,G10,R10

    你可能会注意到有压缩类型, 先看看 GL_UNSIGNED_BYTE_3_3_2, 所有的 red, green 和 blue 被组合成一个不带符号的8位整形中,在 GL_UNSIGNED_SHORT_4_4_4_4 中是把 red, green , blue 和 alpha 值打包成一个不带符号的 short 类型。

    最后一个参数是 texels, 这个指针指向实际的图像数据(你自己生成的或是从文件中加载的)。OpenGL 会按照 type 参数指定的格式来读取这些数据,

    例如, 假设你加载了一个 RGBA 图像到 textureData 中( 宽高为 textureWidth, textureHeight).你想要用它来指定一个纹理, 可以这样做:

glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, textureWidth, textureHeight, 0,
        GL_RGBA, GL_UNSIGNED_BYTE, textureData);

    执行完这个函数后, 纹理会加载,等待被使用。

4.2 1D 纹理

    1D 纹理其实就是 2D 纹理的特殊形式(高度等于1)。这类纹理常常用来描绘颜色边界从而创造出阴影效果。创建 1D 纹理的函数如下:

 
void glTExImage1D (GLenum target, GLint level, GLint internalFormat, GLsizei width,
GLint border, GLenum format, GLenum type, const GLvoid *texels);

    函数中的参数同 glTexImage2D(), 不同的是 height 参数没有被给出(因为定值1), 参数 target 也需要指定为 *GL_TEXTURE_1D*。

    下面是简单的代码, 用于创建32个纹理点宽度的 RGBA 纹理:

unsigned char imageData[128];
...
glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

4.3 3D 纹理

    创建 3D 纹理的函数:

 
glTexImage3D(GLenum target, GLint level, GLint internalFormat, GLsizei width GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *texels);

    函数参数同 glTexImage1D()  glTexImage2D() 大部分相同,不同的是多了一个深度参数 depth, 指定了纹理的第三维。

    下面的代码片段, 用于创建一个 16*16*16 个纹理点的 RGB 纹理:

...
glTexImage3D (GL_TEXTURE_3D, 0, GL_RGB, 16, 16, 16, 0, GL_RGB, 
                GL_UNSIGNED_BYTE, imageData);

4.4 Cube Map 纹理

    一个 Cube Map 纹理是由6个2D纹理组成。对应的, 需要通过 glTexImage2D() 来指定6个 target 参数: GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X,  GL_TEXTURE_CUBE_MAP_POSITIVE_Y,  GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z,  GL_TEXTURE_CUBE_MAP_NEGATIVE_Z.

5 纹理过滤

    将纹理映射到多边形上, 实际上是将纹理的图像数据空间映射到帧缓冲图像空间上。所以, 你必须保证纹理图像加载完成。 纹理图像被映射到多边形上可能会造成失真。纹理图像映射到多边形上去,屏幕上的一个点可能是纹理点的一个部分(如果视口设置的离纹理很近), 也有可能屏幕上的一个像素点是多个纹理的集合(如果视口设置的足够远). 纹理过滤就是告诉 OpenGL 在纹理到屏幕像素点的映射中如何计算最终显示的图像数据。

    在纹理过滤中, 放大器处理一个屏幕像素点代表一个纹理点的一部分的情况;缩小器处理一个像素点代表多个纹理点的情况. 你可以通过下面函数来告诉 OpenGL 怎样处理这两种情况:

 
void glTexParameter{if}(GLenum target, GLenum pname, T param);
void glTexParameter{if}v(GLenum target, GLenum pname, T params);
glTexParameter 不仅仅设置放大器和缩小器, 在本章中,由于只涉及纹理,所以只讨论纹理相关的参数取值.

   参数 target 指的是纹理目标, 可以是 GL_TEXTURE_1D, GL_TEXTURE_2D*, GL_TEXTURE_3D  GL_TEXTURE_CUBE_MAP 。 指定纹理放大过滤器需要指定参数 pname 为 GL_TEXTURE_MAG_FILTER, 指定纹理缩小过滤器需要指定参数 pname 为 GL_TEXTURE_MIN_FILTER.

当指定为 GL_TEXTURE_MAG_FILTER, 参数 param 取值 GL_NEAREST  GL_LINEAR. 对放大过滤器而言, 使用 GL_NEAREST 将告诉 OpenGL 使用离像素点中心最近的纹理来渲染, 这被称作 点样( point sampling); 使用 GL_LINEAR 告诉 OpenGL 会使用离像素点中心最近的四个纹理的平均值来渲染. 这被称作 双线性过滤( bilinear filtering)。

缩小过滤器比放大过滤器的取值更广, 下表是指定缩小过滤器时, 参数 param 的取值, 下面表中的值是为了增强渲染质量。

缩小过滤器的参数
过滤参数 注解
GL_NEAREST 使用像素点中心最近的点渲染
GL_LINEAR 使用双线性过滤
GL_NEAREST_MIPMAP_NEAREST  
GL_NEAREST_MIPMAP_LINEAR  
GL_LINEAR_MIPMAP_NEAREST  
GL_LINEAR_MIPMAP_LINEAR  

    在缩小过滤器中, 有4个参数处理mipmap, 这将会在后面的mipmap部分讲解。

默认情况下, 放大过滤器的参数为 GL_LINEAR, 缩小过滤器为 GL_NEAREST_MIPMAP_LINEAR.

在渲染纹理时, OpenGL 会先检查当前的纹理是否加载完成,同时也会处理其他事情,如在选用缩小过滤器的mipmap处理时会验证mipmap的所有级别是否被定义。 如果纹理未完成, 纹理会被禁用。因为缩小过滤器的缺省值使用mipmap,所以你必须指定所有的mipmap级别或是将缩小过滤器的参数设为 *GL\_LINEAR* 或*GL\_NEAREST*.
 

6 简单例程

    在初始化函数 init() 中创建了纹理对象, 设定了过滤模式:

bool CGfxOpenGL::init ()
{
    glClearColor (0.0f, 0.0f, 0.0f, 0.0f);

    // 启用 2D 纹理
    glEnable (GL_TEXTURE_2D);

    m_textureOne = new CTargaImage;

    // 加载纹理图像
    if (!m_textureOne->Load ("rock.tga"))
        return false;

    // 创建纹理对象,
    glGenTextures (1, &m_textureObjectOne);

    // 绑定纹理对象
    glBindTexture (GL_TEXTURE_2D, m_textureObjectOne);

    // 设定缩放器的过滤参数
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    // 为纹理对象指定纹理图像数据
    glTExImage2D (GL_TEXTURE_2D, 0, GL_RGB, m_textureOne->GetWidth(),
                  m_textureOne->GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE,
                  m_textureOne->GetImage());

    // 创建第二个纹理对象
    glGenTexture (1, &m_textureObjectTown);
    glBindTexture (GL_TEXTURE_2D, m_textureObjectTwo);

    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, m_textureOne->GetWidth(),
                  m_textureOne->GetHeight(), 0, GL_TGB, GL_UNSIGNED_BYTE,
                  m_textureOne->GetImage());

    // 初始化运动变量
    m_zPos = -0.5f;
    m_zMoveNegative = true;

    return true;
}

    在 init() 函数中, 我们先启用 2D 纹理( glEnable() ), 然后加载图像到 CTargaImage 类中(详见第6章), 然后通过 glGenTextures() 获得一个未被使用的纹理对象, 继而绑定, 指定缩放器的过滤模式, 最后为纹理指定图像数据(通过 glTexImage2D() ). 然后同样的流程创建了第二个纹理对象, 使用了同样的纹理图像数据。只是缩放器的过滤参数做了下更改。

    主要的渲染函数有两个 DrawPlane(), Render() :

void CGfxOpenGL::DrawPlane ()
{
    glBegin (GL_TRIANGLE_STRIP);
        glTexCoord2f (1.0, 0.0); glVertex3f (2.0, -2.0, -2.0);
        glTexCoord2f (0.0, 0.0); glVertex3f (-2.0, -2.0, -2.0);
        glTexCoord2f (1.0, 1.0); glVertex3f (2.0, -2.0, 2.0);
        glTexCoord2f (0.0, 1.0); glVertex3f (-2.0, -2.0, 2.0);
    glEnd();
}

void CGfxOpenGL::Render ()
{
    // 清除屏幕和深度缓存
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 重置当前矩阵
    glLoadIdentity ();

    // 绘制左边的多边形
    glPushMatrix ();
            //
            glTranslatef (-3.0, 0.0, m_zPos);
            glRotatef (90.0, 1.0, 0.0, 0.0);
        
            // 绑定纹理
            glBindTexture (GL_TEXTURE_2D, m_textureObjectOne);
        
            // 绘制 Plane
            DrawPlane ();
    glPopMatrix();

    // 同样地, 绘制右边多边形
    glPushMatrix ();
            glTranslatef (3.0, 0.0, m_zPos);
            glRotatef (90.0, 1.0, 0.0, 0.0);
            glBindTexture (GL_TEXTURE_2D, m_textureObjectTwo);
            DrawPlane ();
    glPopMatrix();

}

    在 DrawPlane() 中, 我们指定了纹理坐标然后绘制多边形的顶点。在 Render() 中,我们先绑定好纹理对象, 然后绘制多边形。


 

Date: 2010-06-25 22:37:45 CST

HTML generated by org-mode 6.33x in emacs 23

 

Avatar_small
JAC Question Paper C 说:
2022年9月01日 19:02

Jharkhand Board Model Paper 2023 Class 3 Pdf Download with Answers for English Medium, Hindi Medium, Urdu Medium & Students for Small Answers, Long Answer, Very Long Answer Questions, and Essay Type Questions to Term1 & Term2 Exams at official website. JAC Question Paper Class 3 New Exam Scheme or Question Pattern for Sammittive Assignment Exams (SA1 & SA2): Very Long Answer (VLA), Long Answer (LA), Small Answer (SA), Very Small Answer (VSA), Single Answer, Multiple Choice and etc.

Avatar_small
jnanabhumiap.in 说:
2024年1月23日 14:39

JNANABHUMI AP provides all the latest educational updates and many more. The main concept or our aim behind this website has been the will to provide resources with full information on each topic jnanabhumiap.in which can be accessed through the Internet. To ensure that every reader gets what is important and worthy about the topic they search and link to hear from us.


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter