学习 opengl_OpenGL学习笔记(七)-综合案例

本文是OpenGL学习系列的综合案例,详细介绍了如何构建包含地板、旋转大球、随机分布的小球以及小球公转和键盘控制观察者移动的3D场景。从初始化工程到实现各种效果,每个步骤都有清晰的代码示例,帮助读者深入理解OpenGL的实践应用。
摘要由CSDN通过智能技术生成

2c1adea543855fc62d61d1309eedfb7c.png

这是一个前面所学的一个综合案例。具体文章可以看我的专栏:

OpenGL&OpenGL ES学习笔记​zhuanlan.zhihu.com
21b1d62eca7b22815f60d65df9fcf334.png

我们现在需要实现的最终效果如下:

ddb50dbb507133ed2e91b51271292621.gif
最终效果

0.前期准备

初始化空工程

#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLFrame.h"
#include "GLFrustum.h"
#include "GLGeometryTransform.h"
#include "GLBatch.h"
#include "StopWatch.h"

#include <math.h>
#include <glut/glut.h>

GLShaderManager     shaderManager;      // 着色器
GLMatrixStack       modelViewMatrix;    // 模型视图矩阵
GLMatrixStack       projectionMatrix;   // 投影矩阵
GLFrustum           viewFrustum;        // 视景体
GLGeometryTransform transformPipeline;  // 几何图形变换管道

GLTriangleBatch     torusBatch;         // 大球
GLTriangleBatch     sphereBatch;        // 小球
GLBatch             floorBath;          // 地板

// 此函数在呈现上下文中进行任何必要的初始化。.
// 这是第一次做任何与opengl相关的任务。
void SetupRC() {
   
}

// 窗口已更改大小,或刚刚创建。无论哪种情况,我们都需要
// 使用窗口维度设置视口和投影矩阵.
void ChangeSize(int w, int h) {
    
}

//召唤场景
void RenderScene(void) {
  
}

int main(int argc, char* argv[]) {
   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
   glutInitWindowSize(800, 600);
    
   glutCreateWindow("综合案例");
   glutReshapeFunc(ChangeSize);
   glutDisplayFunc(RenderScene);
   
   GLenum err = glewInit();
   if (GLEW_OK != err) {
       fprintf(stderr, "GLEW Error: %sn", glewGetErrorString(err));
       return 1;
   }
   
   SetupRC();
   
   glutMainLoop();
   return 0;
}

得到的效果:

558755f251429d701f5a0aa6de2a0724.png
空工程

1.实现地板

SetupRC里面

void SetupRC() {
    //1.清理颜色并初始化
    glClearColor(0, 0, 0, 1);
    shaderManager.InitializeStockShaders();
    
    //2.开启深度测试
    glEnable(GL_DEPTH_TEST);
    
    //3.设置地板顶点数据
    //基于物体坐标系的正方体
    floorBath.Begin(GL_LINES, 324); // 用线段连接,一共324个顶点
    for (GLfloat x = -20; x <= 20.0; x += 0.5) {
        floorBath.Vertex3f(x, -0.55, 20.0);
        floorBath.Vertex3f(x, -0.55, -20.0);
        
        floorBath.Vertex3f(20.0, -0.55, x);
        floorBath.Vertex3f(-20.0, -0.55, x);
    }
    floorBath.End();
}

ChangeSize里面

void ChangeSize(int w, int h) {
    //1.设置视口
    glViewport(0, 0, w, h);
    
    // 2.创建投影矩阵.
    // SetPerspective 是创建viewFrustum投影矩阵
    viewFrustum.SetPerspective(35.0, float(w) / float(h), 1.0, 100.0);
    
    // GetProjectionMatrix 是获取viewFrustum投影矩阵
    // 并将其加载到投影矩阵堆栈上
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 3. 设置变换管道已使用两个矩阵(投影矩阵:modelViewMatrix,变换矩阵projectionMatrix)
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

RenderScene函数里面

void RenderScene(void) {
    // 1.定义地板颜色
    static GLfloat vFloorColor[] = {0.5, 1.5, 0.5, 1.0};
    
    // 2.清理颜色缓冲区和深度缓冲区,因为上面使用了深度测试,所以要清理深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 3.绘制地面
    shaderManager.UseStockShader(GLT_SHADER_FLAT,
                                 transformPipeline.GetModelViewProjectionMatrix(),
                                 vFloorColor);
    // 4.开始绘制
    floorBath.Draw();
    
    // 5.执行缓冲区交换
    glutSwapBuffers();
}

效果如下:

9ef5a6750d4b377a2b21996d2f336c29.png

2.实现大球并开始旋转

SetupRC最后一行加上

...
//4.设置大球数据
gltMakeSphere(torusBatch, 0.4, 40, 80);

在RenderScene里面定义小球颜色的下面也定义大球的颜色:

static GLfloat vFloorColor[] = {0.5, 1.5, 0.5, 1.0};
// 定义大球颜色
static GLfloat vTorusColor[] = {0.5, 0.5, 1.0, 1.0};

...
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 // 基于时间的计时器,用作旋转动画
 static CStopWatch rotTimer;
 float yRot = rotTimer.GetElapsedSeconds() * 60.0;
 
 // 开始压栈
 // 压栈的目的:如果一个单元矩阵需要旋转,移动,缩放,完成后再出,当次的工作已经完毕,需要还原成原始的状态。
 // 下次进来也是基于开始的原地进行操作
 // 保证每次的操作不会出现冲突
 // push1
 modelViewMatrix.PushMatrix();

...
floorBath.Draw();
 // 绘制大球
 // 5.设置点光源
 M3DVector4f vLightPos = {0, 10, 5, 1};
 // 6.往z轴移动3个像素
 // 笛卡尔坐标系屏幕左右是x,上下是y,垂直屏幕是z,且屏幕向内为负,相外为正
 modelViewMatrix.Translate(0, 0, -3);
 // 7.设置旋转
 // 压栈
 // push2
 modelViewMatrix.PushMatrix();
 // 8.开始旋转
 modelViewMatrix.Rotate(yRot, 0, 1, 0);
 // 9.指定合适的着色器
 shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
                              transformPipeline.GetModelViewMatrix(),
                              transformPipeline.GetProjectionMatrix(),
                              vLightPos,
                              vTorusColor);
 // 绘制大球
 torusBatch.Draw();
 // 出栈:入栈几次,就要出栈几次
 // pop1
 modelViewMatrix.PopMatrix();
 // pop2
 modelViewMatrix.PopMatrix();
 
 // 10.执行缓冲区交换
...

画出的大球效果:

5ceb5f862509157bd3012fab4d633c8d.gif

3.实现随机的50个小球

先在前面定义小球的个数和存放小球的数组

...
GLBatch             floorBath;          // 地板

static int const NUM_MIN_SPHERES = 50; // 随机小球的个数
GLFrame spheres[NUM_MIN_SPHERES];
...

在SetupRC函数里面循环创建50个小球

...
gltMakeSphere(torusBatch, 0.4, 40, 80);

//5.设置小球
gltMakeSphere(sphereBatch, 0.1, 15, 26);
//6.创建随机小球
// 在同一个平面,Y值是一样的,X和Z产生随机值
for (int i = 0; i < NUM_MIN_SPHERES; i ++) {
    // X和Z产生随机值
    GLfloat x = ((GLfloat)((rand() % 400) - 200) * 0.1);
    GLfloat z = ((GLfloat)((rand() % 400) - 200) * 0.1);
    
    spheres[i].SetOrigin(x, 0.0, z); // 球的顶点
}

在RenderScene里面定义小球颜色

...
static GLfloat vTorusColor[] = {0.5, 0.5, 1.0, 1.0};
// 定义小球颜色
static GLfloat vSphereColor[] = {1.0f, 1.0f, 0.0f, 1.0f};

并开始循环创建50个

...
 modelViewMatrix.PopMatrix();
 
 // 绘制50个小球:注意需要在第一个push里面,保证独立性
 for (int i = 0; i < NUM_MIN_SPHERES; i ++) {
     // push3
     modelViewMatrix.PushMatrix();
     modelViewMatrix.MultMatrix(spheres[i]);
     // 还是用过点光源的方式绘制
     shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
                                  transformPipelin.GetModelViewMatrix(),
                                  transformPipelin.GetProjectionMatrix(),
                                  vLightPos,
                                  vSphereColor);
     // 开始绘制
     sphereBatch.Draw();
     // pop3
     modelViewMatrix.PopMatrix();
 }
 
 // pop2
 modelViewMatrix.PopMatrix();
...

得到的效果如下:

effeb96256545002d15ecfbd8502f41a.png

4.实现小球围绕大球公转,并且使用键盘控制观察者移动

先创建照相机(观察者)角色帧

...
GLFrame             cameraFrame;        // 角色帧 照相机角色帧
...

在RenderScene函数里面设置观察者矩阵

...
modelViewMatrix.PushMatrix();

// 设置观察者矩阵
M3DMatrix44f mCamera;
cameraFrame.GetCameraMatrix(mCamera);
// push5
modelViewMatrix.PushMatrix(mCamera);

// 3.绘制地面
shaderManager.UseStockShader(GLT_SHADER_FLAT,
...

绘制一个小球围绕大球旋转

...

    modelViewMatrix.PopMatrix();
 }
// 绘制一个小球围绕大球旋转(围绕y轴旋转)
// PS: 因为这是最后一个绘制出来的图像,不会影响到其他的图像,所以就不需要push和pop
// modelViewMatrix.PushMatrix(); // push4
// Rotate(每次旋转的度数, x, y, z),
modelViewMatrix.Rotate(yRot * -1, 0, 1, 0); //
yRot*1可以调节快慢和方向,正为逆时针,负为顺时针
// 防止和大球重叠,需要平移一段距离
modelViewMatrix.Translate(1.0, 0, 0);
// 用过点光源的方式绘制
shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,
                             transformPipeline.GetModelViewMatrix(),
                             transformPipeline.GetProjectionMatrix(),
                             vLightPos,
                             vSphereColor);
// 绘制
sphereBatch.Draw();
// modelViewMatrix.PopMatrix(); // pop4
// pop5
modelViewMatrix.PopMatrix();

 // pop2
 modelViewMatrix.PopMatrix();
...

效果如下:

fae2731a6dab18794c831e8217b3e0ed.gif

5.实现键位控制

实现SpeacialKey函数

// 实现键位控制
void SpeacialKey(int key, int x, int y) {
    // 设置移动的步长
    float linear = 0.1;
    // 设置移动的角度
    float angular = float(m3dDegToRad(5.0));
    
    if (key == GLUT_KEY_UP) {
        cameraFrame.MoveForward(linear);
    } else if (key == GLUT_KEY_DOWN) {
        cameraFrame.MoveForward(-linear);
    } else if (key == GLUT_KEY_LEFT) {
        cameraFrame.RotateWorld(angular, 0.0, 1, 0.0);
    } else if (key == GLUT_KEY_RIGHT) {
        cameraFrame.RotateWorld(-angular, 0.0, 1, 0.0);
    } else {
        // Other
    }
    // glutPostRedisplay(); // 可以不用写,因为最终会调用RenderScene函数里面,里面调用了该函数
}

在main函数里面注册

glutSpecialFunc(SpeacialKey);

最后的效果:

ddb50dbb507133ed2e91b51271292621.gif

完整代码:

综合案例的完整代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值