OpenGL代码学习(24)--地板镜像效果的应用

注意:需要在配置好OpenGL的编程环境中运行下列代码,环境配置文章可参考:

OpenGL在Mac项目上的配置

下面的代码,直接放置在main.cpp文件中即可:

#include "GLTools.h"
#include "GLShaderManager.h"
#include "GLFrustum.h"
#include "GLBatch.h"
#include "GLFrame.h"
#include "GLMatrixStack.h"
#include "GLGeometryTransform.h"
#include "StopWatch.h"

#include <math.h>
#include <stdio.h>

#ifdef __APPLE__
#include <glut/glut.h>
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>
#endif

// 着色器
GLShaderManager shaderManager;
// 视图矩阵堆栈
GLMatrixStack modelViewMatrix;
// 投影矩阵堆栈
GLMatrixStack projectionMatrix;
// 视景体
GLFrustum viewFrustum;
// 照相机角色
GLFrame cameraFrame;
// 变换管线
GLGeometryTransform transformPipeline;
// 花托批次
GLTriangleBatch torusBatch;
// 地板批次
GLBatch floorBatch;
// 旋转小球批次
GLTriangleBatch sphereBatch;

// 随机小球群
#define NUM_SPHERES 50
GLFrame spheres[NUM_SPHERES];

// 定义3个纹理标识
#define TEXTURE_MARBLE      0
#define TEXTURE_MARSLIKE    1
#define TEXTURE_MOONLIKE    2
#define TEXTURE_COUNT       3
// 纹理标识数组
GLuint uiTextures[TEXTURE_COUNT];
// 纹理文件名数组
const char *szTextureFiles[TEXTURE_COUNT] = { "Marble.tga", "Marslike.tga", "MoonLike.tga" };

// 从 TGA 文件中加载纹理数据
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode) {
    // 从 TGA 文件读取纹理数据
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if(pBits == NULL)
        return false;
    
    // 设置环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    // 设置缩小和放大过滤器
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
    // 设置纹理压缩
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    // 加载纹理数据到2维纹理缓冲区
    glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, nWidth, nHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBits);
    
    // 释放纹理数据
    free(pBits);
    
    // 判断缩小过滤器是否是 Mip 贴图缩小过滤器,是则开启 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;
}

// 加载所有纹理数据
void LoadAllTextureData() {
    // 申请纹理数据标识
    glGenTextures(TEXTURE_COUNT, uiTextures);

    // 加载 Marble 纹理
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MARBLE]);
    LoadTGATexture(szTextureFiles[TEXTURE_MARBLE], GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_REPEAT);

    // 加载 Mars 纹理
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MARSLIKE]);
    LoadTGATexture(szTextureFiles[TEXTURE_MARSLIKE], GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);

    // 加载 Moon 纹理
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MOONLIKE]);
    LoadTGATexture(szTextureFiles[TEXTURE_MOONLIKE], GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR, GL_CLAMP_TO_EDGE);
}

// 程序初始化
void SetupRC() {
    // 设置窗口背景颜色为黑色
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    
    // 初始化着色器
    shaderManager.InitializeStockShaders();
    
    // 开启深度测试
    glEnable(GL_DEPTH_TEST);
    // 开启背面剔除,用于地板镜像效果实现
    glEnable(GL_CULL_FACE);
    
    // 得到花托批次数据
    gltMakeTorus(torusBatch, 0.4f, 0.15f, 30, 30);
    
    // 得到旋转小球批次数据
    gltMakeSphere(sphereBatch, 0.1f, 26, 13);
    
    // 得到地板批次数据
    GLfloat texSize = 10.0f;
    floorBatch.Begin(GL_TRIANGLE_FAN, 4, 1);
    floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    floorBatch.Vertex3f(-20.0f, -0.41f, 20.0f);

    floorBatch.MultiTexCoord2f(0, texSize, 0.0f);
    floorBatch.Vertex3f(20.0f, -0.41f, 20.0f);

    floorBatch.MultiTexCoord2f(0, texSize, texSize);
    floorBatch.Vertex3f(20.0f, -0.41f, -20.0f);

    floorBatch.MultiTexCoord2f(0, 0.0f, texSize);
    floorBatch.Vertex3f(-20.0f, -0.41f, -20.0f);
    floorBatch.End();
    
    // 随机小球群位置数据生成
    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);
    }
    
    // 加载所有纹理数据
    LoadAllTextureData();
}

// 程序资源释放
void ShutdownRC(void) {
    glDeleteTextures(TEXTURE_COUNT, uiTextures);
}

// 绘制球体世界除开地板外的所有物体
void DrawSongAndDance(GLfloat yRot) {
    static GLfloat vWhite[] = { 1.0f, 1.0f, 1.0f, 1.0f };
    static GLfloat vLightPos[] = { 0.0f, 3.0f, 0.0f, 1.0f };
    
    // 获取点光源位置
    M3DMatrix44f mCamera;
    M3DVector4f vLightEyePos;
    modelViewMatrix.GetMatrix(mCamera);
    m3dTransformVector4(vLightEyePos, vLightPos, mCamera);
    
    // 绘制随机小球群,这里的做法是瞬间切换当前角色位置并开始绘制小球,然后切换回原来角色位置
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MOONLIKE]);
    for(int i = 0; i < NUM_SPHERES; i++) {
        // 保存当前矩阵状态,为了能切换回原来的角色位置
        modelViewMatrix.PushMatrix();
        // 调整当前角色位置为随机生成小球的位置
        modelViewMatrix.MultMatrix(spheres[i]);
        // 绘制随机小球
        shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, modelViewMatrix.GetMatrix(),
                                     transformPipeline.GetProjectionMatrix(), vLightEyePos, vWhite, 0);
        sphereBatch.Draw();
        // 还原矩阵状态,切换为原来角色位置
        modelViewMatrix.PopMatrix();
    }
    
    // 视图矩阵进行平移
    modelViewMatrix.Translate(0.0f, 0.2f, -2.5f);
    // 保持旋转前的视图矩阵,因为如果不在旋转前保存,会导致旋转小球也会带上花托的旋转
    modelViewMatrix.PushMatrix();
    // 继续进行旋转
    modelViewMatrix.Rotate(yRot, 0.0f, 1.0f, 0.0f);
    
    // 绘制中间花托圆环
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MARSLIKE]);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, modelViewMatrix.GetMatrix(),
                                 transformPipeline.GetProjectionMatrix(), vLightEyePos, vWhite, 0);
    torusBatch.Draw();
    
    // 恢复到旋转前的视图矩阵
    modelViewMatrix.PopMatrix();
    
    // 绘制旋转小球
    modelViewMatrix.Rotate(yRot * -2.0f, 0.0f, 1.0f, 0.0f);
    modelViewMatrix.Translate(0.8f, 0.0f, 0.0f);
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MOONLIKE]);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_POINT_LIGHT_DIFF, modelViewMatrix.GetMatrix(),
                                 transformPipeline.GetProjectionMatrix(), vLightEyePos, vWhite, 0);
    sphereBatch.Draw();
}

// 窗口渲染回调
void RenderScene(void) {
    
    // 获取2次渲染之间的时间间隔
    static CStopWatch rotTimer;
    float yRot = rotTimer.GetElapsedSeconds() * 60.0f;
    static GLfloat vFloorColor[] = { 1.0f, 1.0f, 1.0f, 0.75f };
    
    // 清空缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 压入照相机矩阵
    M3DMatrix44f mCamera;
    cameraFrame.GetCameraMatrix(mCamera);
    modelViewMatrix.PushMatrix(mCamera);
    
    // 绘制地板镜像,Y 轴反转实现镜像效果
    modelViewMatrix.PushMatrix();
    modelViewMatrix.Scale(1.0f, -1.0f, 1.0f);
    modelViewMatrix.Translate(0.0f, 0.8f, 0.0f);
    glFrontFace(GL_CW);
    DrawSongAndDance(yRot);
    glFrontFace(GL_CCW);
    modelViewMatrix.PopMatrix();
    
    // 开启颜色混合,使地板镜像和地板融合一体
    glEnable(GL_BLEND);
    glBindTexture(GL_TEXTURE_2D, uiTextures[TEXTURE_MARBLE]);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_MODULATE, transformPipeline.GetModelViewProjectionMatrix(), vFloorColor, 0);
    floorBatch.Draw();
    glDisable(GL_BLEND);
    
    // 绘制地板上面的球体世界
    DrawSongAndDance(yRot);
    
    // 出栈,恢复成原始矩阵
    modelViewMatrix.PopMatrix();
    
    // 因为是双缓冲区模式,后台缓冲区替换到前台缓存区进行显示
    glutSwapBuffers();
    
    // 自动触发渲染,达到动画效果
    glutPostRedisplay();
}

// 特殊按键点击回调
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);
    
    if(key == GLUT_KEY_DOWN)
        cameraFrame.MoveForward(-linear);
    
    // 控制角色向左右旋转
    if(key == GLUT_KEY_LEFT)
        cameraFrame.RotateWorld(angular, 0.0f, 1.0f, 0.0f);
    
    if(key == GLUT_KEY_RIGHT)
        cameraFrame.RotateWorld(-angular, 0.0f, 1.0f, 0.0f);
}

// 窗口变换回调
void ChangeSize(int width, int height) {
    
    // 防止除数为0
    if(height == 0)
        height = 1;
    
    // 设置视口
    glViewport(0, 0, width, height);
    
    // 设置透视投影
    viewFrustum.SetPerspective(35.0f, float(width)/float(height), 1.0f, 100.0f);
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    
    // 设置变换管线的矩阵堆栈
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
}

// 程序入口
int main(int argc, char* argv[]) {
    // 设置 Mac OS 工作目录路径
    gltSetWorkingDirectory(argv[0]);
    
    // GLUT初始化
    glutInit(&argc, argv);
    
    // 设置渲染模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    
    // 初始化窗口大小
    glutInitWindowSize(800, 720);
    
    // 创建窗口并命名
    glutCreateWindow("OpenGL SphereWorld");
    
    // 判断驱动程序是否初始化完毕
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    
    // 窗口变化回调函数设置
    glutReshapeFunc(ChangeSize);
    
    // 窗口渲染回调函数设置
    glutDisplayFunc(RenderScene);
    
    // 特殊按键回调函数设置
    glutSpecialFunc(SpecialKeys);
    
    // 初始化环境
    SetupRC();
    
    // 主消息循环
    glutMainLoop();
    
    // 程序资源释放
    ShutdownRC();
    
    return 0;
}

效果如下:

 

posted @ 2021-05-03 00:31  码出境界  阅读(219)  评论(0编辑  收藏  举报