美文网首页OpenGL
OpenGL绘制球体世界

OpenGL绘制球体世界

作者: iOSer_jia | 来源:发表于2020-07-22 11:12 被阅读0次

    本文主要记录使用所学OpenGL相关知识绘制OpenGL中的一个经典案例--球体世界,最终实现效果如下:

    球体世界.gif

    准备工作

    系统环境

    macOS Catalina 10.15.6

    开发工具

    XCode 11.4.1

    依赖库

    • OpenGL.framework
    • GLUT.framework
    • libToools.a

    相关头文件导入

    main.cpp:

    #include <GLTools.h>
    #include <GLShaderManager.h>
    #include <GLFrustum.h>
    #include <GLFrame.h>
    #include <GLBatch.h>
    #include <GLMatrixStack.h>
    #include <GLGeometryTransform.h>
    #include <StopWatch.h>
    
    #include <math3d.h>
    #include <stdio.h>
    #include <math.h>
    
    #ifdef __APPLE__
    #include <glut/glut.h>
    #else
    #define FREEGLUT_STATIC
    #include <GL/glut.h>
    #endif
    

    相关属性声明

    // 小球数量
    #define NUM_SPHERES 50
    // 小球对象数组
    GLFrame spheres[NUM_SPHERES];
    
    // 观察者
    GLFrame cameraFrame;
    
    // 视角
    GLFrustum frustum;
    // 投影矩阵堆栈
    GLMatrixStack projectionMatrix;
    // 模型视图矩阵堆栈
    GLMatrixStack modelViewMatrix;
    // 变换管道
    GLGeometryTransform transformPipeline;
    // 固定管线管理者
    GLShaderManager shaderManager;
    // 地板批次类
    GLBatch floorBatch;
    // 大球批次类
    GLTriangleBatch torusBatch;
    // 小球批次类
    GLTriangleBatch sphereBatch;
    // 纹理数组
    GLuint textures[3];
    

    初始化

    在main函数中:

    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(SpecialKeys);
        
        
        GLenum err = glewInit();
        if (GLEW_OK != err) {
            fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
            return 1;
        }
        
        // 设置相关参数
        SetupRC();
        // 运行循环
        glutMainLoop();
        
        ShutdownRC();
        return 0;
    }
    
    

    具体函数的执行流程如图:

    函数执行流程.png

    ChangeSize

    在这个函数中,我们需要的事情有:

    • 设置视口大小
    • 设置投影方式
    • 加载投影矩阵
    • 加载模型试图矩阵
    • 设置变化管道
    void ChangeSize(int width, int height) {
        // 设置视口
        glViewport(0, 0, width, height);
        // 设置投影方式
        frustum.SetPerspective(35.5f, float(width)/float(height), 1.f, 300.f);
        // 加载投影矩阵
        projectionMatrix.LoadMatrix(frustum.GetProjectionMatrix());
        // 模型视图矩阵加载单元矩阵
        modelViewMatrix.LoadIdentity();
        // 设置管道
        transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    }
    

    SetupRC

    在这函数中,我们需要做的有:

    • 设置清屏颜色
    • 初始化固定着色器管理者
    • 开启深度测试和正背面剔除
    • 设置顶点
    • 加载纹理
    void SetupRC() {
        
        // 设置颜色
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        // 初始话着色器管理者
        shaderManager.InitializeStockShaders();
        // 开启深度测试
        glEnable(GL_DEPTH_TEST);
        // 开启正背面剔除
        glEnable(GL_CULL_FACE);
        // 设置地板顶点
        floorBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
        // 纹理坐标与地板顶点对应
        floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        floorBatch.Vertex3f(-20.0f, -0.4f, -20.f);
        
        floorBatch.MultiTexCoord2f(0, 0.0f, 10.0f);
        floorBatch.Vertex3f(-20.0f, -0.4f, 20.f);
        
        floorBatch.MultiTexCoord2f(0, 10.0f, 10.0f);
        floorBatch.Vertex3f(20.0f, -0.4f, 20.f);
        
        floorBatch.MultiTexCoord2f(0, 10.0f, 0.0f);
        floorBatch.Vertex3f(20.0f, -0.4f, -20.f);
        
        floorBatch.End();
        
        // 设置大球顶点
        gltMakeSphere(torusBatch, 0.4f, 40, 80);
        
        // 设置小球
        gltMakeSphere(sphereBatch, 0.1f, 20, 40);
        // 循环随机布置小球位置
        for (int i = 0; i < NUM_SPHERES; i ++) {
            GLfloat x = ((GLfloat)((rand() % 400) -200) * 0.1f);
            GLfloat z = ((GLfloat)((rand() % 400) -200) * 0.1f);
            
            spheres[i].SetOrigin(x, 0.0f, z);
        }
        
        // 命名纹理对象
        glGenTextures(3, textures);
        
        // 加载地板纹理
        glBindTexture(GL_TEXTURE_2D, textures[0]);
        LoadTGATexture("marble.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);
        
        // 加载大球纹理
        glBindTexture(GL_TEXTURE_2D, textures[1]);
        LoadTGATexture("marslike.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
        
        //加载小球纹理
        glBindTexture(GL_TEXTURE_2D, textures[2]);
        LoadTGATexture("moonlike.tga", GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
       
    }
    

    LoadTGATexture函数中具体实现了加载纹理的过程:

    bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode) {
        
        // 声明相关参数
        GLbyte *pBits;
        GLint iWidth, iHeight, iComponents;
        GLenum eFormat;
        
        // 读取纹理
        pBits = gltReadTGABits(szFileName, &iWidth, &iHeight, &iComponents, &eFormat);
        if (pBits == NULL) {
            return false;
        }
        
        // 设置过滤参数
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
        // 设置环绕方式参数
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
        
        // 载入纹理
        glTexImage2D(GL_TEXTURE_2D, 0, iComponents, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
        // 释放
        free(pBits);
        
        
        // 生成MIP
        if (minFilter == GL_LINEAR_MIPMAP_LINEAR || minFilter == GL_LINEAR_MIPMAP_NEAREST ||minFilter == GL_NEAREST_MIPMAP_LINEAR || minFilter == GL_NEAREST_MIPMAP_NEAREST)
        {
            glGenerateMipmap(GL_TEXTURE_2D);
            
        }
        return true;
    }
    

    RenderScene

    RenderScene()中具体实现了绘制的过程,具体过程如下:

    void RenderScene() {
        
        // 清空缓存
        glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
        // 设置地板颜色
        static GLfloat vFloorColor[] = {1.0f, 1.0f, 0.0f, 0.75f};
        
        // 定时器
        static CStopWatch timer;
        float yRot = timer.GetElapsedSeconds() * 60.0f;
        // 观察者矩阵
        M3DMatrix44f mCamera;
        cameraFrame.GetCameraMatrix(mCamera);
        // 后续坐标都以此变化,所以先压栈
        modelViewMatrix.PushMatrix(mCamera);
        
        // 绘制镜像
        modelViewMatrix.PushMatrix();
        // 矩阵翻转
        modelViewMatrix.Scale(1.f, -1.f, 1.f);
        // 往下平移一个大球直径的距离(此时矩阵是翻转的说y向下位正)
        modelViewMatrix.Translate(0.f, 0.8f, 0.f);
        //设置顺时针位正面(因为已经翻转了)
        glFrontFace(GL_CW);
        // 具体绘制
        DrawSomething(yRot);
        //恢复正面
        glFrontFace(GL_CCW);
        // 矩阵出栈恢复,开始绘制镜面及镜面以上
        modelViewMatrix.PopMatrix();
        
        // 绘制地板
        // 开启颜色混色
        glEnable(GL_BLEND);
        // 指定颜色和方程
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, textures[0]);
        // 使用纹理调整着色器
        shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor, 0);
        //开始绘制
        floorBatch.Draw();
        
        // 关闭颜色混合
        glDisable(GL_BLEND);
    
        // 绘制镜面以上
        DrawSomething(yRot);
    
        // 观察者矩阵出栈
        modelViewMatrix.PopMatrix();
        
        // 交换缓冲区
        glutSwapBuffers();
        // 提交渲染
        glutPostRedisplay();
        
    }
    
    

    DrawSomething具体实现了大球及小球的绘制,代码如下

    void DrawSomething(float yRot) {
        // 设置点光源位置
        M3DVector4f vLight = {0, 10, 5, 1.0f};
        // 白色
        static GLfloat vWhite[] = {1.0f, 1.0f, 1.0f, 1.0f};
        // 完y轴正方向平移,并往里偏移-3.0,便于观察
        modelViewMatrix.Translate(0.0f, 0.3f, -3.0f);
        
        // 开始绘制大球
        modelViewMatrix.PushMatrix();
        // 大球自传
        modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
        // 绑定纹理
        glBindTexture(GL_TEXTURE_2D, textures[1]);
        // 使用纹理光源着色器
        shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLight, vWhite, 0);
        // 绘制大球
        torusBatch.Draw();
        
        //大球绘制完成将矩阵堆栈里的自转矩阵出栈
        modelViewMatrix.PopMatrix();
        
        // 小球
        for (int i = 0; i < NUM_SPHERES; i++) {
            modelViewMatrix.PushMatrix();
            // 小球位置
            modelViewMatrix.MultMatrix(spheres[i]);
            // 绑定纹理
            glBindTexture(GL_TEXTURE_2D, textures[2]);
            // 使用纹理光源着色器
            shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLight, vWhite, 0);
            // 绘制小球
            sphereBatch.Draw();
            // 出栈
            modelViewMatrix.PopMatrix();
        }
        
        // 小球公转
        modelViewMatrix.PushMatrix();
        // 绕y轴旋转
        modelViewMatrix.Rotate(-2*yRot, 0.f, 1.f, 0.f);
        // 往x轴正方向平移1.0
        modelViewMatrix.Translate(1.0f, 0.0f, 0.0f);
        // 使用纹理光源着色器
        glBindTexture(GL_TEXTURE_2D, textures[2]);
        shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, transformPipeline.GetModelViewMatrix(), transformPipeline.GetProjectionMatrix(), vLight, vWhite, 0);
        // 开始绘制
        sphereBatch.Draw();
        // 整个矩阵出栈
        modelViewMatrix.PopMatrix();
    }
    
    

    SpecialKeys

    在这个函数中,我们修改观察者的位置或观察方向

    void SpecialKeys(int key, int x, int y) {
        float linear = 0.1f;
        float angular = float(m3dDegToRad(5.0f));
        
        if (key == GLUT_KEY_UP) {
            // 往前移动
            cameraFrame.MoveForward(linear);
        } else if (key == GLUT_KEY_DOWN) {
            // 往后移动
            cameraFrame.MoveForward(-linear);
        } else if (key == GLUT_KEY_LEFT) {
            // 像左看
            cameraFrame.RotateWorld(angular, 0, 1, 0);
        } else if (key == GLUT_KEY_RIGHT) {
            // 像右看
            cameraFrame.RotateWorld(-angular, 0, 1, 0);
        }
    }
    

    注意细节

    1. 地板纹理坐标与顶点坐标需要对应正确
    2. 每次绘制前一定要清空颜色缓冲区和深度缓冲区,避免上一次的绘制对本次产生影响
    3. 正背面规则修改后要记得修正回来
    4. 矩阵堆栈压栈出栈需要成对出现,否则会对下一个绘制图像产生影响
    5. 矩阵相乘不满足交换律,比如小球公转要先旋转在平移,如果交换顺序就会变成小球在一个固定位置自转

    相关文章

      网友评论

        本文标题:OpenGL绘制球体世界

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