我们使用的图片格式为.TGA的
纹理图片的数据解包与包装
OpenGL在读取一张图片时,首先要知道以什么方式在解析(解包)图像数据,因为一张图片的内存排列方式可以是4字节对齐,也可以是1字节排列,而.TGA格式的文件
是1字节对齐的
函数glPixelStorei可以改变像素的存储方式
比如:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
参数GL_UNPACK_ALIGNMENT 指定OpenGL如何从数据缓冲区中解包图像数据,这里表示以"紧密包装像素数据"的方式对像素数据进行操作
//类似的我们可以使用GL_PACK_ALIGNMENT来告诉OpenGL如何从像素缓冲区中读取并防止到一个用户指定的内存缓冲区的数据进行包装
纹理过滤
试想一下把一张图片放大或者缩小,每个像素会发生什么变化。假如原先在纹理图片上有A , B两个点,放大后 A和B中间多了个C点,那么C点像素的数据应该是什么呢?
填充C点数据的方法就叫做纹理过滤,我们来看两种:
最邻近过滤GL_NEAREST
这种模式是 C点距离哪个店最近,就采用那个点的数据
线性过滤GL_LINEAR
这种模式是以到A点和B点的距离为权重,根据A,B两点的数据算出的一个数据
我们可以使用下面的方法设置放大和 缩小时所采用的过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
纹理坐标
纹理坐标,大家可以把它理解成,一张威力图片的左下角是(0,0),右下角是(0,1),右上角是(1.1),左上角是(0,1)的一个坐标系。
我们会在绘制图像的顶点信息中设置,这个顶点要用到的纹理坐标值,在0~1之间,这样纹理中的像素就对应到相应的点上了
纹理环绕
试想一下,如果我们制定的纹理坐标超出了纹理图片的范围后会出现什么情况
设置纹理环绕模式,就是为了解决这个问题的。
我们可以用下面的方法,使超出的部分都取纹理边界的值
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
GL_TEXTURE_WRAP_S标示第一维坐标超出后采取的模式
GL_TEXTURE_WRAP_T标示第一维坐标超出后采取的模式
纹理对象
假如我们想使用三张纹理图片,但我们不可能用到的时候再一张一张的去加载,这样浪费性能
我们可以一次性加载三张图片,每一张都是一个纹理对象,使用的时候我们直接使用这个纹理对象
首先我们要生成三个纹理对象
#define TEXTURE_COUNT 3
GLuint textures[TEXTURE_COUNT];
glGenTextures(3, textures);
然后我们去绑定每一个纹理图像,然后就可以处理每一张纹理对象了,比如设置纹理过滤模式,环绕模式等
for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
{
//绑定纹理对象
glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
//下面是对这张纹理对象的处理
........
}
当我们想使用第一个纹理对象时,只需要这样
glBindTexture(GL_TEXTURE_2D, 1);
纹理对象我们会在下一节讲到mip贴图的时候会用到
下面来看看我们怎么来实现本节的例子
首先是需要包含的头文件和全局变量的声明
#include <GLTools.h>
#include <GLMatrixStack.h>
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLGeometryTransform.h>
#define FREEGLUT_STATIC
#include <GL/glut.h>
GLShaderManager shaderManager;
GLMatrixStack modelViewMatrix;
GLMatrixStack projectionMatrix;
GLFrame cameraFrame;
GLFrustum viewFrustum;
GLBatch pyramidBatch;
GLuint textureID;
GLGeometryTransform transformPipeline;
下面是main函数
int main(int argc, char* argv[])
{
//设置工作路径
gltSetWorkingDirectory(argv[0]);
//初始化glut库
glutInit(&argc, argv);
//申请一个带有双缓冲区,颜色缓冲区,深度缓冲区,模板缓冲区的窗口
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
//窗口的大小
glutInitWindowSize(800, 600);
//窗口的名字
glutCreateWindow("Texture");
//窗口改变大小时的回调函数
glutReshapeFunc(ChangeSize);
//窗口渲染时的回调函数
glutDisplayFunc(RenderScene);
//初始化glew库
GLenum err = glewInit();
if (GLEW_OK != err)
{
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
//初始化函数
SetupRC();
//主循环函数
glutMainLoop();
return 0;
}
下面是窗口大小改变时的回调函数ChangeSize
void ChangeSize(int w, int h)
{
//设置窗口的大小
glViewport(0, 0, w, h);
//设置投影矩阵
viewFrustum.SetPerspective(35.0f, float(w) / float(h), 1.0f, 500.0f);
//设置模型矩阵
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//把投影矩阵 和 模型矩阵进行统一的管理
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
下面是初始化函数SetupRC
void SetupRC()
{
//清除背景颜色
glClearColor(0.7f, 0.7f, 0.7f, 1.0f );
//初始化着色器函数
shaderManager.InitializeStockShaders();
//开启深度测试
glEnable(GL_DEPTH_TEST);
//加载纹理图片
LoadTGATexture("stone.tga", GL_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
//设置顶点数据
MakePyramid(pyramidBatch);
<span style="white-space:pre"> </span>
cameraFrame.MoveForward(-7.0f);
}
其中加载纹理图片函数LoadTGATexture 和 设置顶点数据函数MakePyramid是我们自己写的,下面回讲到
下面是纹理图片函数LoadTGATexture
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode)
{
GLbyte *pBits;
int nWidth, nHeight, nComponents;
GLenum eFormat;
//读取纹理图片,并保存在pBits中
pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
if(pBits == NULL)
return false;
//设置纹理环绕模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
//设置纹理过滤模式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
//以"紧密包装像素数据"的方式对像素数据进行操作
//参数GL_UNPACK_ALIGNMENT 指定OpenGL如何从数据缓冲区中解包图像数据
//类似的我们可以使用GL_PACK_ALIGNMENT来告诉OpenGL如何从像素缓冲区中读取并防止到一个用户指定的内存缓冲区的数据进行包装
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
//创建一个2维的纹理图片
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
eFormat, GL_UNSIGNED_BYTE, pBits);
//释放pBits的内存
free(pBits);
return true;
}
下面是
设置顶点数据函数MakePyramid
void MakePyramid(GLBatch& pyramidBatch)
{
pyramidBatch.Begin(GL_QUADS, 4, 1);
// Bottom of pyramid
pyramidBatch.Normal3f(0.0f, 0.0f, 11.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.Vertex3f(-1.0f, -1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, 0.0f, 11.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
pyramidBatch.Vertex3f(-1.0f, 1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, 0.0f, 11.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
pyramidBatch.Vertex3f(1.0f, 1.0f, -1.0f);
pyramidBatch.Normal3f(0.0f, 0.0f, 1.0f);
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3f(1.0f, -1.0f, -1.0f);
pyramidBatch.End();
}
其中Normal3f 表示设置该顶点的法线,MultiTexCoord2f表示设置该顶点的纹理坐标,Vertex3f是该顶点的位置