OpenGL纹理使用

411 阅读8分钟

纹理使用流程

纹理使用流程图

代码如下

// 1. 分配纹理对象,参数1:纹理对象个数,参数2:纹理对象指针
glGenTextures(1, &textureID);
// 2. 绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
glBindTexture(GL_TEXTURE_2D, textureID);

// 3. 将TGA文件加载为2D纹理
/*
 参数1: 纹理文件名称
 参数2:&参数3: 需要缩小&放大的过滤器
 参数4: 纹理坐标的环绕方式
 */
LoadTGATexture("stone.tga", GL_LINEAR_MIPMAP_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
// 将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: wrap,环绕模式
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
	
	// 参数1: 纹理维度
	// 参数2: 线性过滤
	// 参数3: wrap 环绕模式
	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);
	free(pBits);
	
	// 4. 加载Mip,纹理生成所有的Mip层
	// 参数:GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D
	glGenerateMipmap(GL_TEXTURE_2D);
	
    return true;
}

纹理对应API

从颜色缓存区内容作为像素图直接读取

// 参数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);

// 指定读取的缓存
glReadBuffer(mode);

// 指定写入的缓存
glWriteBuffer(mode);

OpenGL像素格式

载入纹理

// 载入1维纹理
void glTexImage1D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLint border,GLenum format,GLenum type,void *data);
// 载入2维纹理
void glTexImage2D(GLenum target,GLint level,GLint internalformat,GLsizei width,GLsizei height,GLint border,GLenum format,GLenum type,void * data);
// 载入3维纹理
void glTexImage3D(GLenum target,GLint level,GLint internalformat,GLSizei width,GLsizei height,GLsizei depth,GLint border,GLenum format,GLenum type,void *data);

通用参数:

参数 target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
参数 level:指定所加载的mip贴图层次。一般我们都把这个参数设置为0
参数 internalformat:每个纹理理单元中存储多少颜色成分。
参数 width、height、depth:指加载纹理的宽度、高度、深度
参数 border:允许为纹理贴图指定一个边界宽度
参数 format: 读取内容颜色数据
参数 type:像素数据的类型
参数 data:像素数据被读取后会存放此指针中

注意! 宽度,高度,深度 这些值必须是 2的整数次⽅方。 (这是因为OpenGL 旧版本上的遗留下的一个要求。当然现在已经可以支持不是 2的整数次方。但是开发者们还是习惯使⽤用以2的整数次方去设置这些参数。)

纹理对象

  • 使用函数分配纹理对象

注意: 指针指向一个⽆符号整形数组,由纹理对象标识符填充。

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

glIsTexture(GLuint texture);
  • 读取纹理数据

TGA文件中读取像素图

GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat);
// 参数1: 纹理文件名称
// 参数2,3:文件宽高地址
// 参数4: 文件组件地址
// 参数5:文件格式地址
// 返回值:pBits 指向图像数据的指针
  • 设置纹理参数
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);

// 参数1: target, 指定这些参数将要应用在哪个纹理上,比如GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
// 参数2: pname,指定需要设置哪个纹理参数
// 参数3: param,设定特定的纹理参数的值
  • 载入纹理
void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
// 参数 target:GL_TEXTURE_2D, GL_TEXTURE_1D, GL_TEXTURE_3D
// 参数 level:指定所加载的mip贴图层次,一般设置为0
// 参数 internalformat:每个纹理单元总存储多少颜色成分
// 参数 width,height:纹理宽度,高度
// 参数 border:纹理贴图指定一个边界宽度
// 参数 format:文件格式
// 参数 type:像素数据的类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
// 参数 pixels:指向纹理图像数据的指针
  • 生成Mip贴图
// 参数:GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D
glGenerateMipmap(GL_TEXTURE_2D);

纹理处理方式

过滤方式
  • 临近过滤(GL_NEAREST)——一般缩小时使用

    坐标在哪个位置,就选择那个位置的像素

  • 线性过滤(GL_LINEAR)——一般放大时使用

    坐标位置周围的像素做一次运算,取运算后的结果值

效果图

过滤对比

代码如下:

// 纹理理缩小时,使⽤用邻近过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST)

// 纹理理放大时,使⽤用线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR) 
环绕方式

场景: 当纹理坐标超出默认范围时,每个选项都有不同的视觉效果输出。以下为各种效果图实例

环绕方式

  • GL_REPEATOpenGL在纹理理坐标超过1.0的方向上对纹理进⾏行重复;

  • GL_CLAMP:所需的纹理单元取⾃纹理边界或TEXTURE_BORDER_COLOR.

  • GL_CLAMP_TO_EDGE:强制对范围之外的纹理坐标沿着合法的纹理单元的最后⼀行或者最后⼀ 列来进行行采样。

  • GL_CLAMP_TO_BORDER:在纹理理坐标在0.01.0范围之外的只使⽤边界纹理单元。边界纹理单元是 作为围绕基本图像的额外的行和列,并与基本纹理图像一起加载的。

代码如下

// 参数1:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 
// 参数2:GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,针对s,t,r坐标  
// 参数3:GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE、GL_CLAMP_TO_BORDER

// 坐标关系s,t,r,q = x, y, z, w

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

Mip贴图

以下描述出自:<编程指南第9版 >—第6章纹理理与帧缓存

Mip贴图1

Mip贴图2


法线

一个平面的法线是一个长度为1并且垂直于这个平面的向量。

我们在使用光源时,除了强度和颜色之外,还需要指定光源的位置和方向,并且这些光源的位置和方向将会极大地影响场景的外观。

为什么光照需要法线?

OpenGL至少支持8中独立的光源。当我们指定一个光源的时候,便要告诉OpenGL这个光源的位置以及它的照射方向。

光源经常向四周照射,但也可以向一个方向照射。无论哪种情况,对于我们所绘制的任何物体,来自任何光源的光线都将根据一个角度撞击做成这个物体的多边形的表面。

为了计算围绕多边形表面的着色效果,OpenGL必须能够计算光线与多边形表面之间的角度。

物体的法线向量定义了它的表面在空间中的朝向,即定义了表面相对于光源的方向。因为OpenGL是使用法线向量来确定一个物体表面的某个顶点所接受的光照的。

个人理解:

对于一个面,明确了法线就可以明确光照在这个面上的照射区域,再根据观察者所在的位置进行计算,得出这个面展示的效果。

法线:表示每个顶点垂直向上的向量

对于光照计算,所有的法线向量都必须先进行规范化,然后才参与计算

将法线转换为单位法线的过程,称为法线的规范化。

获取一个平面的法线向量方式。注意返回的法线不一定是单位法线向量

void m3dFindNormal(M3DVector3f vNormal,
                         const M3DVector3f vP1,
                         const M3DVector3f vP2,
                         const M3DVector3f vP3);

基本操作方式:

M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
M3DVector3f vBackLeft = { -1.0f,  -1.0f, -1.0f };
M3DVector3f vBackRight = { 1.0f,  -1.0f, -1.0f };
M3DVector3f n;	// 接收法线
    
//1.找到三角形X 法线
m3dFindNormal(n, vBackLeft, vBackRight, vFrontRight);
   
//设置批次类对应法线
batch.Normal3fv(n);
    
// 设置顶点对应的纹理坐标
batch.MultiTexCoord2f(0, 0.0f, 0.0f);

// 向批次类添加顶点
batch.Vertex3fv(vBackLeft);  

法线向量只表示方向,不表示大小(即与长度无关)。理论上可以为顶点指定任意大小的法线向量,但在OpenGL执行光照操作时,会将顶点的法线向量规范化(单位化)。而这样必然会降低程序的性能,所以一般应由我们自己提供各个顶点的规范化法线向量。

如果只是对模型进行移动、旋转操作,法线向量的长度将不会发生改变,而如果对模型进行缩放操作,长度则将变化。如果需要OpenGL对法线向量进行规范化就需要启动一下功能。

glEnable(GL_NORMALIZE);// 默认为关闭

如果所进行的缩放是均匀缩放,则可使用一下性能更优的方式

glEnable(GL_RESCALE_NORMAL); // 默认为关闭

参考:

www.khronos.org/opengl/wiki…

blog.csdn.net/u014800094/…