OpenGL综合案例-球体公转自转

388 阅读4分钟

最终效果预览

录屏.gif 包含以下几个元素:

  • 绿线网格地板
  • 远处有若干个静止的小蓝球
  • 一个固定的大红球
  • 一个围绕着红球公转的蓝球 另外还可以通过点击上下左右按键移动观察者。

导入的头文件

#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
GLFrame spheres[NUM_SPHERES];

main函数

int main(int argc, char* argv[])
{
    gltSetWorkingDirectory(argv[0]);
	// 初始化GLUT库,这个函数只是传输命令参数并且初始化glut库
    glutInit(&argc, argv);
   /*
     初始化双缓冲窗口,其中标志GLUT_DOUBLE、GLUT_RGBA、GLUT_DEPTH、GLUT_STENCIL分别指双缓冲窗口、RGBA颜色模式、深度测试、模板缓冲区
     --GLUT_DOUBLE`:双缓存窗口,是指绘图命令实际上是离屏缓存区执行的,然后迅速转换成窗口视图,这种方式,经常用来生成动画效果;
     --GLUT_DEPTH`:标志将一个深度缓存区分配为显示的一部分,因此我们能够执行深度测试;
     --GLUT_STENCIL`:确保我们也会有一个可用的模板缓存区。
    */
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    // GLUT窗口大小、窗口标题
    glutInitWindowSize(800,600);
    glutCreateWindow("OpenGL SphereWorld");
	// 注册重塑函数
    glutReshapeFunc(ChangeSize);
    // 注册显示函数
    glutDisplayFunc(RenderScene);
    // 注册特殊键位函数
    glutSpecialFunc(SpecialKeys);

    // 初始化一个GLEW库,确保OpenGL API对程序完全可用。
    // 在试图做任何渲染之前,要检查确定驱动程序的初始化过程中没有任何问题
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    // 设置渲染环境
    SetupRC();
    // 类似于iOS runloop运行循环
    glutMainLoop();    
    return 0;
}

SetupRC函数

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, 26, 13);
    
    //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);
    }
}

ChangeSize函数

//屏幕更改大小或已初始化
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);
}

RenderScene函数

//进行调用以绘制场景
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);

    //4.加入观察者 平移10步(地板,大球,小球,小小球)
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    
    //5.获取光源位置
    M3DVector4f vLightPos = {0.0f,10.0f,5.0f,1.0f};
    
    //6.绘制地面
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    floorBatch.Draw();
    
    //7.使得红球位置向屏幕里面平移3个单位长度,只移动1次
     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.让一个蓝球围绕红球公转+自转
    //这里可以加PushMatrix/PopMatrix,但是因为是最后的绘图所以不加也不会有影响
    modelViewMatrix.PushMatrix();
    
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);

 shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vSphereColor);
    sphereBatch.Draw();
    modelViewMatrix.PopMatrix();
    
    modelViewMatrix.PopMatrix();
    
    //14.执行缓存区交换
    glutSwapBuffers();
    
    //15.手动触发重新渲染
    glutPostRedisplay();
}

SpecialKeys函数

void SpecialKeys(int key,int x,int y){
    //移动步长
    float linear = 0.1f;
    //旋转度数
    float angular = float(m3dDegToRad(5.0f));
    
    if (key == GLUT_KEY_UP) {
        //MoveForward 平移
        cameraFrame.MoveForward(linear);
    }
    if (key == GLUT_KEY_DOWN) {
        cameraFrame.MoveForward(-linear);
    }
    
    if (key == GLUT_KEY_LEFT) {
        //RotateWorld 旋转
        cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
    }
    
    if (key == GLUT_KEY_RIGHT) {
        cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
    }
}