美文网首页
OpenGL之 公转自转

OpenGL之 公转自转

作者: HLinzl | 来源:发表于2020-07-19 16:11 被阅读0次

    本篇将会介绍一个大球的自转以及一个小球围绕大球公转的demo,效果如下图:

    公转自转.gif

    实现过程

    image.png

    如上图所示,整个项目的基本流程较之前几个例子没有太多的变化。都是:

    • 初始化窗口;
    • 注册各函数的监听,如 重塑函数、重绘函数等;
    • 调用setupRC,初始化窗口背景、着色器管理器、顶点数据等;
    • 开启glut的mainloop,类似iOS的runloop。

    1、SetupRC方法

    #pragma mark - 顶点数据私有方法
    void vertextDataFloor() {
        floorBatch.Begin(GL_LINES, 324);
        
        /*
         GL_LINES  每两个点画一条线
         画法是
         第一次循环
         (-20,-0.5,20) 到(-20,-0.5,-20) 画一条直线
         (20,-0.5,-20)到(-20,-0.5,-20)画一条直线
         第二次循环
         (-19.5,-0.5,20) 到(-19.5,-0.5,-20) 画一条直线
         (20,-0.5,-19.5)到(-20,-0.5,-19.5)画一条直线
         
         直到最后一次循环,闭合整个网格
         */
        for (GLfloat x = -20.f; x <= 20.f; x+=0.5f) {
            floorBatch.Vertex3f(-x, commonY, 20.f);
            floorBatch.Vertex3f(-x, commonY, -20.f);
            
            floorBatch.Vertex3f(20.f, commonY, x);
            floorBatch.Vertex3f(-20.f, commonY, x);
        }
        
        floorBatch.End();
    }
    
    void vertextDataBigBall() {
        gltMakeSphere(torusBatch, 0.5f, 40, 80);
    }
    
    void vertextDataSmallBallRotate() {
        gltMakeSphere(sphereBatch, 0.2, 20, 40);
    }
    
    void vertextDataRandomSmallBall(){
        for (int i = 0; i < SMALL_BALL_NUMBER; i ++) {
            GLfloat randowX = ((random()%400) - 200) * 0.1;
            GLfloat randowZ = ((random()%400) - 200) * 0.1;
            sphereFrames[i].SetOrigin(randowX,0,randowZ);
        }
    }
    
    void SetupRC() {
        glClearColor(1, 1, 1, 1);
        
        glEnable(GL_LINE_SMOOTH);
        shaderManager.InitializeStockShaders();
        
        vertextDataFloor();
        
        vertextDataBigBall();
        
        vertextDataSmallBallRotate();
        
        vertextDataRandomSmallBall();
    }
    
    

    在这个方法中,主要做一些初始化的工作,如初始化窗口背景色、着色器管理器、顶点数据等。

    2、ChangeSize方法

    void ChangeSize(int nWidth, int nHeight) {
        glViewport(0, 0, nWidth, nHeight);
        viewFrustum.SetPerspective(35.f, float(nWidth)/float(nHeight), 1.f, 500.f);
        
        //将投影矩阵载入投影矩阵堆栈
        projectionMatrixStack.LoadMatrix(viewFrustum.GetProjectionMatrix());
        
        //可以不调用,默认会有一个单元矩阵
        modelViewMatrixStack.LoadIdentity();
        transformPipeline.SetMatrixStacks(modelViewMatrixStack, projectionMatrixStack);
        
    }
    

    如代码所示,在这个方法中依然是常规的设置视口的位置和大小、设置投影方式、载入投影矩阵到投影矩阵堆栈、载入单元矩阵进模型视图矩阵堆栈,然后将这两个堆栈设置到变化管道对象里,方便使用和管理。

    3、RenderScence方法

    void RenderScence() {
        //GL_STENCIL_BUFFER_BIT 在这个demo中,可以不清
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        
        //开启深度测试
        glEnable(GL_DEPTH_TEST);
        
        modelViewMatrixStack.PushMatrix();
        
        //画地面
        static GLfloat vBlue[] = {0.f,0.5f,1.f,1.f};
        static GLfloat vBigBall[] = {0,0,1,1};
        static GLfloat vSmallBall[] = {1,0.5,0,1};
        static GLfloat vSmallBallRandom[] = {0,0.5,1,1};
        
        static CStopWatch watchObj;
        GLfloat angle = watchObj.GetElapsedSeconds() * 60.f;
        
        M3DMatrix44f viewMatrix;
        viewFrame.GetCameraMatrix(viewMatrix);
        modelViewMatrixStack.MultMatrix(viewMatrix); 
    shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vBlue);
        floorBatch.Draw();
        
        //栈顶矩阵z轴负方向方向平移
        modelViewMatrixStack.Translate(0, 0, -3);
        
        //复制一份,画完大球之后,pops栈顶数据,然后栈顶数据是设置过平移的矩阵
        modelViewMatrixStack.PushMatrix();
        modelViewMatrixStack.Rotate(angle, 0, 1, 0);
        
        M3DVector4f pointPosition = {0,10,5,1};
        shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),
                                     transformPipeline.GetProjectionMatrix(),pointPosition,vBigBall);
        
        torusBatch.Draw();
        
        modelViewMatrixStack.PopMatrix();
        
        
        for (int i = 0; i < SMALL_BALL_NUMBER; i ++) {
            GLFrame frame = sphereFrames[i];
            modelViewMatrixStack.PushMatrix();
            
            modelViewMatrixStack.MultMatrix(frame);
            
            shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),
                                         transformPipeline.GetProjectionMatrix(),pointPosition,vSmallBallRandom);
            sphereBatch.Draw();
            
            modelViewMatrixStack.PopMatrix();
        }
        
        
        //无需压栈,因为这是当前绘制的最后一个图形
        modelViewMatrixStack.Rotate(angle * -2.f, 0, 1, 0);
        //z值的绝对值越大,离大球越远。
        modelViewMatrixStack.Translate(0, 0, 1.1);
        
        shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),
                                     transformPipeline.GetProjectionMatrix(),pointPosition,vSmallBall);
        
        sphereBatch.Draw();
        
        modelViewMatrixStack.PopMatrix();
        
        glutSwapBuffers();
        glutPostRedisplay();
        //关闭深度测试
        glDisable(GL_DEPTH_TEST);
    }
    

    这个方法是本次案例中,与之前的案例相比最大的方法。

    3.1 画地板
    • 清理颜色缓存区和深度缓存区,这里的模板缓存区在本demo中可以不清。
    • 开启深度测试
    • 栈顶矩阵copy一份压栈
    • 初始化各颜色值以及一个定时器,定时器用于计算当前旋转角度
    • 从观察者角色帧获取观察者矩阵,并用当前模型视图矩阵堆栈栈顶矩阵乘以观察者矩阵,得到结果覆盖栈顶矩阵
    • 使用平面着色器处理数据
    • 通过批次类画出地板
    • 在全部图形绘制完之后,会交换缓存区
    3.2 画大球
    • 栈顶矩阵沿z轴负方向移动3;
    • 栈顶矩阵copy一份压栈(后续计算完成出栈之后,栈顶矩阵依然是之前沿着z轴负方向移动3之后的矩阵)
    • 沿着y轴旋转
    • 设置光源位置
    • 使用点光源着色器处理数据
    • 通过三角形批次类画出大球
    • 针对最近一次的入栈操作进行出栈,保证栈顶矩阵为之前沿着z轴负方向移动3之后的矩阵。
    • 在全部图形绘制完之后,会交换缓存区
    3.3 画多个分散的小球

    在上述的SetupRC方法中调用的vertextDataRandomSmallBall方法里,做了很多顶点数据的初始化,这些就是随机小球的位置数据。
    在RenderScence方法中也需要将他们绘制出来。

    • 开启for循环
    • 每次循环都复制一份栈顶矩阵压栈
    • 将栈顶矩阵乘以当前小球的角色帧数据,赋值给栈顶数据
    • 使用点光源着色器处理数据
    • 使用三角形批次类进行绘制
    • 将每次循环的栈顶矩阵出栈
    • 在全部图形绘制完之后,会交换缓存区
    3.4 画公转的小球
    • 栈顶矩阵绕y轴旋转一定的角度,角度根据当前的计时器的时间来计算
    • 栈顶矩阵沿z轴平移操作,无论正负,绝对值越大,离大球越远
    • 使用点光源着色器处理数据
    • 使用三角形批次类进行绘制
    • 将当前栈顶矩阵出
    3.5 收尾
    • 交换缓存区
    • 提交重新渲染,保证重复调用RenderScence方法,形成旋转动画
    • 关闭深度测试

    总结:
    RenderScence方法中,会有各种矩阵变换的计算操作,将原始的顶点数据,经过各种变换,全部到达一个新的位置,最终实现我们要的效果。在计算的过程中,需要注意的几个点:

    • 整体的入栈和出栈要成对出现,有入栈就必须有出栈,否则下次绘制的时候数据会错乱
    • 对于整个变换过程,拿公转小球举例,虽然它是经过了
    modelViewMatrixStack.Translate(0, 0, -3);
    modelViewMatrixStack.Rotate(angle * -2.f, 0, 1, 0);
    modelViewMatrixStack.Translate(0, 0, 1.1);
    

    这三部,但是在OpenGL实现的时候,由于OpenGL采用的是右乘的方式,所以,其实小球是经过了先z轴平移,再旋转,再z轴平移的操作。
    而对于大球来说,它经过了

    modelViewMatrixStack.Translate(0, 0, -3);
    modelViewMatrixStack.PushMatrix();
    modelViewMatrixStack.Rotate(angle, 0, 1, 0);
    

    这三部操作,而OpenGL实现的时候,其实大球是先旋转,再z轴平移。
    因此,对于大球来说,它是自转,而对于小球来说,它是公转。

    最终总结

    上述为个人实现与总结,如有错漏之处,欢迎并感谢批评指正。

    相关文章

      网友评论

          本文标题:OpenGL之 公转自转

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