效果图结合之前文章所涉及到的知识,我们来实现一个简单案例
一、案例分析
image.png金字塔由4个三角形+底部正方形构成,而底部正方形是由X、Y两个三角形构成的。所以说整个模型是有5个顶点:(以物体中心点为原点坐标)
VBackLeft (-1.0,-1.0,-1.0)
VBackRight (1.0,-1.0,-1.0)
vFrontLeft (-1.0,-1.0,1.0)
VFrontRight(1.0,-1.0,1.0)
vApex (0,1.0,0)
结合上一篇文章的纹理坐标知识来看,那么他们相对应的纹理坐标就是:
VBackLeft ( 0,0)
VBackRight (1,0)
vFrontLeft (0,1)
VFrontRight (1,1)
vApex (0.5,1)
二、代码逻辑流程图
image.png三、源码
#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;
//用来初始化纹理,将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、设置纹理参数
//S、T方向的环绕方式
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);
//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);
//4、使用完,释放指针
free(pBits);
return true;
}
//用来绘制金字塔
void MakePyramid(GLBatch& pyramidBatch)
{
//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 };
//2、begin 开始设置
/*
1、类型
2、定点数
3、这个批次中会用到几个纹理,不写就是0
*/
pyramidBatch.Begin(GL_TRIANGLES, 18, 1);
//======================金字塔底部======================
//底部的四边形 = 三角形X + 三角形Y
//三角形X = (vBackLeft,vBackRight,vFrontRight)
//1、vBackLeft
//导入纹理坐标
/*
参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
参数2:s 对应顶点坐标中的x坐标
参数3:t 对应顶点坐标中的y坐标
*/
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
//导入顶点坐标
pyramidBatch.Vertex3fv(vBackLeft);
//2、vBackRight
pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
pyramidBatch.Vertex3fv(vBackRight);
//3、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);
//3、end 结束设置
pyramidBatch.End();
}
//初始化纹理和金字塔
void SetupRC()
{
//1、设置背景色
glClearColor(0.77, 0.77, 0.77, 1.0);
//2、初始化着色器
shaderManager.InitializeStockShaders();
//3、深度测试
glEnable(GL_DEPTH_TEST);
//4、分配纹理对象
glGenTextures(1, &textureID);
//5、绑定纹理状态
glBindTexture(GL_TEXTURE_2D, textureID);
//6、加载纹理数据并且设置纹理数据
//纹理文件名称、缩小时候的过滤方式、放大时候的过滤方式、环绕方式
LoadTGATexture("brick.tga", GL_NEAREST, GL_LINEAR, GL_CLAMP_TO_EDGE);
//7、初始化金字塔
MakePyramid(pyramidBatch);
//8、设置观察者位置
cameraFrame.MoveForward(-10);
}
//清理,删除纹理对象
void ShutdownRC(void)
{
glDeleteTextures(1, &textureID);
}
void RenderScene(void)
{
//1、清理缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//2、压栈
modelViewMatrix.PushMatrix();
//观察者矩阵
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
//物体矩阵
M3DMatrix44f mObjectFrame;
objectFrame.GetMatrix(mObjectFrame);
modelViewMatrix.MultMatrix(mObjectFrame);
//3、以防万一,在这里绑定纹理
glBindTexture(GL_TEXTURE_2D, textureID);
//4、使用着色器 纹理替换矩阵着色器
/*
参数1:GLT_SHADER_TEXTURE_REPLACE(着色器标签)
参数2:模型视图投影矩阵
参数3:纹理层
*/
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE,transformPipeline.GetModelViewProjectionMatrix(),0);
//5、绘制
pyramidBatch.Draw();
//6、出栈
modelViewMatrix.PopMatrix();
//7、交换缓冲区
glutSwapBuffers();
}
void SpecialKeys(int key, int x, int y)
{
//这里让物体在世界坐标系中进行旋转
if(key == GLUT_KEY_UP){
objectFrame.RotateWorld(m3dDegToRad(-5.0f), 1, 0, 0);
}
if(key == GLUT_KEY_DOWN){
objectFrame.RotateWorld(m3dDegToRad(5.0f), 1, 0, 0);
}
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);
}
//提交,调用RenderScene重新渲染
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);
//3、将投影矩阵加载到投影矩阵堆栈上
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//4、设置变换管道来使用MV矩阵堆栈和P矩阵堆栈
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, 800);
glutCreateWindow("纹理金字塔");
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
glutReshapeFunc(ChangeSize);
glutSpecialFunc(SpecialKeys);
glutDisplayFunc(RenderScene);
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
四、注意事项
- 避免一个方法太臃肿,提炼出两个封装函数,看起来更清晰
- 注意LoadTGATexture 方法中,纹理API的使用顺序
- 注意MakePyramid 方法中,各个顶点在纹理坐标中对应的映射关系,不要搞混了
- 注意RenderScene 方法中,压栈后为了防止纹理在其他地方的使用,特地又绑定了一次。然后记得出栈操作
网友评论