OpenGL

OpenGL绘制球体世界

2020-07-22  本文已影响0人  iOSer_jia

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

球体世界.gif

准备工作

系统环境

macOS Catalina 10.15.6

开发工具

XCode 11.4.1

依赖库

相关头文件导入

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. 矩阵相乘不满足交换律,比如小球公转要先旋转在平移,如果交换顺序就会变成小球在一个固定位置自转
上一篇 下一篇

猜你喜欢

热点阅读