案例的实现效果是在一个场景中绘制地板、大球、小球、公转自转和相机的移动,其中结合了OpenGL中大部分知识点,最终的效果如下图
大球自转,小球公转
主要有以下3部分构成:
1.绿色的地板
2.自转的红色大球
3.50个随机分布的蓝色小球以及1个围绕大球公转的动态小球
整体的流程的如下
整体流程.png
比较重要的逻辑都在以下三个函数中
- SetupRC函数
- RenderScene函数
- SpecialKeys函数
整体的效果的实现,可以大致分为4部分
- 地板
- 大球(自转)
- 小球(包含50个静态小球+1个围绕大球公转的动态小球)
- 移动(特殊键位上下左右触发)
1、地板绘制
- SetupRC函数:准备地板的顶点数据
- RenderScene函数:利用平面着色器绘制地板
两个函数此时的流程图大致如下
地板绘制流程具体代码:
1.1 声明变量
#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_SPHERE 50 //小球数量
GLFrame spheres[NUM_SPHERE];
1.2 main函数
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800,600);
glutCreateWindow("OpenGL SphereWorld");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
1.3设置视口
//屏幕更改大小或已初始化
void ChangeSize(int nWidth, int nHeight)
{
//1.设置视口
glViewport(0, 0, nWidth, nHeight);
//2.创建投影矩阵,。
viewFrustum.SetPerspective(35.0f, float(nWidth)/float(nHeight), 1.0f, 100.0f);
//viewFrustum.GetProjectionMatrix() 获取viewFrustum投影矩阵
//并将其加载到投影矩阵堆栈上
projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
//3.设置变换管道以使用两个矩阵堆栈(变换矩阵modelViewMatrix ,投影矩阵projectionMatrix)
//初始化GLGeometryTransform 的实例transformPipeline.通过将它的内部指针设置为模型视图矩阵堆栈 和 投影矩阵堆栈实例,来完成初始化
//当然这个操作也可以在SetupRC 函数中完成,但是在窗口大小改变时或者窗口创建时设置它们并没有坏处。而且这样可以一次性完成矩阵和管线的设置。
transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}
1.4 设置地板数据
void SetupRC()
{
//1.初始化
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
shaderManager.InitializeStockShaders();
//2.开启深度测试
glEnable(GL_DEPTH_TEST);
//3. 设置地板顶点数据
floorBatch.Begin(GL_LINES, 324);
for(GLfloat x = -20.0; 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();
}
1.5 地板渲染
//进行调用以绘制场景
void RenderScene(void)
{
//1.颜色值(地板,大球,小球颜色)
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
//2.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//3.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
//渲染地板
floorBatch.Draw();
//4.执行缓存区交换
glutSwapBuffers();
}
运行代码,地板就绘制好了
地板绘制.png
2、大球绘制
- SetupRC函数:系统模型创建大球
- RenderScene函数:分为三个部分
(1). 设置定时器,根据时间变化,设置大球自转
(2). 设置点光源位置
(3). 设置大球平移
(4). 开启定时器,渲染大球
两个函数的流程图如下,图中红框部分流程均与大球相关
大球绘制流程图具体代码:
2.1 设置大球数据
gltMakeSphere(torusBatch, 0.4f, 40, 80);
void SetupRC()
{
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
shaderManager.InitializeStockShaders();
glEnable(GL_DEPTH_TEST);
floorBatch.Begin(GL_LINES, 324);
for (GLfloat x = -20.0f; 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();
gltMakeSphere(torusBatch, 0.4f, 40, 80);
}
2.2 大球渲染
//2.基于时间动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//4.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//5.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//6.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//7.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//8.指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
//9.绘制完毕则Pop
modelViewMatrix.PopMatrix();
//进行调用以绘制场景
void RenderScene(void)
{
//1.颜色值(地板,大球,小球颜色)
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f };
//2.基于时间动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//2.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//**// 压栈
modelViewMatrix.PushMatrix();
//3.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//4.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//5.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//6.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//7.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//8.指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
//9.绘制完毕则Pop
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
//10.执行缓存区交换
glutSwapBuffers();
glutPostRedisplay();
}
运行代码,得到地板与大球
地板与大球.png
大球变换的代码如下
//5.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//6.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//7.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
Translate:目的是为了更好的观察的大球,因为大球创建时默认是在(0,0,0)原点位置,当前的观察者也处于原点位置,不便于观察
PushMatrix:拷贝矩阵堆栈栈顶并压栈,此时只需要将大球平移一次,然后在平移后坐标基础上围绕y轴旋转,实现自转
Rotate:大球围绕y轴旋转,实现自转
3、小球绘制
- SetupRC函数:初始化小球数据
- RenderScene函数:首先绘制50个静态小球,每绘制一个小球都需要进行push和pop操作
3.1 设置小球数据
//5. 设置小球球模型
gltMakeSphere(sphereBatch, 0.1f, 13, 26);
//6. 随机位置放置小球球
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);
}
void SetupRC()
{
//1.初始化
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
shaderManager.InitializeStockShaders();
//2.开启深度测试
glEnable(GL_DEPTH_TEST);
//3. 设置地板顶点数据
floorBatch.Begin(GL_LINES, 324);
for(GLfloat x = -20.0; 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();
//4.设置大球模型
gltMakeSphere(torusBatch, 0.4f, 40, 80);
//5. 设置小球球模型
gltMakeSphere(sphereBatch, 0.1f, 13, 26);
//6. 随机位置放置小球球
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);
}
}
3.2 小球渲染
//11.画小球
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();
}
//进行调用以绘制场景
void RenderScene(void)
{
//1.颜色值(地板,大球,小球颜色)
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f};
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f};
//2.基于时间动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//3.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix();
//4.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//5.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//6.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//7.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//8.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//9.指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
//10.绘制完毕则Pop
modelViewMatrix.PopMatrix();
//11.画小球
for (int i = 0; i < NUM_SPHERES; i++) {
//每绘制一个小球都需要进行push和pop操作
modelViewMatrix.PushMatrix();
modelViewMatrix.MultMatrix(spheres[I]);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vSphereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
}
modelViewMatrix.PopMatrix();
//12.执行缓存区交换
glutSwapBuffers();
glutPostRedisplay();
}
运行代码,得到地板+大球+小球
地板+大球+小球.png
4、小蓝球围着红球自转
//13. 让一个小蓝球围绕大球自转
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();
//进行调用以绘制场景
void RenderScene(void)
{
//1.颜色值(地板,大球,小球颜色)
static GLfloat vFloorColor[] = { 0.0f, 1.0f, 0.0f, 1.0f};
static GLfloat vTorusColor[] = { 1.0f, 0.0f, 0.0f, 1.0f};
static GLfloat vSphereColor[] = { 0.0f, 0.0f, 1.0f, 1.0f};
//2.基于时间动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
//3.清除颜色缓存区和深度缓冲区
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
modelViewMatrix.PushMatrix();
//5.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
transformPipeline.GetModelViewProjectionMatrix(),
vFloorColor);
floorBatch.Draw();
//6.获取光源位置
M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
//7.使得大球位置平移(3.0)向屏幕里面
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//8.压栈(复制栈顶)
modelViewMatrix.PushMatrix();
//9.大球自转
modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
//10.指定合适的着色器(点光源着色器)
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(),
transformPipeline.GetProjectionMatrix(), vLightPos, vTorusColor);
torusBatch.Draw();
//11.绘制完毕则Pop
modelViewMatrix.PopMatrix();
//12.画小球
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();
}
//13. 让一个小蓝球围绕大球自转
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();
modelViewMatrix.PopMatrix();
//14.执行缓存区交换
glutSwapBuffers();
glutPostRedisplay();
}
运行代码,得到一个小蓝球围绕大红球自转
小蓝球围绕大红球自转.gif 小球渲染流程图.png
5、设置视图变换
项目中setupRc、RenderScene、SpecialKeys总的流程图如下
总渲染流程图.png
具体代码如下
5.1加入观察者
//4、加入观察者
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
modelViewMatrix.PopMatrix();
//进行调用以绘制场景
void RenderScene(void)
{
//3.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//1. 颜色(地板,大球颜色,小球颜色)
static GLfloat vFloorColor[] = {0.0f,1.0f,0.0f,1.0f};
static GLfloat vTorusColor[] = {1.0f,0.0f,0.0f,1.0f};
static GLfloat vSpereColor[] = {0.0f,0.0f,1.0f,1.0f};
//2. 动画
static CStopWatch rotTimer;
float yRot = rotTimer.GetElapsedSeconds()*60.0f;
modelViewMatrix.PushMatrix();
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
modelViewMatrix.PushMatrix(mCamera);
//4.地面绘制;
shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vFloorColor);
floorBatch.Draw();
//5. 设置点光源位置
M3DVector4f vLightPos = {0,10,5,1};
//6. 使得整个大球往里平移3.0
modelViewMatrix.Translate(0.0f, 0.0f, -3.0f);
//7. 大球
modelViewMatrix.PushMatrix();
modelViewMatrix.Rotate(yRot, 0, 1, 0);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vTorusColor);
torusBatch.Draw();
modelViewMatrix.PopMatrix();
//8. 小球
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,vSpereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
}
//9.让一个小球围着大球公转;
modelViewMatrix.Rotate(yRot * -2.0f, 0, 1, 0);
modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vSpereColor);
sphereBatch.Draw();
modelViewMatrix.PopMatrix();
modelViewMatrix.PopMatrix();
glutSwapBuffers();
glutPostRedisplay();
}
5.2 main函数添加SpeacialKeys监听
glutSpecialFunc(SpeacialKeys);
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800,600);
glutCreateWindow("OpenGL SphereWorld");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
glutSpecialFunc(SpeacialKeys);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
return 0;
}
5.3实现SpeacialKeys键位函数
void SpeacialKeys(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, 1, 0);
}
if (key == GLUT_KEY_RIGHT) {
cameraFrame.RotateWorld(-angular, 0, 1, 0);
}
}
运行代码,就可以对视图进行前进后退旋转操作了
对视图进行前进后退旋转操作.gif
错误分析
运行代码的时候,有可能会出现以下情况,或者运行崩溃
入栈出栈操作对应分析.png
如上图,入栈出栈操作一一对应,红色入栈push对应红色出栈pop
网友评论