美文网首页OpenGL & Metal
OpenGL案例--绘制公转和自转的效果

OpenGL案例--绘制公转和自转的效果

作者: 黑眼豆豆_ | 来源:发表于2020-07-21 08:35 被阅读0次

    首先我们来看效果


    公转自转效果.gif

    由上图,我们可以看到,地面由蓝色的网格线组成,最中心有一个大球,地面上分布着小球,而有一个小球围着大球在再转。

    渲染流程

    渲染流程与之前的OpenGL使用小案例--绘制正方形并进行移动流程是一样的。

    渲染流程
    main开始进入程序,我们在main函数中做了这么几件事:
    1. setupRC(),设置我们的渲染环境,这是做为绘画进行一些准备工作,设置背景色,初始化shaderManager等等。

    2.注册函数GLUT 内部运行一个本地消息循环,拦截适当的消息。然后调用我们不同时间注册的回调函数。这就相当于iOS的block以及代理,在特定的时间会調起这些函数。

    • 注册重塑函数:glutReshapeFunc(changeSize);,这个函数会在两个时间点进行触发:
      1.程序运行时;
      2.窗口大小改变时。
    • 注册显示函数:glutDisplayFunc(RenderScene);这是整个项目的核心内容,在这个函数里,我们会进行图形的绘制。
    • 注册特殊函数:glutSpecialFunc(SpecialKeys);这个函数中,我们点击特殊按钮,来改变图形位置,让图形移动。
    1. glutMainLoop(),这个方法就是为程序提供一个runloop,保证程序处在一个循环中。

    准备工作

    首先我们需要定义一些变量。

    • 我们定义了3个批次类,为 torusBatch ,sphereBatch floorBatch ,分别对应大球,小球和地板。
    • 通过宏定义#define NUM_SPHERES 50定义了50个小球。
    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];
    

    Main()

    • 定义窗口大小glutInitWindowSize(800,600);
    • 注册几个函数,
      glutReshapeFunc(ChangeSize); 设置视口
      glutDisplayFunc(RenderScene); 具体绘制
      glutSpecialFunc(SpeacialKeys);特殊键位
    • SetupRC();初始化项目
    • glutMainLoop();运行一个循环,以免程序退出
    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;
    }
    

    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);
       projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
        
        //3. 变换管道设置2个矩阵堆栈(管理)
        transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    

    绘制地板

    void SetupRC()

    首页在SetupRC中做些初始化的工作

    • 清楚缓存中的颜色,并设置背景色为黑色
    • 初始化固定着色器shaderManager
    • 注意,由于项目中绘制了立体图形,所以要进行深度测试glEnable(GL_DEPTH_TEST);大家可以去看正背面剔除、深度测试这篇文章,具体讲到了深度测试。
        //1. 初始化
        glClearColor(0, 0, 0, 1);
        shaderManager.InitializeStockShaders();
        glEnable(GL_DEPTH_TEST);
    

    接下来,将地板数据放入floorBatch这个批次类中。

    //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();
    

    void SetupRC()

    接下来绘制地板,我们使用平面着色器进行渲染
    但是,首先,清除缓存

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    

    否则,页面会显示不出样式。

    //4.地面绘制;
    //定义地板线条颜色
    static GLfloat vFloorColor[] = {0.0f,1.0f,0.0f,1.0f};
    shaderManager.UseStockShader(GLT_SHADER_FLAT,transformPipeline.GetModelViewProjectionMatrix(),vFloorColor);
     floorBatch.Draw();
    

    还有,渲染完成后别忘了glutSwapBuffers();进行缓存区交换,从而渲染到屏幕上。

    地板.png

    绘制大球

    void setupRC()

    由于OpenGL为我们提供了绘制球的方法,所以在setupRC中我们只需要做一件事

    //设置好球的参数后将参数存入torusBatch中
    gltMakeSphere(torusBatch, 0.4f, 40, 80);
    

    void RenderScene(void)

    现在我们要让这个球进行自转,所以如下操作

    • 首先,我们要在最前面压一个空栈
    //压一个空栈
     modelViewMatrix.PushMatrix();
    

    为什么要PushMatrix呢?因为我们这次绘制到屏幕之后,不能对下次操作产生影响,所以压一个栈让每次绘制到处于原始状态。

    • 然后,我们要让球往里面移动,否则看不到
    //移动
    modelViewMatrix.Translate(0, 0, -3);
    
    • 接下来,我们让球旋转
    //定时器
        static CStopWatch roTimer;
        float yRot = roTimer.GetElapsedSeconds()*60.0f;
    
    //
        modelViewMatrix.Rotate(yRot, 0, 1, 0);
    
    • 最后绘制大球
    //设置点光源的点
     M3DVector4f vLightPos = {0,10,5,1};
    
    //使用点光源着色器
    shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vTorusColor);
    //绘制
    torusBatch.Draw();
    modelViewMatrix.PopMatrix();
    

    但这个时候,球并不会转,因为我们并没有调用RenderScene,调用

    glutPostRedisplay();
    

    让球进行自转。效果如下:


    绘制大球.png

    绘制小球

    void setupRC()

    我们随机定义50个小球,如下

    gltMakeSphere(sphereBatch, 0.1f, 30, 60);
        for (int i = 0 ; i<NUM_SPHERES; i++) {
            //Y值固定,x,z值随机
            GLfloat x = ((GLfloat)((random() % 400) - 200) *0.1f);
            GLfloat z = ((GLfloat)((random() % 400) - 200) *0.1f);
            spheres[i].SetOrigin(x,0.0f,z);
        }
    

    我们随机定义球的x和z轴的坐标,而固定y轴的坐标保证球在同一个平面上。

    void RenderScene(void)

    我们for循环的将小球绘制到屏幕上
    注意,push跟pop要成对出现。

    for (int i = 0 ; i<NUM_SPHERES; i++) {
            modelViewMatrix.PushMatrix(spheres[i]);
            shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vSphereColor);
            sphereBatch.Draw();
            modelViewMatrix.PopMatrix();
        }
    

    绘制旋转小球

    此时我们仍然可以使用之前定义的sphereBatch
    具体代码如下

        //旋转
        modelViewMatrix.Rotate(yRot * -2, 0, 1, 0);
        modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
        shaderManager.UseStockShader(GLT_SHADER_POINT_LIGHT_DIFF,transformPipeline.GetModelViewMatrix(),transformPipeline.GetProjectionMatrix(),vLightPos,vSphereColor);
        sphereBatch.Draw();
    

    此时不用push和pop,因为这是最后一个绘制,并不会影响前面的绘制

    接入观察者

    void SpecialKeys(int key, int x, int y)

    我们用上下左右键位对cameraFrame进行控制

    void SpecialKeys(int key, int x, int y){
        float linear = 0.1f;
        float angular = float(m3dDegToRad(5.0));
        //往外移动观察者
        if (key == GLUT_KEY_UP) {
            cameraFrame.MoveForward(-linear);
        }
        //往里移动观察者
        if (key == GLUT_KEY_DOWN) {
            cameraFrame.MoveForward(linear);
        }
        //观察者视角往左移动
        if (key == GLUT_KEY_LEFT) {
            cameraFrame.RotateWorld(angular, 0.0, 1.0, 0.0);
        }
        //观察者视角往右移动
        if (key == GLUT_KEY_RIGHT) {
            cameraFrame.RotateWorld(-angular, 0.0, 1.0, 0.0);
        }
    }
    

    最后把观察者压栈放入矩阵中

    void RenderScene(void)

        //将观察者放入矩阵中
        M3DMatrix44f mCamare;
        cameraFrame.GetCameraMatrix(mCamare);
        modelViewMatrix.PushMatrix(mCamare);
    

    注意,push跟pop要成对出现

        modelViewMatrix.PopMatrix();
    

    运行后就是我们最终的效果了。

    相关文章

      网友评论

        本文标题:OpenGL案例--绘制公转和自转的效果

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