在之前我写过几篇博客,关于OpenGL的一些专业名词的介绍,绘制一些简单的图形(三角形,正方形,球,金字塔等图案)。现在我通过一个案例,将之前所有写过的东西通过一个案例来展现出来。
这个案例效果图如下:
QQ20200718-134336-HD.gif
这个案例用到的知识点:
1、GLShaderManager着色器
2、GLMatrixStack矩阵堆栈
3、GLFrustum透视投影 视景体
4、GLGeometryTransform几何变换管道
5、GLTriangleBatch三角形批次类
6、GLBatch批次类容器
7、GLFrame角色帧
8、M3DMatrix44f矩阵变换
9、CStopWatch时间监视器
下面来实现一下具体的过程
一、引入头文件,定义对象、属性、容器类和自定义函数
#include "GLTools.h"
#include "GLShaderManager.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLMatrixStack.h"
#include "GLGeometryTransform.h"
#include "StopWatch.h"
#include <math.h>
#include <stdio.h>
#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif
GLShaderManager shaderManager; //着色器管理器
GLMatrixStack modelViewMatrix; //模型视图矩阵
GLMatrixStack projectionMatrix; //投影矩阵
GLFrustum viewFrustum; //视景体
GLGeometryTransform transformPipeline; //几何图形变换管道
GLTriangleBatch torusBatch; //大球
GLTriangleBatch sphereBatch; //小球
GLBatch floorBatch; //地板
GLFrame cameraFrame; //角色帧 照相机角色帧
#define NUM_SPHERES 50 //添加50个随机球
GLFrame spheres[NUM_SPHERES];
//创建窗口视图
void ChangeSize(int w,int h){}
//加载顶点数据
void SetupRC(){}
//场景绘制
void RendeScene(void){}
//键位移动
void SpecialKeys(int key,int x,int y){}
int main(int argc,char* argv[]){
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE|GLUT_DEPTH|GLUT_RGB);
glutInitWindowSize(800, 600);
glutCreateWindow("综合案例");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RendeScene);
glutSpecialFunc(SpecialKeys);
GLenum err = glewInit();
if(GLEW_OK!=err){
fprintf(stderr, "GLEW Error: %s\n",glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
二、窗口函数的实现
//1.设置视口
glViewport(0, 0, w, h);
//2.创建投影矩阵。
viewFrustum.SetPerspective(35.0, float(w)/float(h), 1.0f, 100.0f);
//viewFrustum.GetProjectionMatrix() 获取viewFrustum投影矩阵
//并将其加载到投影矩阵堆栈上
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//3.设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
三、设置底部的地板视图
SetupRC设置位置
//初始化颜色
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
//初始化着色器
shaderManager.InitializeStockShaders();
//开启深度测试
glEnable(GL_DEPTH_TEST);
//1、加载地板的坐标
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20; x<=20.0f; x+=0.5) {
floorBatch.Vertex3f(x, -0.55f, 20.0f);
floorBatch.Vertex3f(x, -0.55f, -20.0f);
floorBatch.Vertex3f(20.0f, -0.55f, x);
floorBatch.Vertex3f(-20.0f, -0.55f, x);
}
floorBatch.End();
在RendeScene绘制场景
//设置颜色(小球,地板,大球)
static GLfloat vFloorColor[] = {0.0f,1.0f,0.0f,1.0f};
//清除颜色、深度缓冲区
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
//1.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vFloorColor);
floorBatch.Draw();
//执行缓存区交换
glutSwapBuffers();
glutPostRedisplay();
四、设置大球位置和自转
SetupRC函数
//2、设置大球模型
gltMakeSphere(torusBatch, 0.4f, 40, 80);
RendeScene函数
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
modelViewMatrix.PushMatrix();
//2、绘制大球
//获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//使大球位置平移3.0向屏幕里面
modelViewMatrix.Translate(0.0f,0.0f, -3.0f);
//压栈
modelViewMatrix.PushMatrix();
//大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vtorusColor);
torusBatch.Draw();
//绘制完毕pop
modelViewMatrix.PopMatrix();
五、绘制小球和围绕大球公转
SetupRC
//3、设置小球模型
gltMakeSphere(sphereBatch, 0.1f, 13, 26);
//随机放置小球位置
for (int i = 0; i<NUM_SPHERES; i++) {
//y轴不变,x,z产生随机值
GLfloat x = ((GLfloat)(rand() % 400) - 200) * 0.1f;
GLfloat z = ((GLfloat)(rand() % 400) - 200) * 0.1f;
//在y轴方向,将球体设置为0.0的位置,使得他们看起来是漂浮在眼睛的高度
//对spheres数组中的每一个顶点,设置顶点数据
spheres[i].SetOrigin(x, 0.0f, z);
RendeScene
static GLfloat vsphereColor[] = {0.0f,0.0f,1.0f,1.0f};
//3、绘制小球
for (int i = 0; i<NUM_SPHERES; i++) {
modelViewMatrix.PushMatrix();
modelViewMatrix.MultMatrix(spheres[i]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vsphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
}
//让小球围绕大球自转
modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vsphereColor);
sphereBatch.Draw();
六、设置视图变换
RendeScene中加入观察者
//4、加入观察者
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
void SpecialKeys(int key,int x,int y){
//移动步长
float linear = 0.1f;
//旋转度数
float angular = float(m3dDegToRad(5.0f));
if(key == GLUT_KEY_UP)
cameraFrame.MoveForward(linear);
if(key == GLUT_KEY_DOWN)
cameraFrame.MoveForward(-linear);
if(key == GLUT_KEY_LEFT)
cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
if(key == GLUT_KEY_RIGHT)
cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}
在SpecialKeys的键位移动过程结束后,为什么不用glutPostRedisplay()函数重新提交渲染呢?
因为在RendeScene函数中,使用了时间函数CStopWatch,它在每秒钟会自动调用glutPostRedisplay函数重新提交渲染,因此在SpecialKeys函数中不必重新提交渲染。
可能也有人好奇为什么大球和小球同样设置旋转,为什么大球是自转,小球确实围绕大球转呢?
那是因为大球的坐标在正中心,旋转围绕y轴旋转,所以大球旋转是自身旋转,而小球坐标不在坐标正中心,因此,小球需要围绕正中心旋转。因此,小球可以看作是围绕大球公转。
至于小球和地板的坐标的设置,你们可以自行去算一下坐标。他们围绕的都是y轴旋转。
完整代码地址:Demo
网友评论