OpenGL学习——纹理贴图一

本篇文章为纹理贴图的第一次学习,没太多时间去深入的学,把每个函数都搞得明明白白,所以这里自己的理解写的比较少。别人总结的还蛮好的,所以我主要是总结了几篇其他文章的内容,如果不去深究每个函数,我觉得足以让你明白纹理贴图的一个大致操作流程。

这篇就当做入门的吧,纹理映射还是比较难的,后面肯定会结合着代码再写的。

开篇先放一个大致流程,方便以后快速查看:

启用2D纹理
加载纹理图像(即导入图片)
创建纹理对象
绑定纹理对象
设定纹理过滤的参数
为纹理对象指定纹理图像数据
纹理映射,绘制纹理图像

一、创建纹理对象,并为它指定一个纹理

启用纹理贴图

glEnable(GL_TEXTURE_2D);

定义一个纹理对象编号

static GLuint texName;

创建一个纹理对象,&texName指向纹理索引

glGenTextures(1, &texName);

绑定纹理:改变OpenGL状态,使得后续的纹理操作都对texName指向的2D纹理生效

glBindTexture(GL_TEXTURE_2D, texName);

二、载入纹理,设置纹理参数

  1. 纹理过滤函数

glTexParameteri()

图象从纹理图象空间映射到帧缓冲图象空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),这时就可用glTexParmeteri()函数来确定如何把纹理象素映射成像素。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

这些参数先不用管,就是说贴图时的各种限制和模式。

以后还会总结的。

  1. 指定纹理贴图与材质的混合模式

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

材质这个不是太懂。

  1. 为纹理对象指定一个纹理

glTexImage2D (
GLenum target, //指定的目标,可以使用GL_TEXTURE_2D
GLint level, //“多重细节层次”,不考虑多重纹理的话设置为零。
GLint internalformat,//RGB数据存储的格式,比如GL_RGB
GLsizei width,
GLsizei height, //二维纹理像素的宽度和高度。
GLint border, //纹理边框的大小,不使用纹理边框设置为零。
GLenum format, //
GLenum type, //数据格式和数据保存形式。
const GLvoid *pixels //保存了纹理图像的内参块地址。
);

此函数的参数可以像下面这样设置:

glTexImage2D(
GL_TEXTURE_2D,        //target,目标纹理
0, 			          //分辨率级数参数,默认为0
GL_RGBA, 	       	  //纹理单元格式
checkImageWidth,      //纹理图像的宽
checkImageHeight,     //纹理图像的高
0, 			          //纹理图像边框的宽度,0或1
GL_RGBA,              //纹理像素数据的格式
GL_UNSIGNED_BYTE, 	  //像素数据类型
checkImage	          //内存中指向纹理图像的指针
);

三、纹理映射,绘制纹理图像

纹理映射跟颜色的绘制一样,需要指定每一个顶点在纹理图像中所对应的位置,OpenGL会自动计算出顶点之间的其他点在纹理图像中应该对应的位置。这里注意纹理图像的座标范围是从(0,0)到(1,1),左下角为(0,0),右上角为(1,1)。比如,将图像映射到多个四边形上:

    // 绘制底面以及纹理
    glBindTexture(GL_TEXTURE_2D, texSea);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f);   //纹理座标配置函数
    glVertex3f(-8.0f, -8.0f, 0.0f);
    glTexCoord2f(0.0f, 3.0f);
    glVertex3f(-8.0f, 8.0f, 0.0f);
    glTexCoord2f(3.0f, 3.0f);
    glVertex3f(8.0f, 8.0f, 0.0f);
    glTexCoord2f(3.0f, 0.0f);
    glVertex3f(8.0f, -8.0f, 0.0f);
    glEnd();

此代码显示3行3列个位图(共9个)(如果座标是小于1的,那就是正常的映射),座标可以这样映射:(0.0f, 0.0f)、(0.0f, 3.0f)、(3.0f, 3.0f)、(3.0f, 0.0f)。

glTexCoord2f()主要与glVertex3f()配合使用,glTexCoord2f()是配置纹理座标,glVertex3f()是配置图形座标。

四、其他补充

参考文章:
https://blog.csdn.net/dcrmg/article/details/53180369

更详细的解释可以看这篇文章(我以后会再总结):

https://www.cnblogs.com/sunliming/archive/2011/07/17/2108917.html

下面这个代码的原文章有大致这样说明,读取纹理图片到内存时,位图的大小有规定的大小,宽度什么的有些要求,如果长宽有超过当前版本所支持最大长宽数值,还需要对图像进行缩放。这里我们先不管这个格式问题了,给的位图是标准的。

还有代码中关于读取位图的操作,我觉得有点麻烦,我看有些直接使用stb_image.h来加载图像,这个我还没有研究,但肯定比这篇文章的示例代码简单些。

他这个代码是手动分配内存的,就先别管了,重点是看纹理的使用流程。

ps:给的代码放到桌面上运行。
在这里插入图片描述

#define WindowWidth  400
#define WindowHeight 400
#define WindowTitle  "纹理贴图"

#include <gl/glut.h>
#include <stdio.h>
#include <stdlib.h>
#define GL_BGR_EXT 0x80E0   //我这个版本找不到这个宏,需要自己定义

//定义两个纹理对象编号
GLuint texSea;
GLuint texSky;

#define BMP_Header_Length 54  //图像数据在内存块中的偏移量

static GLfloat angle = 0.0f;   //旋转角度

//读取一个BMP文件作为纹理。如果失败,返回0,如果成功,返回纹理编号.
//这个代码的处理位图的操作太麻烦了,可以先不管,知道他把位图保存到了一个字节变量pixel中就好
GLuint load_texture(const char* file_name)
{
    GLint width, height, total_bytes;
    GLubyte* pixels = 0;
    GLuint last_texture_ID=0, texture_ID = 0;

    // 打开文件,如果失败,返回
    FILE* pFile = fopen(file_name, "rb");
    if( pFile == 0 )
    {
        printf("Wrong!!!!\n");
        return 0;
    }

    // 读取文件中图象的宽度和高度
    fseek(pFile, 0x0012, SEEK_SET);
    fread(&width, 4, 1, pFile);
    fread(&height, 4, 1, pFile);
    fseek(pFile, BMP_Header_Length, SEEK_SET);

    // 计算每行像素所占字节数,并根据此数据计算总像素字节数
    GLint line_bytes = width * 3;
    while( line_bytes % 4 != 0 )
        ++line_bytes;
    total_bytes = line_bytes * height;

    // 根据总像素字节数分配内存
    pixels = (GLubyte*)malloc(total_bytes);
    if( pixels == 0 )
    {
        fclose(pFile);
        return 0;
    }

    // 读取像素数据
    if( fread(pixels, total_bytes, 1, pFile) <= 0 )
    {
        free(pixels);
        fclose(pFile);
        return 0;
    }


    // 分配一个新的纹理编号
    glGenTextures(1, &texture_ID);
    if( texture_ID == 0 )
    {
        free(pixels);
        fclose(pFile);
        return 0;
    }

    // 绑定新的纹理,载入纹理并设置纹理参数
    // 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
    GLint lastTextureID = last_texture_ID;
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &lastTextureID);

    glBindTexture(GL_TEXTURE_2D, texture_ID);   //绑定纹理

    //设置4个常用的纹理参数。。如何把纹理像素映射成像素
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //指定纹理贴图与材质的混合模式

    //参数: 目标纹理,多重细节层次(不考虑多重纹理为0),RGB数据存储格式,二维纹理像素宽高,纹理边框大小
    //       纹理像素数据的格式,数据保存形式(像素数据类型,字节),内存中指向纹理图像的指针(纹理图像的内参快地址)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
                 GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels); //为纹理对象指定一个纹理

    glBindTexture(GL_TEXTURE_2D, lastTextureID);  //恢复之前的纹理绑定

    free(pixels);
    return texture_ID;
}


void display(void)
{
    // 清除屏幕
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 设置视角和观察点
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(75, 1, 1, 21);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(-4, 7,7, 0, 0, 0, 0, 0, 1);

    glRotatef(angle, 0.0f, 0.0f, 1.0f); //旋转矩形,实现物体的旋转


    // 绘制底面以及纹理
    glBindTexture(GL_TEXTURE_2D, texSea);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f);   //纹理座标配置函数
    glVertex3f(-8.0f, -8.0f, 0.0f);
    glTexCoord2f(0.0f, 3.0f);
    glVertex3f(-8.0f, 8.0f, 0.0f);
    glTexCoord2f(3.0f, 3.0f);
    glVertex3f(8.0f, 8.0f, 0.0f);
    glTexCoord2f(3.0f, 0.0f);
    glVertex3f(8.0f, -8.0f, 0.0f);
    glEnd();
    //比如显示3行2列个位图(共6个),座标可以这样:(0.0f, 0.0f)、(0.0f, 3.0f)、(2.0f, 3.0f)、(2.0f, 0.0f)。
    //该函数主要与glVertex3f()配合使用,glTexCoord2f()是配置纹理座标,glVertex3f()是配置图形座标。

    // 绘制立面
    glBindTexture(GL_TEXTURE_2D, texSky);
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-6.0f, -3.0f, 0.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-6.0f, -3.0f, 5.0f);
    glTexCoord2f(2.0f, 1.0f);
    glVertex3f(6.0f, -3.0f, 5.0f);
    glTexCoord2f(2.0f, 0.0f);
    glVertex3f(6.0f, -3.0f, 0.0f);
    glEnd();

    //绘制另外一个立面
    glBegin(GL_QUADS);
    glTexCoord2f(2.0f, 0.0f);
    glVertex3f(6.0f, -3.0f, 0.0f);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(6.0f, 9.0f, 0.0f);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(6.0f, 9.0f, 5.0f);
    glTexCoord2f(2.0f, 1.0f);
    glVertex3f(6.0f, -3.0f, 5.0f);
    glEnd();

    glutSwapBuffers();
}

//旋转角度
void myIdle(void)
{
    angle += 0.01f; //1.8f
    if( angle >= 360.0f )
        angle = 0.0f;
    display();
}

int main(int argc, char* argv[])
{
    // GLUT初始化
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(WindowWidth, WindowHeight);
    glutCreateWindow(WindowTitle);

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

    texSea = load_texture("C:\\Users\\user\\Desktop\\贴图1_方块\\sea.bmp");
    texSky = load_texture("C:\\Users\\user\\Desktop\\贴图1_方块\\sky.bmp");  //加载纹理

    //绘图
    glutDisplayFunc(&display);
    glutIdleFunc(&myIdle);
    glutMainLoop();
    return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章