OpenGL综合案例(大小球公转自转)

2020-07-21  本文已影响0人  K哥的贼船

先来看案例的完成效果展示


大小球公转自转

我们把整个绘制的步骤分为
初始化环境——视口调整——绘制地板——绘制大球——绘制小球——绘制公转的小球——移动视角

1.环境

首先我们先全局创建待会需要用到的实例。


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

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

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

//**4、添加附加随机球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

配置绘制环境

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;
}

设置视口和投影方式,初始化变换管道管理两个模型视图矩阵堆栈和投影矩阵堆栈


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

2. 绘制地板

我们先来画地板。

  1. 配置地板的数据。在SetupRC()中开启深度测试是为了后面球体的展示。设置地板的顶点数据和连接方式
  2. RenderScene(void)中设置地板着色器画笔颜色,清空颜色和深度缓存区,通过变换管道获取到矩阵堆栈的栈顶矩阵,开始绘制地板.
    这时候可以看到地板的效果图
    地板效果图

3. 绘制大球

  1. SetupRC()中设置大球模型gltMakeSphere(torusBatch, 0.4f, 40, 80);
  2. RenderScene(void)中绘制大球,开启自转。使用
//2.基于时间动画
    static CStopWatch rotaTimer;
    //获取当前时间*60度
    float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;

会根据每次调用屏幕重新渲染的时间戳乘以角度,实现动态自转。

重点在于:

4. 绘制小球

  1. 使用gltMakeSphere(sphereBatch, 0.1f, 13, 26);设置小球模型,给定小球的位置坐标。我这里给了50个小球的坐标,在y方向,将球体设置为0.0的位置,这使得它们看起来是飘浮在眼睛的高度。x方向和z方向我们取随机值,使得小球随机放置,之后通过角色帧函数SetOrigin(x, 0.0f, z);分别给到每个小球的位置。
  2. RenderScene(void)函数中我们用for循环绘制50个小球。每一次绘制都要先压栈,保证小球绘制互不影响,将栈顶的矩阵乘以小球(模型)的矩阵,之后用点光源着色器绘制,然后pop出栈。这是每一个小球的绘制流程。

5. 绘制公转的小球

由于这一步是最后的绘制步骤,后面不会再绘制视图了,绘制完成后就整体出栈,还原成原始堆栈了,所以不需要再压栈了。

  1. modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    直接在原点设置小球模型围绕y轴转,由于我们要明显的看到小球相对大球自转的公转,所以公转的角度倍数可以设置大点,这里我设置-2.0f,也就是两倍于大球自转的角度,并且是反方向的转。
  2. modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);再让小球距离大球往x轴方向隔开0.8的单位
  3. 开启绘制shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLightPos, vSphereColor);
    sphereBatch.Draw();
  4. 最后整体出栈。记得PushMatrix()了几次就要用几次PopMatrix();
  5. RenderScene(void)函数最后我们要交换缓冲区glutSwapBuffers();,之后重新提交渲染glutPostRedisplay();,相当于加了一个定时器一直在刷新屏幕。

6. 键位控制移动视角

  1. main()函数里加上glutSpecialFunc(SpeacialKeys);特殊键位函数
  2. void SpeacialKeys(int key, int x, int y)函数里面,我们对键位的移动进行处理,up和down则让观察者向屏幕内外平移,left和right则让观察者围绕观察者坐标系y轴(也就是自身)旋转视角。
  3. RenderScene(void)函数的第一次压栈之后(绘制地板之前),我们需要获得观察者矩阵并压入栈,因为键位控制视角的移动,是要引起所有包括地板、大球、小球的视觉变化(观察者和物体矩阵相乘),才是真实正确的世界视角。所以必须在堆栈顶部先压入观察者的矩阵。而在最后整体pop出栈的时候也要再加上一次PopMatrix();

整个demo的代码如下:


#include <stdio.h>
#include "GLTools.h"
#include "GLMatrixStack.h"
#include "GLBatch.h"
#include "StopWatch.h"
#include "GLFrustum.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;        // 投影矩阵
GLFrustum            viewFrustum;            // 视景体
GLGeometryTransform    transformPipeline;        // 几何图形变换管道

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

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

//**4、添加附加随机球
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

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

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 i = -20.0; i <= 20.0f; i += 0.5) {
        floorBatch.Vertex3f(i, -0.55f, 20.0f);
        floorBatch.Vertex3f(i, -0.55f, -20.0f);
        
        floorBatch.Vertex3f(20.0f, -0.55f, i);
        floorBatch.Vertex3f(-20.0f, -0.55f, i);
    }
    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);
    }
}

//进行调用以绘制场景
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 rotaTimer;
    //获取当前时间*60度
    float yRot = rotaTimer.GetElapsedSeconds() * 60.0f;
    
    //3.清除颜色缓存区和深度缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //4.压栈
    modelViewMatrix.PushMatrix();
    
    //4.加入观察者 平移10步(地板,大球,小球,小小球)
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    
    //5.绘制地面
    shaderManager.UseStockShader(GLT_SHADER_FLAT, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor);
    
    floorBatch.Draw();
    //6.获取光源位置
    M3DVector4d 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();
    }
    
    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();
    modelViewMatrix.PopMatrix();
    glutSwapBuffers();
    glutPostRedisplay();
}

void SpeacialKeys(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);
    }
}

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;
}

上一篇下一篇

猜你喜欢

热点阅读