OpenGL

007-基础纹理上篇

2020-07-19  本文已影响0人  清风烈酒2157

[toc]

image.png

前言

首先,我们通过对点,线,三角形的进行渲染,也可以通过计算机颜色对它们进行着色,也可以进行插值操作来模拟灯光的效果,这一切都非常不错,但是为了更加真实的效果,还有一种非常棒的途径,那就是纹理贴图(texture mapping).

纹理是一种能应用到场景中的三角形图形数据,它通过经过过滤的纹理单元(telex,想当于基于纹理的像素)填充到实心区域.

原始图像数据

像素的包装

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

通过下面函数改变或恢复像素的存储方式


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

  1. 参数1:GL_UNPACK_ALIGNMENT 指定OpenGL 如何从数据缓存区中解包图像 数据
  2. 参数2:表示参数GL_UNPACK_ALIGNMENT 设置的值
  3. GL_UNPACK_ALIGNMENT 指内存中每个像素⾏行行起点的排列列请求,允许设置为1 (byte排列列)、2(排列列为偶数byte的⾏行行)、4(字word排列列)、8(⾏行行从双字节 边界开始)

举例


glPixelStorei(GL_UNPACK_ALIGNMENT,1);

glPixeIStore参数

GLenum pname

image.png

像素图

像素图在内存布局上和位图非常相似,但是每个像素需要一个以上的存储位来表示.

每个像素的附加为运行存储强度(intensity,有时被称为亮度即Iuminance)或颜色分量值.

OpenGL中无法将一个像素图绘制到颜色缓冲区中,可以使用下面函数将颜色缓冲区的内容作为像素图直接读取.

参数1:x,矩形左下⻆角的窗⼝口坐标
参数2:y,矩形左下⻆角的窗⼝口坐标
参数3:width,矩形的宽,以像素为单位
参数4:height,矩形的⾼高,以像素为单位
参数5:format,OpenGL 的像素格式
参数6:type,解释参数pixels指向的数据,告诉OpenGL 使⽤用缓存区中的什么数据类型来存储颜⾊色分量量,像素数据的数据类型

参数7:pixels,指向图形数据的指针


void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei
height, GLenum format, GLenum type,const void * pixels);

OpenGL像素格式

GLenum format

image.png

像素数据的数据类型

GLenum type

image.png

glReadPixels的读取操作在双缓存区渲染环境下在后台缓冲区进行

glReadPixels从图形硬件中复制数据,通常通过总线传输到系统内存.在这种情况下应用程序将被阻塞,知道内存传输完成.
此外,如果我们指定一个与图形硬件的本地排序不同的像素布局,那么在数据进行重定格式时将产生额外的性能开销.

包装的像素格式

包装的像素格式将颜色数据压缩到了尽可能少的存储位中,每个颜色通道的位数显示在常量中.

格式的第一个分量提供3位存储位,第一个也是,第三个提供2位.

这些数据从高位(MSB)到低位(LSB)进行排列.

image.png

单缓冲区渲染环境下则在前台缓冲区进行读取操作,使用函数如下


glReadBuffer(mode)

模式参数使用:

image.png

写入缓存


glWriteBuffer(mode)

读取像素

Traga图像格式是一种方便而且容易使用的图像格式.

读纹理位,读取像素
参数1:纹理文件名称
参数2:文件宽度地址
参数3:文件高度地址
参数4:文件组件地址
参数5:文件格式地址
返回值pBits,指向图像数据的指针


GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL);

载入纹理

在几何图形中应用纹理贴图,第一个必要的步骤就是将纹理载入内存.一旦被载入就会成为当前纹理状态的一部分.

OpenGL3函数经常用来从存储器缓冲区中裁入纹理数据.


void glTexImage1D(GLenum target,GLint level,GLint
     internalformat,GLsizei width,GLint border,GLenum
     format,GLenum type,void *data);
     
    
void glTexImage2D(GLenum target,GLint level,GLint
 internalformat,GLsizei width,GLsizei height,GLint
 border,GLenum format,GLenum type,void * data);
    

void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);

它们都是由glTextImage派生而来,OpenGL支持一维,二维,三维纹理贴图.

常见纹理内部格式
GLint nternalformat

image.png

使用颜色缓冲区

一维和二维纹理也可以从颜色缓冲区加载数据,也可以从颜色缓冲区读取一幅画像.

x,y 在颜⾊色缓存区中指定了了开始读取纹理理数据的位置; 缓存区⾥里里的数据,是源缓存区通过glReadBuffer设置的。

注意:不不存在glCopyTextImage3D ,因为我们无法从2D颜⾊缓存区中获取体积数据



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

更新纹理

替换一个纹理比重新加载一个新的纹理快得多.

常见函数:

大部分参数和glTextImage函数使用的参数准确对应.
xOffset yOffset zOffset参数指定在原来的纹理贴图中开始替换纹理参数的偏移量.
width height depth指定插入到原来那个纹理中的新的纹理的宽度,高度和深度.


void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum
    format,GLenum type,const GLvoid *data);

void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei
    width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);
    
void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint
    zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);



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

 void glCopyTexSubImage2D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint x,
     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);

纹理对象

纹理直接进行切换或者加载不同的纹理图像可能会是开销很大的操作.

纹理对象允许我们加载一个以上的纹理状态(包括纹理图像),以及他们之间快速切换.纹理状态是由当前绑定的纹理对象维护的,而纹理对象是由一个无符号整数表示的.


指定纹理理对象的数量量 和 指针(指针指向⼀一个⽆无符号整形数组,由纹理理对象标识符填充)。 

void glGenTextures(GLsizei n,GLuint * textTures);

参数target:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数texture:需要绑定的纹理理对象


void glBindTexture(GLenum target,GLunit texture);

纹理理对象 以及 纹理理对象指针(指针指向⼀一个⽆无符号整形数组,由纹理理对象标识符填充)


void glDeleteTextures(GLsizei n,GLuint *textures);


如果texture是⼀一个已经分配空间的纹理理对象,那么这个函数会返回GL_TRUE,否则会返回GL_FALSE。 

GLboolean glIsTexture(GLuint texture);

纹理应用

纹理坐标

纹理坐标是作为0.01.0范围内的浮点值指定的.

纹理坐标被命名为s,f,rq.

q对应几何左边中的w.

一维,二维,三维纹理坐标方式和纹理单元排列的纹理.

image.png

四边形中应用一个二维纹理

image.png

三角形应用纹理贴图中的一部分

image.png

纹理参数

纹理参数通过glTexParameter进行设置的.

参数1:target,指定这些参数将要应⽤用在那个纹理理模式上,⽐比如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数2:pname,指定需要设置那个纹理理参数
参数3:param,设定特定的纹理理参数的值


glTexParameterf(GLenum target,GLenum pname,GLFloat param);

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

glTexParameterfv(GLenum target,GLenum pname,GLFloat *param);

glTexParameteriv(GLenum target,GLenum pname,GLint *param);

基本过滤

纹理图像中的纹理单元和屏幕上的像素几乎从来不会形成一对一的关系.

根据几何图形的方向,一个特定的纹理甚至有可能会贴到一些物体表面的同时就开始进行拉伸收缩.

根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤(texture fililtering).

image.png
  1. 最邻近过滤

纹理坐标总是根据纹理图像的纹理单元进行求值绘图的.不管纹理坐标位于哪个纹理单元,这个纹理单元的颜色就作为这个片段的纹理颜色`.最邻近过滤最显著的特征就是当纹理被拉伸到特别大时所出现的大片斑驳庄像素.

使用函数


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

  1. 线性过滤

线性过滤并不是把最邻近的纹理单元应用打纹理坐标中,而是把把换个纹理坐标周围的纹理单元的加权平均值应用到这个纹理坐标上(线性插值).

使用函数


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

比较

image.png

纹理环绕

在正常情况下,我们在0.01.0范围内知道纹理坐标,使它与纹理贴图中的纹理单元形成映射关系,如果纹理坐落在这个范围之外,OpenGL根据当前的纹理环绕模式(Wrapping Mode)处理这个问题.

使用函数


GL_REPEAT:OpenGL 在纹理坐标超过1.0的⽅向上对纹理进行重复;GL_CLAMP:所需的纹理理单元取⾃自纹理理边界或TEXTURE_BORDER_COLOR. GL_CLAMP_TO_EDGE环绕模式强制对范围之外的纹理坐标沿着合法的纹理单元的最后一行或者最后一列来进⾏采样。GL_CLAMP_TO_BORDER:在纹理理坐标在0.01.0`范围之外的只使用边界纹理单元。边界纹理理单元是作为围绕基本图像的额外的行和列列,并与基本纹理图像⼀起加载的。



glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE)

image.png

案例

image.png

综合应用

1. 加载纹理stone.tga


 //1.分配纹理对象
    glGenTextures(1, &textureID);
    //2.绑定纹理状态
    glBindTexture(GL_TEXTURE_2D, textureID);
    //3.将TGA文件加载到2D纹理
    LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);

2.LoadTGATexture函数实现加载纹理和设置纹理状态

// 将TGA文件加载为2D纹理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
    GLbyte *pBits;
    int nWidth,nHeight,nComponents;
    GLenum eFormat;
    
    //读取像素
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    
    if(!pBits){
        return false;
    }
    
    //s设置纹理参数
    //1 纹理环绕
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    //2 过滤方式
    
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
    // 3.载入纹理
    
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
    free(pBits);
    
    if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
          minFilter == GL_LINEAR_MIPMAP_NEAREST ||
          minFilter == GL_NEAREST_MIPMAP_LINEAR ||
          minFilter == GL_NEAREST_MIPMAP_NEAREST)
    
      glGenerateMipmap(GL_TEXTURE_2D);
        
    return true;
}

3. 指定纹理坐标 通过批次类构建金字塔

金字塔图形解析

image.png
  1. 先确定顶点位置,以金字塔为中心 -1 1

  2. 确定每一个点对应的纹理坐标 以vbackLeft为纹理坐标原点

void MakePyramid(GLBatch& pyramidBatch)
{
    
     /***前情导入
        
        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);
       */
      pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
    
      // 点位置
      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 };
    
    
      //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();
}


4. 使用纹理替换矩阵着色器渲染



void RenderScene(void)
{
   //1.灯泡 颜色值
    
    static GLfloat vLightPos [] = {1.0f, 1.0f, 0.0f};
    static GLfloat vWhite [] = {1.0f, 1.0f, 1.0f, 1.0f};
    
  //2.清除缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    //压榨出栈
    modelViewMatrix.PushMatrix();
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.MultMatrix(mCamera);
    
    M3DMatrix44f mObjectFrame;
    objectFrame.GetMatrix(mObjectFrame);
    modelViewMatrix.MultMatrix(mObjectFrame);
    
    //绑定纹理
    
    glBindTexture(GL_TEXTURE_2D, textureID);
    
    //纹理替换矩阵着色器
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
    pyramidBatch.Draw();
    
    modelViewMatrix.PopMatrix();
    
    glutSwapBuffers();
    
}

严格来说,对纹理进行绑定并不是必须的,因为项目中就一个纹理,在加载纹理的时候就已经进行了绑定.

效果图

image.png

Demo地址

Demo

上一篇 下一篇

猜你喜欢

热点阅读