七、OpenGL 纹理

167 阅读10分钟

一、原始图像数据

图像存储空间 = 图像的高度 * 图像的宽度 * 每个像素的字节数

二、纹理相关函数

API文档

1.1 设置像素存储方式

glPixelStore

// 改变/恢复像素的存储方式 
void glPixelStoref (GLenum pname, GLfloat param);
void glPixelStorei (GLenum pname, GLint param);

pname: 指定参数的要设置的内容
param: 指定 pname 代表的属性的值

eg:
glPixelStoref(GL_UNPACK_ALIGNMENT, 1)

pname=GL_UNPACK_ALIGNMENT, 设置从缓存区中解包图像数据的格式
param=1, 这个数值一般是1、2、4或8,用于指定存储器中每个像素行有多少个字节对齐。对齐的字节数越高,系统就越能优化。

1.2 把颜色缓冲区内容作为像素图读取

glReadPixels

void glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);
参数 说明
x 矩形左下角的窗口坐标
y 矩形右下角的窗口坐标
width 矩形的宽,以像素为单位
height 矩形的高,以像素为单位
format 像素格式,参考附表一
type 存储像素数据的数据类型,参考附表二
pixels 图形数据存储的指针
// 指定读取的缓存
void glReadBuffer (GLenum mode);
// 指定写入的缓存
void glDrawBuffer (GLenum mode);

下面的纹理相关方法,都是存储或修改纹理数据到,当前指定 target 绑定的纹理数组

1.3 载入纹理

常用 glTexImage2D

void glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);

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

void glTexImage3D (GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
参数 说明
target GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
level 指定所加载的mip贴图层次,一般为 0
internalFormat 每个纹理单元存储多少颜色成分
width、height、 depth 加载纹理的宽度、高度、深度。习惯上设置为2的整数次方
border 允许为纹理贴图指定一个边界宽度
format 像素格式,参考附表一
type 存储像素数据的数据类型,参考附表二
pixels 图形数据存储的指针

1.4 更新纹理

void glTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const void* pixels);

void glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels);

void glTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels);

1.5 插入替换纹理

void glCopyTexSubImage1D (GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);

void glCopyTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);

void glCopyTexSubImage3D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);

1.6 从颜色缓冲区加载数据,形成新的纹理使用

void glCopyTexImage1D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border);

void glCopyTexImage2D (GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border);

1.7 纹理对象

// 使用函数分配纹理对象
// 指定纹理对象的数量 和 指针。
// textures 生成的纹理标识符存储的数组指针
void glGenTextures (GLsizei n, GLuint *textures);

// 绑定纹理状态
// target: `GL_TEXTURE_1D`、`GL_TEXTURE_2D`、 `GL_TEXTURE_3D`
void glBindTexture (GLenum target, GLuint texture);

// 删除绑定的纹理对象
// n: 被删除的纹理数量,即数组的大小
void glDeleteTextures (GLsizei n, const GLuint *textures);

// 测试纹理对象是否有效
GLboolean glIsTexture (GLuint texture);

1.8 设置纹理参数

glTexParameter

void glTexParameterf (GLenum target, GLenum pname, GLfloat param);

void glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params);

void glTexParameteri (GLenum target, GLenum pname, GLint param);

void glTexParameteriv (GLenum target, GLenum pname, const GLint *params);

target: GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
pname: 指定需要设置的纹理参数
param: 指定需要设置的纹理参数的值
params: 指定需要设置的纹理参数的数组值

1.9 设置过滤方式

邻近过滤(GL_NEAREST) / 线性过滤(GL_LINEAR)

一张小图片填充到大的视图后的效果区别

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

缩小时使用线性过滤,放大时使用邻近过滤

1.10 设置环绕方式

环绕方式 描述
GL_REPEAT 对纹理的默认行为。重复纹理图像。
GL_MIRRORED_REPEAT 和GL_REPEAT一样,但每次重复图片是镜像放置的。
GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。

eg.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

target: GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
pname: GL_TEXTURE_WRAP_SGL_TEXTURE_TGL_TEXTURE_R,针对s,t,r坐标 param: GL_REPEATGL_MIRRORED_REPEATGL_CLAMP_TO_EDGEGL_CLAMP_TO_BORDER

1.11 设置 Mip 贴图

根据纹理生成多个不同尺寸的贴图,根据目标尺寸适配不同大小贴图。

void glTexParameteri (GLenum target, GLenum pname, GLint param);

eg.

// 设置 Mip 贴图基层
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
// 设置 Mip 贴图最大层
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

经过Mip贴图的纹理过滤

常量 描述
GL_NEAREST 在Mip基层上执⾏最邻近过滤
GL_LINEAR 在Mip基层执⾏线性过滤
GL_NEAREST_MIPMAP_NEAREST 在最邻近Mip层,并执⾏最邻近过滤
GL_NEAREST_MIPMAP_LINEAR 在Mip层之间执⾏线性插补,并执⾏最邻近过滤
GL_LINEAR_MIPMAP_NEAREST 选择最邻近Mip层,并执⾏线性过滤
GL_LINEAR_MIPMAP_LINEAR 在Mip层之间执⾏线性插补,并执⾏线性过滤,⼜称三线性Mip贴图

附表一: OpenGL 像素格式

常量 描述
GL_RGB 描述红、绿、蓝顺序排列列的颜色
GL_RGBA 按照红、绿、蓝、Alpha顺序排列的颜色
GL_BGR 按照蓝、绿、红顺序排列颜⾊
GL_BGRA 按照蓝、绿、红、Alpha顺序排列颜⾊
GL_RED 每个像素只包含了一个红色分量
GL_GREEN 每个像素只包含了一个绿⾊分量
GL_BLUE 每个像素只包含了一个蓝⾊分量
GL_RG 每个像素依次包含了一个红⾊和绿⾊的分量
GL_RED_INTEGER 每个像素包含了⼀个整数形式的红⾊分量
GL_GREEN_INTEGER 每个像素包含了⼀个整数形式的绿⾊分量
GL_BLUE_INTEGER 每个像素包含了⼀个整数形式的蓝⾊⾊分量
GL_RG_INTEGER 每个像素依次包含了⼀个整数形式的红⾊、绿⾊分量
GL_RGB_INTEGER 每个像素包含了⼀个整数形式的红⾊、蓝⾊、绿⾊分量
GL_RGBA_INTEGER 每个像素包含了⼀个整数形式的红⾊、蓝⾊、绿⾊、Alpah分量
GL_BGR_INTEGER 量 每个像素包含了⼀个整数形式的蓝⾊、绿⾊、红⾊分量
GL_BGRA_INTEGER 每个像素包含了⼀个整数形式的蓝⾊、绿⾊、红⾊、Alpah分量
GL_STENCIL_INDEX 量 每个像素只包含了⼀个模板值
GL_DEPTH_COMPONENT 每个像素值包含一个深度值
GL_DEPTH_STENCIL 每个像素包含一个深度值和一个模板值

附表二: OpenGL 像素格式

三、纹理坐标

纹理的坐标是固定的,但是映射到其他坐标的方式可以自行设置。如下图:

四、案例:绘制纹理金字塔

整体流程

完整代码

#include "GLTools.h"
#include "GLShaderManager.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLFrame.h"
#include "GLMatrixStack.h"
#include "GLGeometryTransform.h"

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

GLShaderManager		shaderManager;
GLMatrixStack		modelViewMatrix;
GLMatrixStack		projectionMatrix;
GLFrame				cameraFrame;
GLFrame             objectFrame;
GLFrustum			viewFrustum;

GLBatch             pyramidBatch;

//纹理变量,一般使用无符号整型
GLuint              textureID;

GLGeometryTransform	transformPipeline;
M3DMatrix44f		shadowMatrix;

//绘制金字塔
void MakePyramid(GLBatch& pyramidBatch)
{
    /*1、通过pyramidBatch组建三角形批次
      参数1:类型
      参数2:顶点数
      参数3:这个批次中将会应用1个纹理
      注意:如果不写这个参数,默认为0。
     */
    pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
    
    /***前情导入
     
     2)设置纹理坐标
     void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
     参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
     参数2:s:对应顶点坐标中的x坐标
     参数3:t:对应顶点坐标中的y
     (s,t,r,q对应顶点坐标的x,y,z,w)
     
     pyramidBatch.MultiTexCoord2f(0,s,t);
     
     3)void Vertex3f(GLfloat x, GLfloat y, GLfloat z);
      void Vertex3fv(M3DVector3f vVertex);
     向三角形批次类添加顶点数据(x,y,z);
      pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
    
     */
    
    //塔顶
    M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
    M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
    M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
    M3DVector3f vBackLeft = { -1.0f,  -1.0f, -1.0f };
    M3DVector3f vBackRight = { 1.0f,  -1.0f, -1.0f };
    
    //金字塔底部
    //底部的四边形 = 三角形X + 三角形Y
    //三角形X = (vBackLeft,vBackRight,vFrontRight)
    //vBackLeft
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vBackRight
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    //vFrontRight
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    
    //三角形Y =(vFrontLeft,vBackLeft,vFrontRight)
    //vFrontLeft
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //vBackLeft
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vFrontRight
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3fv(vFrontRight);

    
    // 金字塔前面
    //三角形:(Apex,vFrontLeft,vFrontRight)
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);

    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);

    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    //金字塔左边
    //三角形:(vApex, vBackLeft, vFrontLeft)
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //金字塔右边
    //三角形:(vApex, vFrontRight, vBackRight)
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);

    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    //金字塔后边
    //三角形:(vApex, vBackRight, vBackLeft)
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //结束批次设置
    pyramidBatch.End();
}

// 将TGA文件加载为2D纹理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;
    
    //1、读纹理位,读取像素
    //参数1:纹理文件名称
    //参数2:文件宽度地址
    //参数3:文件高度地址
    //参数4:文件组件地址
    //参数5:文件格式地址
    //返回值:pBits,指向图像数据的指针
    
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if(pBits == NULL)
        return false;
    
    //2、设置纹理参数
    //参数1:纹理维度
    //参数2:为S/T坐标设置模式
    //参数3:wrapMode,环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    //参数1:纹理维度
    //参数2:线性过滤
    //参数3: 缩小/放大过滤方式.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    

    //3.载入纹理
    //参数1:纹理维度
    //参数2:mip贴图层次
    //参数3:纹理单元存储的颜色成分(从读取像素图是获得)
    //参数4:加载纹理宽
    //参数5:加载纹理高
    //参数6:加载纹理的深度
    //参数7:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
    //参数8:指向纹理图像数据的指针
    
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
    
    //使用完毕释放pBits
    free(pBits);
    
    //只有minFilter 等于以下四种模式,才可以生成Mip贴图
    //GL_NEAREST_MIPMAP_NEAREST具有非常好的性能,并且闪烁现象非常弱
    //GL_LINEAR_MIPMAP_NEAREST常常用于对游戏进行加速,它使用了高质量的线性过滤器
    //GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 过滤器在Mip层之间执行了一些额外的插值,以消除他们之间的过滤痕迹。
    //GL_LINEAR_MIPMAP_LINEAR 三线性Mip贴图。纹理过滤的黄金准则,具有最高的精度。
    if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
       minFilter == GL_LINEAR_MIPMAP_NEAREST ||
       minFilter == GL_NEAREST_MIPMAP_LINEAR ||
       minFilter == GL_NEAREST_MIPMAP_NEAREST)
    //4.纹理生成所有的Mip层
    //参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    glGenerateMipmap(GL_TEXTURE_2D);

    return true;
}

void SetupRC() {
    //1.
    glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
    shaderManager.InitializeStockShaders();
    
    //2.
    glEnable(GL_DEPTH_TEST);

    //3.
    //分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针
    glGenTextures(1, &textureID);
    
    //绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //将TGA文件加载为2D纹理。
    //参数1:纹理文件名称
    //参数2&参数3:需要缩小&放大的过滤器
    //参数4:纹理坐标环绕模式
    LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
    
    //4.创造金字塔pyramidBatch
    MakePyramid(pyramidBatch);
    
    //5.
    /**相机frame MoveForward(平移)
    参数1:Z,深度(屏幕到图形的Z轴距离)
     */
    cameraFrame.MoveForward(-10);
}


// 清理…例如删除纹理对象
void ShutdownRC(void)
{
    glDeleteTextures(1, &textureID);
}

void RenderScene(void)
{
    //2.清理缓存区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    
    //3.当前模型视频压栈
    modelViewMatrix.PushMatrix();
    
    //添加照相机矩阵
    M3DMatrix44f mCamera;
    //从camraFrame中获取一个4*4的矩阵
    cameraFrame.GetCameraMatrix(mCamera);
    //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将照相机矩阵 与 当前模型矩阵相乘 压入栈顶
    modelViewMatrix.MultMatrix(mCamera);
    
    //创建mObjectFrame矩阵
    M3DMatrix44f mObjectFrame;
    //从objectFrame中获取矩阵,objectFrame保存的是特殊键位的变换矩阵
    objectFrame.GetMatrix(mObjectFrame);
    //矩阵乘以矩阵堆栈顶部矩阵,相乘结果存储到堆栈的顶部 将世界变换矩阵 与 当前模型矩阵相乘 压入栈顶
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //4.绑定纹理,因为我们的项目中只有一个纹理。如果有多个纹理。绑定纹理很重要
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //5.纹理替换矩阵着色器
     /*
     参数1:GLT_SHADER_TEXTURE_REPLACE(着色器标签)
     参数2:模型视图投影矩阵
     参数3:纹理层
     */
    
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
    
    //pyramidBatch 绘制
    pyramidBatch.Draw();
    
    //模型视图出栈,恢复矩阵(push一次就要pop一次)
    modelViewMatrix.PopMatrix();
    
    //6.交换缓存区
    glutSwapBuffers();
}



void SpecialKeys(int key, int x, int y)
{
    if(key == GLUT_KEY_UP)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_DOWN)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 1.0f, 0.0f, 0.0f);
    
    if(key == GLUT_KEY_LEFT)
        objectFrame.RotateWorld(m3dDegToRad(-5.0f), 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        objectFrame.RotateWorld(m3dDegToRad(5.0f), 0.0f, 1.0f, 0.0f);
    
    glutPostRedisplay();
}

void ChangeSize(int w, int h)
{
    //1.设置视口
    glViewport(0, 0, w, h);
    
    //2.创建投影矩阵
    viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
  
    //viewFrustum.GetProjectionMatrix()  获取viewFrustum投影矩阵
    //并将其加载到投影矩阵堆栈上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    //3.设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)
    //初始化GLGeometryTransform 的实例transformPipeline.通过将它的内部指针设置为模型视图矩阵堆栈 和 投影矩阵堆栈实例,来完成初始化
    //当然这个操作也可以在SetupRC 函数中完成,但是在窗口大小改变时或者窗口创建时设置它们并没有坏处。而且这样可以一次性完成矩阵和管线的设置。
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}


int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
    
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Pyramid");
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
    
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    
    SetupRC();
    
    glutMainLoop();
    
    ShutdownRC();
    
    return 0;
}

store.tga 资源地址