美文网首页
OpenGL综合案例(大小球公转自转)

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

作者: K哥的贼船 | 来源:发表于2020-07-21 22:20 被阅读0次

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


    大小球公转自转

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

    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;
    

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

    重点在于:

    • 在绘制地板之前压栈PushMatrix(),保存当前的OpenGL堆栈的状态。
    • 由于地板的一直静止不动的,为了更好的观察,我们将大球往z轴移动-3,往屏幕里面移动,这两步都是一直持续不变的状态,所以我们要再压一次栈保存这个状态,之后才能保证我们对大球的自转处理不会影响到地板和大球的位置。
    • 大球开启自转,用点光源着色器绘制大球,为什么选择点光源呢,这样才有真实感,球体有明暗变化,更立体。
    • 把栈顶矩阵推出栈,恢复成大球自转前的堆栈状态。

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

    相关文章

      网友评论

          本文标题:OpenGL综合案例(大小球公转自转)

          本文链接:https://www.haomeiwen.com/subject/txhpkktx.html