美文网首页
OpenGL案例02

OpenGL案例02

作者: 卡布奇诺_95d2 | 来源:发表于2020-08-19 18:02 被阅读0次

绘制一个带纹理的金字塔模型,通过本案例来加深对纹理的理解,案例执行结果如下效果图:


Jietu20200819-160303.gif

搭建框架

#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 RenderScene(){
}

void SetupRC(){
}

void SpecialKeys(int key, int x, int y){
}

void ChangeSize(int w, int h){
    glViewport(0, 0, w, h);
    viewFrustum.SetPerspective(35, float(w)/float(h), 1, 100);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    cameraFrame.MoveForward(-10.0f);
}

int main(int argc, char * argv[]) {
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_RGBA | GLUT_DOUBLE | GLUT_STENCIL);
    glutInitWindowSize(800, 600);
    glutCreateWindow("test");
    
    glutDisplayFunc(RenderScene);
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    
    GLenum err = glewInit();
    if(GLEW_OK != err){
        return 1;
    }
    
    SetupRC();
    glutMainLoop();
    ShutdownRC();
    return 0;
}

SetupRC函数

在SetupRC函数中,主要有以下几个操作:

  1. 清除背景颜色;
glClearColor(0.7f, 0.7f, 0.7f, 1.0f);
  1. 初始化着色器;
shaderManager.InitializeStockShaders();
  1. 打开深度测试;
glEnable(GL_DEPTH_TEST);
  1. 分配纹理对象;
//分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针
glGenTextures(1, &textureID);
  1. 绑定纹理对象;
//绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
glBindTexture(GL_TEXTURE_2D, textureID);
  1. 加载纹理;
    加载纹理是本次案例一个重点,实现从tga文件加载纹理。又细分为以下几个步骤:
    6.1 读取纹素
//参数1:纹理文件名称
//参数2:返回纹理宽度
//参数3:返回纹理高度
//参数4:返回纹理组件
//参数5:返回纹理格式
//返回值:pBits,指向图像数据的指针
int nWidth, nHeight, nComponents;
GLenum eFormat;
GLbyte* pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);

6.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);

6.3 载入纹理

//参数1:纹理维度
//参数2:mip贴图层次
//参数3:纹理单元存储的颜色成分(从读取像素图是获得)
//参数4:加载纹理宽
//参数5:加载纹理高
//参数6:加载纹理的深度
//参数7:加载纹理的格式
//参数8:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
//参数9:指向纹理图像数据的指针
glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
  1. 使用三角形批次类创建金字塔
    由于金字塔需要附着纹理,所以需要在金字塔创建的时候,需要分别设置顶点坐标与纹理坐标。
    在坐标系中绘制金字塔,坐标原点位置金字塔的中心。一个金字塔共有5个顶点,5个顶点组成6个三角形。5个顶点的坐标如下:
vApex(0.0, 1.0, 0.0)
vBackLeft(-1.0, -1.0, -1.0)
vBackRight(1.0. -1.0, -1.0)
vFrontRight(1.0, -1.0, 1.0)
vFrontLeft(-1.0, -1.0, 1.0)
image.png

金字塔纹理坐标如下:


image.png
  • 设置纹理坐标使用三角形批次类的MultiTexCoord2f函数。
//参数1:图层,即纹理的level,一般为0
//参数2:纹理的坐标s,类似于顶点坐标x
//参数3:纹理的坐标t,类似于顶点坐标y
pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
  • 设置顶点坐标使用三角形批次类的Vertex3fv函数。
//参数:顶点坐标x,y,z
pyramidBatch.Vertex3fv(vApex);

设置金字塔的前面、左面、右面、后面三角形的顶点坐标与纹理坐标:

// 金字塔前面
//三角形:(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);

接下来就是设置金字塔底部三角形X、Y的顶点坐标和纹理坐标了。这里需要发挥一下空间想象。
在3D空间里面,我们有6个方向:上下左右前后,分别是观察者观察物体所在的方向,三角形X、Y处于金字塔底部,观察者应该从下面观察这个金字塔。


image.png

根据这个图,我们可以知道顶点坐标与纹理坐标的对应关系:

//三角形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);

至此,SetupRC函数算是完成了。

RenderScene函数

在SetupRC函数里面已经得到了顶点坐标,也关联了对应的纹理坐标,那在RenderScene函数的操作就相对较简单。

  • 清理缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  • 模型视图矩阵压入堆栈
modelViewMatrix.PushMatrix();
  • 由于在ChangeSize函数里面移动了相机的位置,此时应该要将模型视图矩阵堆栈的栈顶乘以观察者矩阵。
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.MultMatrix(mCamera);
  • 在SpecialKeys函数里面将会对物体进行旋转,此时需要将模型视图矩阵堆栈的栈顶再乘以物体矩阵。
M3DMatrix44f mObject;
objectFrame.GetMatrix(mObject);
modelViewMatrix.MultMatrix(mObject);
  • 重新绑定纹理,这个案例中只有一个纹理,此处若不重新绑定也不会出问题,出于严谨性,还是重新绑定一下。
glBindTexture(GL_TEXTURE_2D, textureID);
  • 使用纹理替换矩阵着色器进行绘制
shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
    
pyramidBatch.Draw();
  • 绘制完成之后,模型视图矩阵出栈,恢复原样。
modelViewMatrix.PopMatrix();
  • 交换缓冲区
glutSwapBuffers();

ShutdownRC函数

出于代码的完整性,在程序退出前删除纹理对象。

void ShutdownRC(void){
    glDeleteTextures(1, &textureID);
}

至此,案例2就完成了。
总结本次案例重点:

  1. 加载纹理的流程。
  2. 顶点坐标与纹理坐标的关联。

Demo地址

相关文章

网友评论

      本文标题:OpenGL案例02

      本文链接:https://www.haomeiwen.com/subject/tdfdjktx.html