OpenGL 蓝书学习(第四版)第九章

OpenGL 蓝书学习(第四版)第九章

纹理贴图高级部分

各向异性过滤

#include <OpenGL/glext.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#include <array>
#include <iosfwd>
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
 
#include <iostream>
#include <format>
#include <numbers>
#include <fstream>
#include <vector>
 
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLuint NUM_TEXTURES = 3;
static constexpr std::array<std::string, NUM_TEXTURES> textureFiles = {
    "../brick.tga", // 0 砖块
    "../floor.tga", // 1 地板
    "../ceiling.tga" // 2 天花板
};
 
// 变量
static GLfloat zPos = -60.0F; // z位置
static int mainWindowId; // 主窗口 ID
static GLuint textureIds[NUM_TEXTURES];
 
 
// Targa 图像文件头结构体
#pragma pack(push, 1)          // 关闭结构体对齐填充
struct TargaHeader {
    // 图像 ID 区长度,0 = 无
    GLubyte idLength;
    GLubyte colorMapType;  // 0 = 无调色板
    GLubyte dataTypeCode;     // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
    GLushort colorMapOrigin;  // 调色板起始索引
    GLushort colorMapLength;  // 调色板颜色的数量
    GLubyte colorMapDepth;  // 调色板每条颜色的位数
    GLushort xOrigin;   // 图像在屏幕上的 X 坐标
    GLushort yOrigin;   // 图像在屏幕上的 Y 坐标
    GLushort width;   // 图像宽度
    GLushort height;  // 图像高度
    // 每像素 24 位 = RGB 各 8 位
    GLubyte bitsPerPixel;
    // 0 表示自下而上存储
    GLubyte imageDescriptor;
};
#pragma pack(pop)             // 恢复结构体对齐填充
 
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
    os << "TargaHeader { "
       << "idLength=" << static_cast<int>(header.idLength) << ", "
       << "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
       << "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
       << "colorMapOrigin=" << header.colorMapOrigin << ", "
       << "colorMapLength=" << header.colorMapLength << ", "
       << "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
       << "xOrigin=" << header.xOrigin << ", "
       << "yOrigin=" << header.yOrigin << ", "
       << "width=" << header.width << ", "
       << "height=" << header.height << ", "
       << "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
       << "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
       << " }";
    return os;
}
 
void changeSize(const int w, int h) {
    if (h == 0) h = 1; // Prevent division by zero
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, static_cast<double>(w) / static_cast<double>(h), 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
void normalize(GLfloat v[3]) {
    const GLfloat len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    if (len > 0.0F) {
        v[0] /= len;
        v[1] /= len;
        v[2] /= len;
    }
}
 
GLfloat toonShade(const GLfloat n[3], const GLfloat m[16], const GLfloat light[3]) {
    GLfloat e[3] = {
        m[0] * n[0] + m[4] * n[1] + m[8]  * n[2],
        m[1] * n[0] + m[5] * n[1] + m[9]  * n[2],
        m[2] * n[0] + m[6] * n[1] + m[10] * n[2],
    };
    normalize(e);
    return light[0] * e[0] + light[1] * e[1] + light[2] * e[2];
}
 
void renderScene() {
 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glPushMatrix();    
        
    glTranslatef(0.0F, 0.0F, zPos);
 
    for(GLfloat z = 60.0F; z >= 0.0F; z -= 10.0F){
        // 地板
        glBindTexture(GL_TEXTURE_2D, textureIds[1]); // 绑定第一个纹理
        glBegin(GL_QUADS);
            glTexCoord2f(0.0F, 0.0F); 
            glVertex3f(-10.0F, -10.0F, z);
            glTexCoord2f(1.0F, 0.0F); 
            glVertex3f( 10.0F, -10.0F, z);
            glTexCoord2f(1.0F, 1.0F); 
            glVertex3f( 10.0F,  -10.0F, z - 10.0F);
            glTexCoord2f(0.0F, 1.0F); 
            glVertex3f(-10.0F,  -10.0F, z - 10.0F);
        glEnd();
        // 天花板
        glBindTexture(GL_TEXTURE_2D, textureIds[2]); // 绑定天花板纹理
        glBegin(GL_QUADS);
            glTexCoord2f(0.0F, 1.0F); 
            glVertex3f(-10.0F,  10.0F, z - 10.0F);
            glTexCoord2f(1.0F, 1.0F); 
            glVertex3f( 10.0F,  10.0F, z - 10.0F);
            glTexCoord2f(1.0F, 0.0F); 
            glVertex3f( 10.0F,  10.0F, z);
            glTexCoord2f(0.0F, 0.0F); 
            glVertex3f(-10.0F,  10.0F, z);
        glEnd();
        // 左
        glBindTexture(GL_TEXTURE_2D, textureIds[0]); // 绑定左墙纹理
        glBegin(GL_QUADS);
            glTexCoord2f(0.0F, 0.0F); 
            glVertex3f(-10.0F, -10.0F, z);
            glTexCoord2f(1.0F, 0.0F); 
            glVertex3f(-10.0F, -10.0F, z - 10.0F);
            glTexCoord2f(1.0F, 1.0F); 
            glVertex3f(-10.0F,  10.0F, z - 10.0F);
            glTexCoord2f(0.0F, 1.0F); 
            glVertex3f(-10.0F,  10.0F, z);
        glEnd();
 
        // 右
        glBindTexture(GL_TEXTURE_2D, textureIds[0]); // 绑定右墙纹理
        glBegin(GL_QUADS);
            glTexCoord2f(0.0F, 1.0F); 
            glVertex3f(10.0F, 10.0F, z);
            glTexCoord2f(1.0F, 1.0F); 
            glVertex3f(10.0F, 10.0F, z - 10.0F);
            glTexCoord2f(1.0F, 0.0F); 
            glVertex3f(10.0F,  -10.0F, z - 10.0F);
            glTexCoord2f(0.0F, 0.0F); 
            glVertex3f(10.0F,  -10.0F, z);
        glEnd();
    }
    
    
    glPopMatrix();
 
    glutSwapBuffers();
}
 
void setupRc() {
 
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
 
    // 开启2d纹理
    glEnable(GL_TEXTURE_2D);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
 
    glGenTextures(NUM_TEXTURES, textureIds);
    for (GLuint i = 0; i < NUM_TEXTURES; ++i) {
        glBindTexture(GL_TEXTURE_2D, textureIds[i]);
 
        // 加载纹理数据
        std::ifstream file(textureFiles[i], std::ios::binary);
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open " + textureFiles[i]);
        }
        TargaHeader header;
        file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
        if(file.fail()) {
            throw std::runtime_error("Failed to read Targa header from " + textureFiles[i]);
        }
        std::cout << "Targa header: " << header << std::endl;
        const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8); 
        std::vector<char> imageData(imageSize);
        file.read(imageData.data(), imageSize);
        if(file.fail()) {
            throw std::runtime_error("Failed to read Targa image data from " + textureFiles[i]);
        }
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, header.width, header.height, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16);
    }
}
void shutdownRc() {
    glDeleteTextures(NUM_TEXTURES, textureIds);
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
    std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
    switch (key) {
        case 27:    // ESC 键
        case 'q':
        case 'Q':
            std::cout << "keyboard close..." << "\n";
            std::exit(0);
            break;
        case 'a':
        case 'A':
            break;
        case 'd':
        case 'D':
            // glutPostRedisplay();
            break;
        case 'w':
        case 'W':
            break;
        case 's':
        case 'S':
            break;
        case 'z':
        case 'Z':
            break;
        case 'x':
        case 'X':
            break;
        case 'c':
        case 'C':
            break;
        case 'v':
        case 'V':
            break;
        default:
            break;
    }
}
 
void specialKeys(const int key, const int x, const int y) {
    std::cout << std::format("specialKeys: key={}, x={}, y={}", key, x, y) << "\n";
    switch (key) {
        case GLUT_KEY_UP:
            zPos += 1.0f;
            break;
        case GLUT_KEY_DOWN:
            zPos -= 1.0f;
            break;
        case GLUT_KEY_LEFT:
            break;
        case GLUT_KEY_RIGHT:
            break;
        default:
            break;
    }
    // Refresh the Window
    glutPostRedisplay();
}
 
void processMenu(const int value) {
    std::cout << std::format("Menu selected: {}", value) << "\n";
    for(GLuint i = 0; i < NUM_TEXTURES; ++i) {
        glBindTexture(GL_TEXTURE_2D, textureIds[i]);
        switch (value) {
            case 0:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                break;
            case 1:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                break;
            case 2:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
                break;
            case 3:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
                break;
            case 4:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
                break;
            case 5:
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
                break;
        }
    }
    glutSetWindow(mainWindowId);
    glutPostRedisplay();
}
 
auto main(const int argc, const char** argv) -> int {
    std::cout << "Hello, World!" << std::endl;
    glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    mainWindowId = glutCreateWindow("OpenGL");
    if (!mainWindowId) {
        throw std::runtime_error("Failed to create OpenGL window");
    }
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    glutSpecialFunc(specialKeys);   // 特殊键事件
    glutKeyboardFunc(keyboard);  // 普通键事件
 
    glutCreateMenu(processMenu);
    glutAddMenuEntry("GL_NEAREST",0);
    glutAddMenuEntry("GL_LINEAR",1);
    glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST",2);
    glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR", 3);
    glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4);
    glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    setupRc();

    if (glutExtensionSupported("GL_EXT_texture_filter_anisotropic")) {
        GLfloat maxAnisotropy = 0.0f;
        glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAnisotropy);
        std::cout << "Anisotropic filtering supported, max: " << maxAnisotropy << std::endl;
    } else {
        std::cout << "Anisotropic filtering not supported" << std::endl;
    }

    glutMainLoop();
    return 0;
}

物体线性贴图、视觉线性贴图

#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#include <array>
#include <iosfwd>
#ifdef __APPLE__
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif
 
#include <iostream>
#include <format>
#include <numbers>
#include <fstream>
#include <vector>
#include <filesystem>
 
// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLuint NUM_TEXTURES = 2;
static const std::array<std::string, NUM_TEXTURES> textureFiles = {
    "../image/stripes.tga", // 0 砖块
    "../image/environment.tga", // 1 地板
};
 
// 变量
static GLfloat xRot = 0.0F; // z位置
static GLfloat yRot = 0.0F; // z位置
static GLfloat zRot = 0.0F; // z位置
static GLfloat zPos = -2.0F; // z位置
static int mainWindowId; // 主窗口 ID
static GLuint textureIds[NUM_TEXTURES];
static GLuint renderMode = 3;
 
 
// Targa 图像文件头结构体
#pragma pack(push, 1)          // 关闭结构体对齐填充
struct TargaHeader {
    // 图像 ID 区长度,0 = 无
    GLubyte idLength;
    GLubyte colorMapType;  // 0 = 无调色板
    GLubyte dataTypeCode;     // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
    GLushort colorMapOrigin;  // 调色板起始索引
    GLushort colorMapLength;  // 调色板颜色的数量
    GLubyte colorMapDepth;  // 调色板每条颜色的位数
    GLushort xOrigin;   // 图像在屏幕上的 X 坐标
    GLushort yOrigin;   // 图像在屏幕上的 Y 坐标
    GLushort width;   // 图像宽度
    GLushort height;  // 图像高度
    // 每像素 24 位 = RGB 各 8 位
    GLubyte bitsPerPixel;
    // 0 表示自下而上存储
    GLubyte imageDescriptor;
};
#pragma pack(pop)             // 恢复结构体对齐填充
 
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
    os << "TargaHeader { "
       << "idLength=" << static_cast<int>(header.idLength) << ", "
       << "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
       << "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
       << "colorMapOrigin=" << header.colorMapOrigin << ", "
       << "colorMapLength=" << header.colorMapLength << ", "
       << "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
       << "xOrigin=" << header.xOrigin << ", "
       << "yOrigin=" << header.yOrigin << ", "
       << "width=" << header.width << ", "
       << "height=" << header.height << ", "
       << "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
       << "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
       << " }";
    return os;
}
 
void changeSize(const int w, int h) {
    if (h == 0) h = 1; // Prevent division by zero
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, static_cast<double>(w) / static_cast<double>(h), 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
void normalize(GLfloat v[3]) {
    const GLfloat len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    if (len > 0.0F) {
        v[0] /= len;
        v[1] /= len;
        v[2] /= len;
    }
}
 
GLfloat toonShade(const GLfloat n[3], const GLfloat m[16], const GLfloat light[3]) {
    GLfloat e[3] = {
        m[0] * n[0] + m[4] * n[1] + m[8]  * n[2],
        m[1] * n[0] + m[5] * n[1] + m[9]  * n[2],
        m[2] * n[0] + m[6] * n[1] + m[10] * n[2],
    };
    normalize(e);
    return light[0] * e[0] + light[1] * e[1] + light[2] * e[2];
}
void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
    const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
    const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
    for (GLint i = 0; i <= stacks; ++i) {
        const GLfloat stackAngle1 = i * stackStep;
        const GLfloat stackAngle2 = (i + 1) * stackStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (GLint j = 0; j <= slices; ++j) {
            const GLfloat sliceAngle1 = j * sliceStep;
            const GLfloat sliceAngle2 = (j + 1) * sliceStep;
 
            const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
            const GLfloat ny1 = std::cos(stackAngle1);
            const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
            const GLfloat x1 = radius * nx1;
            const GLfloat y1 = radius * ny1;
            const GLfloat z1 = radius * nz1;
 
            const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
            const GLfloat ny2 = std::cos(stackAngle2);
            const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
            const GLfloat x2 = radius * nx2;
            const GLfloat y2 = radius * ny2;
            const GLfloat z2 = radius * nz2;
 
            glNormal3f(nx1, ny1, nz1);
            glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
                         1.0F - static_cast<GLfloat>(i) / stacks);
            glVertex3f(x1, y1, z1);
 
            glNormal3f(nx2, ny2, nz2);
            glTexCoord2f(static_cast<GLfloat>(j) / slices,
            1.0F - static_cast<GLfloat>(i + 1) / stacks);
            glVertex3f(x2, y2, z2);
        }
        glEnd();
    }
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
    
    const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
    const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
    for (int i = 0; i < numMajor; ++i) {
        const GLfloat u0 = i * majorStep;   
        const GLfloat u1 = u0 + majorStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (int j = 0; j <= numMinor; ++j) {
            const GLfloat v0 = j * minorStep;
 
            // 计算两个顶点的坐标
            const GLfloat n0[3] = {
                std::cos(v0) * std::cos(u0),
                std::cos(v0) * std::sin(u0),
                std::sin(v0)
            };
            const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
            const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
            const GLfloat z00 = minorRadius * std::sin(v0);
 
            const GLfloat n1[3] = {
                std::cos(v0) * std::cos(u1),
                std::cos(v0) * std::sin(u1),
                std::sin(v0)
            };
            const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
            const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
            const GLfloat z01 = minorRadius * std::sin(v0);
 
            glNormal3f(n0[0], n0[1], n0[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor); 
            glVertex3f(x00, y00, z00);
            glNormal3f(n1[0], n1[1], n1[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);  
            glVertex3f(x01, y01, z01);
        }
        glEnd();
    }
}
void renderScene() {
 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    glPushMatrix();    

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();
    glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glBindTexture(GL_TEXTURE_2D, textureIds[1]);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDepthMask(GL_FALSE);

    glBegin(GL_QUADS);
    glTexCoord2f(0.0F, 0.0F);
    glVertex2f(0.0F, 0.0F);
    glTexCoord2f(1.0F, 0.0F);
    glVertex2f(1.0F, 0.0F);
    glTexCoord2f(1.0F, 1.0F);
    glVertex2f(1.0F, 1.0F);
    glTexCoord2f(0.0F, 1.0F);
    glVertex2f(0.0F, 1.0F);
    glEnd();

    glMatrixMode(GL_PROJECTION);
    glPopMatrix();
    glMatrixMode(GL_MODELVIEW);

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glDepthMask(GL_TRUE);

    if (renderMode != 3) {
        glBindTexture(GL_TEXTURE_2D, textureIds[0]);
    }

    glPushMatrix();
        
    glTranslatef(0.0F, 0.0F, zPos);
    glRotatef(xRot, 1.0F, 0.0F, 0.0F);
    glRotatef(yRot, 0.0F, 1.0F, 0.0F);
    glRotatef(zRot, 0.0F, 0.0F, 1.0F);
 
    drawTorus(0.35F, 0.15F, 32, 16);
    
    glPopMatrix();
 
    glutSwapBuffers();
}
 
void setupRc() {
 
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);

    glEnable(GL_DEPTH_TEST);
    glFrontFace(GL_CCW);
    glEnable(GL_CULL_FACE);
 
    // 开启2d纹理
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    
    glGenTextures(NUM_TEXTURES, textureIds);

    for (GLuint i = 0; i < NUM_TEXTURES; ++i) {
        glBindTexture(GL_TEXTURE_2D, textureIds[i]);
 
        // 加载纹理数据
        std::ifstream file(textureFiles[i], std::ios::binary);
        std::cout << "current path: " << std::filesystem::current_path() << ", texture file: " << textureFiles[i] << std::endl;
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open " + textureFiles[i]);
        }
        TargaHeader header;
        file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
        if(file.fail()) {
            throw std::runtime_error("Failed to read Targa header from " + textureFiles[i]);
        }
        std::cout << "Targa header: " << header << std::endl;
        const std::size_t imageSize = header.width * header.height * (header.bitsPerPixel / 8); 
        std::vector<char> imageData(imageSize);
        file.read(imageData.data(), imageSize);
        if(file.fail()) {
            throw std::runtime_error("Failed to read Targa image data from " + textureFiles[i]);
        }
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        // gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, header.width, header.height, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8,
                     header.width,
                     header.height,
                     0,
                     GL_BGR,
                     GL_UNSIGNED_BYTE,
                     imageData.data()
        );
 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    }
}
void shutdownRc() {
    glDeleteTextures(NUM_TEXTURES, textureIds);
}
// 键盘回调:按 ESC 或 'q' 退出
void keyboard(const unsigned char key, const int x, const int y) {
    std::cout << std::format("keyboard: key={}, x={}, y={}", key, x, y) << "\n";
    switch (key) {
        case 27:    // ESC 键
        case 'q':
        case 'Q':
            std::cout << "keyboard close..." << "\n";
            std::exit(0);
            break;
        case 'a':
        case 'A':
            yRot -= 5.0F;
            break;
        case 'd':
        case 'D':
            yRot += 5.0F;
            break;
        case 'w':
        case 'W':
            xRot -= 5.0F;
            break;
        case 's':
        case 'S':
            xRot += 5.0F;
            break;
        case 'z':
        case 'Z':
            zRot -= 5.0F;
            break;
        case 'x':
        case 'X':
            zRot += 5.0F;
            break;
        case 'c':
        case 'C':
            break;
        case 'v':
        case 'V':
            break;
        default:
            break;
    }
    glutPostRedisplay();
}
 
void specialKeys(const int key, const int x, const int y) {
    std::cout << std::format("specialKeys: key={}, x={}, y={}", key, x, y) << "\n";
    switch (key) {
        case GLUT_KEY_UP:
            zPos += 1.0f;
            break;
        case GLUT_KEY_DOWN:
            zPos -= 1.0f;
            break;
        case GLUT_KEY_LEFT:
            break;
        case GLUT_KEY_RIGHT:
            break;
        default:
            break;
    }
    // Refresh the Window
    glutPostRedisplay();
}
 
void processMenu(const int value) {
    std::cout << std::format("processMenu: value={}", value) << "\n";
    renderMode = value;
    GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f};
    switch (value) {
        case 1:
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
            glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane);
            glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane);
            std::cout << "Object Linear mode selected" << "\n";
            break;
        case 2:
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
            glTexGenfv(GL_S, GL_EYE_PLANE, zPlane);
            glTexGenfv(GL_T, GL_EYE_PLANE, zPlane);
            std::cout << "Eye Linear mode selected" << "\n";
            break;
        case 3:
        default:
            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
            std::cout << "Sphere Map mode selected" << "\n";
            break;
    }
    glutSetWindow(mainWindowId);
    glutPostRedisplay();
}
 
auto main(const int argc, const char** argv) -> int {
    std::cout << "Hello, World!" << std::endl;
    glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    mainWindowId = glutCreateWindow("OpenGL");
    if (!mainWindowId) {
        throw std::runtime_error("Failed to create OpenGL window");
    }
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    glutSpecialFunc(specialKeys);   // 特殊键事件
    glutKeyboardFunc(keyboard);  // 普通键事件
 
    glutCreateMenu(processMenu);
    glutAddMenuEntry("Object Linear",1);
	glutAddMenuEntry("Eye Linear",2);
	glutAddMenuEntry("Sphere Map",3);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
 
    setupRc();
    glutMainLoop();
    return 0;
}

立方体贴图


#include <OpenGL/gl.h>
#include <cstddef>
#include <cmath>
#include <fstream>
#include <vector>
#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif

#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <random>
#include <fstream>
#include <vector>
#include <numbers>

static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;

static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
    "../image/pos_x.tga",
    "../image/neg_x.tga",
    "../image/pos_y.tga",
    "../image/neg_y.tga",
    "../image/pos_z.tga",
    "../image/neg_z.tga"
};
static constexpr GLint CUBE[CUBE_FACES_COUNT] = { 
    GL_TEXTURE_CUBE_MAP_POSITIVE_X, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 
};

static constexpr GLint GROUND_TEXTURE = 0;
static constexpr GLint SPHERE_TEXTURE = 1;
static constexpr GLint CUBE_TEXTURE = 2;
static constexpr GLint NUM_TEXTURES = 3;
static constexpr std::array<std::string, NUM_TEXTURES> TEXTURE_FILES = {
    "../image/grass.tga",
    "../image/orb.tga",
    "../image/down.tga"
};

static constexpr GLint NUM_SPHERES = 30;
static constexpr GLfloat fLightPos[] = { -100.0f, 100.0f, 50.0f, 1.0f };
struct GLFrame {
    GLfloat vLocation[3]; // 位置  -> move x, y, z
    GLfloat vUp[3];       // 上方向 -> y
    GLfloat vForward[3];  // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};

static constexpr GLfloat fBrightLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
static constexpr GLfloat fNoLight[] = { 0.0f, 0.0f, 0.0f, 0.0f };

static GLFrame frameCamera;
static GLFrame spheres[NUM_SPHERES];
static GLuint textures[NUM_TEXTURES];
static GLfloat yRot = 0.0f;



// Targa 图像文件头结构体
#pragma pack(push, 1)          // 关闭结构体对齐填充
struct TargaHeader {
    // 图像 ID 区长度,0 = 无
    GLubyte idLength;
    GLubyte colorMapType;  // 0 = 无调色板
    GLubyte dataTypeCode;     // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
    GLushort colorMapOrigin;  // 调色板起始索引
    GLushort colorMapLength;  // 调色板颜色的数量
    GLubyte colorMapDepth;  // 调色板每条颜色的位数
    GLushort xOrigin;   // 图像在屏幕上的 X 坐标
    GLushort yOrigin;   // 图像在屏幕上的 Y 坐标
    GLushort width;   // 图像宽度
    GLushort height;  // 图像高度
    // 每像素 24 位 = RGB 各 8 位
    GLubyte bitsPerPixel;
    // 0 表示自下而上存储
    GLubyte imageDescriptor;
};
#pragma pack(pop)             // 恢复结构体对齐填充
 
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
    os << "TargaHeader { "
       << "idLength=" << static_cast<int>(header.idLength) << ", "
       << "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
       << "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
       << "colorMapOrigin=" << header.colorMapOrigin << ", "
       << "colorMapLength=" << header.colorMapLength << ", "
       << "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
       << "xOrigin=" << header.xOrigin << ", "
       << "yOrigin=" << header.yOrigin << ", "
       << "width=" << header.width << ", "
       << "height=" << header.height << ", "
       << "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
       << "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
       << " }";
    return os;
}

void gltInitFrame(GLFrame *pFrame) {
    pFrame->vLocation[0] = 0.0f;
    pFrame->vLocation[1] = 0.0f;
    pFrame->vLocation[2] = 0.0f;
 
    pFrame->vUp[0] = 0.0f;
    pFrame->vUp[1] = 1.0f;
    pFrame->vUp[2] = 0.0f;
 
    pFrame->vForward[0] = 0.0f;
    pFrame->vForward[1] = 0.0f;
    pFrame->vForward[2] = -1.0f; // 为什么是-1?
}

void setupRc(){
    // Setup rendering context
    constexpr GLfloat fLowLight[] = {0.25f, 0.25f, 0.25f, 1.0f};

    glClearColor(fLowLight[0], fLowLight[1], fLowLight[2], fLowLight[3]);

    glCullFace(GL_BACK); // 指定要剔除的面为背面
    glFrontFace(GL_CCW); // 指定顶点的逆时针顺序为正面
    glEnable(GL_CULL_FACE); // 启用面剔除
    glEnable(GL_DEPTH_TEST); // 启用深度测试

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight); // 设置全局环境光
    glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); // 设置光照模型为独立的镜面反射颜色
    glLightfv(GL_LIGHT0, GL_AMBIENT, fLowLight); // 设置光源的环境光为low
    glLightfv(GL_LIGHT0, GL_DIFFUSE, fBrightLight); // 设置光源的漫反射光为亮光
    glLightfv(GL_LIGHT0, GL_SPECULAR, fBrightLight); // 设置光源的镜面反射光为亮光
    glEnable(GL_LIGHTING); // 启用光照
    glEnable(GL_LIGHT0); // 启用光源0

    glEnable(GL_COLOR_MATERIAL); // 启用颜色材质,使得物体颜色影响光照计算
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); // 设置颜色材质的属性为前面,并影响环境光和漫反射光
    glMateriali(GL_FRONT, GL_SHININESS, 128); // 设置前面材质的高光指数

    // 初始化相机和球体的帧结构体
    gltInitFrame(&frameCamera);

    std::random_device rd; // 硬件熵源,用于播种
    std::mt19937_64 gen(rd());  // Mersenne Twister 引擎
    std::uniform_real_distribution<GLfloat> dis(-20.0f, 20.0f);
    for(int i = 0; i < NUM_SPHERES; ++i){
        gltInitFrame(&spheres[i]);
        spheres[i].vLocation[0] = dis(gen); // [-20, 20]
        spheres[i].vLocation[1] = 0.0f;
        spheres[i].vLocation[2] = dis(gen); // [-20, 20]
    }

    glEnable(GL_TEXTURE_2D);
    glGenTextures(NUM_TEXTURES, textures);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    for(int i = 0; i < NUM_TEXTURES - 1; i ++ ){
        std::ifstream file(TEXTURE_FILES[i], std::ios::binary);
        if(!file){
            throw std::runtime_error("Failed to open texture file: " + std::string(TEXTURE_FILES[i]));
        }
        TargaHeader targaHeader;
        file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
        if(!file){
            throw std::runtime_error("Failed to read Targa header from file: " + std::string(TEXTURE_FILES[i]));
        }
        std::cout << "tga file: " << TEXTURE_FILES[i] << ", header: " << targaHeader << std::endl;
        const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
        std::vector<char> imageData(imageSize);
        file.read(imageData.data(), imageSize);
        if(!file){
            throw std::runtime_error("Failed to read image data from file: " + std::string(TEXTURE_FILES[i]));
        }
        glBindTexture(GL_TEXTURE_2D, textures[i]);
        // glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
        glGenerateMipmap(GL_TEXTURE_2D);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    }

    glBindTexture(GL_TEXTURE_CUBE_MAP, textures[NUM_TEXTURES - 1]);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    for(int i = 0; i < CUBE_FACES_COUNT; i++){
        std::ifstream file(CUBE_FACES[i], std::ios::binary);
        if(!file){
            throw std::runtime_error("Failed to open texture file: " + std::string(CUBE_FACES[i]));
        }
        TargaHeader targaHeader;
        file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
        if(!file){
            throw std::runtime_error("Failed to read Targa header from file: " + std::string(CUBE_FACES[i]));
        }
        std::cout << "tga file: " << CUBE_FACES[i] << ", header: " << targaHeader << std::endl;
        const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
        std::vector<char> imageData(imageSize);
        file.read(imageData.data(), imageSize);
        if(!file){
            throw std::runtime_error("Failed to read image data from file: " + std::string(CUBE_FACES[i]));
        }
        glTexImage2D(CUBE[i], 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
    }
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
}

void shutdownRc(){
    // Cleanup rendering context
    glDeleteTextures(NUM_TEXTURES, textures);
}

void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
    const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
    const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
    for (GLint i = 0; i < stacks; ++i) {
        const GLfloat stackAngle1 = i * stackStep;
        const GLfloat stackAngle2 = (i + 1) * stackStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (GLint j = 0; j <= slices; ++j) {
            const GLfloat sliceAngle1 = j * sliceStep;
            const GLfloat sliceAngle2 = (j + 1) * sliceStep;
 
            const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
            const GLfloat ny1 = std::cos(stackAngle1);
            const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
            const GLfloat x1 = radius * nx1;
            const GLfloat y1 = radius * ny1;
            const GLfloat z1 = radius * nz1;
 
            const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
            const GLfloat ny2 = std::cos(stackAngle2);
            const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
            const GLfloat x2 = radius * nx2;
            const GLfloat y2 = radius * ny2;
            const GLfloat z2 = radius * nz2;
 
            glNormal3f(nx1, ny1, nz1);
            glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
                         1.0F - static_cast<GLfloat>(i) / stacks);
            glVertex3f(x1, y1, z1);
 
            glNormal3f(nx2, ny2, nz2);
            glTexCoord2f(static_cast<GLfloat>(j) / slices,
            1.0F - static_cast<GLfloat>(i + 1) / stacks);
            glVertex3f(x2, y2, z2);
        }
        glEnd();
    }
}

void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
    
    const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
    const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
    for (int i = 0; i < numMajor; ++i) {
        const GLfloat u0 = i * majorStep;   
        const GLfloat u1 = u0 + majorStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (int j = 0; j <= numMinor; ++j) {
            const GLfloat v0 = j * minorStep;
 
            // 计算两个顶点的坐标
            const GLfloat n0[3] = {
                std::cos(v0) * std::cos(u0),
                std::cos(v0) * std::sin(u0),
                std::sin(v0)
            };
            const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
            const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
            const GLfloat z00 = minorRadius * std::sin(v0);
 
            const GLfloat n1[3] = {
                std::cos(v0) * std::cos(u1),
                std::cos(v0) * std::sin(u1),
                std::sin(v0)
            };
            const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
            const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
            const GLfloat z01 = minorRadius * std::sin(v0);
 
            glNormal3f(n0[0], n0[1], n0[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor); 
            glVertex3f(x00, y00, z00);
            glNormal3f(n1[0], n1[1], n1[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);  
            glVertex3f(x01, y01, z01);
        }
        glEnd();
    }
}

/**
 * 视图矩阵 = 上面的逆:旋转转置 + 平移取负
 * 把世界挪到相机眼前
 */
void gltApplyCameraTransform(const GLFrame *pCamera) {
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
 
    // 构建视图矩阵 (View matrix) 列主序 (column-major order)
    GLfloat m[16];   // 同样的轴,改填「行」,即转置
    m[0]=r[0];              m[4]=r[1];              m[8] =r[2];              m[12]=0.0F;
    m[1]=pCamera->vUp[0];   m[5]=pCamera->vUp[1];   m[9] =pCamera->vUp[2];   m[13]=0.0F;
    m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;              m[7]=0.0F;              m[11]=0.0F;              m[15]=1.0F;
 
    glMultMatrixf(m);
    glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}

/**
 * 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
 * 四列依次是: right、up、-forward、location
 * 物体→世界
 */
void gltApplyActorTransform(const GLFrame *frame){
    // 算出物体的右向量 (Right vector)
    GLfloat r[3];
    r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
    r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
    r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];

    // 构建物体矩阵 (Model matrix) 列主序 (column-major order)
    // 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
    // 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
    GLfloat m[16];
    m[0]=r[0];                  m[4]=frame->vUp[0];             m[8] =-frame->vForward[0];  m[12]=frame->vLocation[0];
    m[1]=r[1];                  m[5]=frame->vUp[1];             m[9] =-frame->vForward[1];  m[13]=frame->vLocation[1];
    m[2]=r[2];                  m[6]=frame->vUp[2];             m[10]=-frame->vForward[2];  m[14]=frame->vLocation[2];
    m[3]=0.0F;                  m[7]=0.0F;                      m[11]=0.0F;                 m[15]=1.0F;

    glMultMatrixf(m);
}

void drawGround(){
    constexpr GLfloat fExtent = 20.0F;
    constexpr GLfloat fStep = 1.0F;
    constexpr GLfloat y = -0.4F;
 
    glBindTexture(GL_TEXTURE_2D, textures[GROUND_TEXTURE]); // 草地
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    for (GLfloat i = -fExtent; i <= fExtent; i += fStep) {
        glBegin(GL_TRIANGLE_STRIP);
        for (GLfloat j = fExtent; j >= -fExtent; j -= fStep) {
            glTexCoord2f((i + fExtent) / fExtent * 2.0F, (j - fExtent) / -fExtent * 2.0F);
            glNormal3f(0.0F,1.0F,0.0F);
            glVertex3f(i, y, j);
            glTexCoord2f((i + fExtent + fStep) / fExtent * 2.0F, (j - fExtent) / -fExtent * 2.0F);
            glNormal3f(0.0F,1.0F,0.0F);
            glVertex3f(i + fStep, y, j);
        }
        glEnd();
    }
}
void drawInhabitants(){
    yRot += 0.5F;

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glBindTexture(GL_TEXTURE_2D, textures[SPHERE_TEXTURE]); // 假设有一个居民纹理
    for(int i = 0; i < NUM_SPHERES; i++){
        glPushMatrix();
        gltApplyActorTransform(&spheres[i]);
        drawSphere(0.3f, 21, 11);
        glPopMatrix();
    }
    glPushMatrix();
        glTranslatef(0.0f, 0.1f, -2.5f);
        glMaterialfv(GL_FRONT, GL_SPECULAR, fBrightLight); // 设置前面材质的镜面反射光为亮光
        glRotatef(yRot, 0.0f, 1.0f, 0.0f);
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_TEXTURE_CUBE_MAP);
        glBindTexture(GL_TEXTURE_CUBE_MAP, textures[CUBE_TEXTURE]); // 假设有一个立方体贴图纹理
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
        glEnable(GL_TEXTURE_GEN_R);
        // Draw the cube-mapped object here
        drawTorus(0.35, 0.15, 61, 37);
        // After drawing, disable the texture generation and cube map
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_TEXTURE_GEN_R);
        glDisable(GL_TEXTURE_CUBE_MAP);
        glEnable(GL_TEXTURE_2D);
        glMaterialfv(GL_FRONT, GL_SPECULAR, fNoLight);

    glPopMatrix();
}
void renderScene(){
    // Rendering code goes here
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    gltApplyCameraTransform(&frameCamera); // Apply the camera transformation to position the world relative to the camera
    glLightfv(GL_LIGHT0, GL_POSITION, fLightPos);
    glColor3f(1.0f, 1.0f, 1.0f);
    drawGround();
    drawInhabitants();

    glPopMatrix();

    glutSwapBuffers();
}

void changeSize(const int w, int h){
    if(h == 0) h = 1;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
} 

void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
    frame->vLocation[0] += frame->vForward[0] * distance;
    frame->vLocation[1] += frame->vForward[1] * distance;
    frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
 * 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
 * @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
 *
 * 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
 *     v' = v·cosθ + (axis × v)·sinθ
 * 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
 * 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
 */
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
    // axis × forward —— 注意这是 -right(right = forward × up),
    // 这个负号决定了旋转方向,别误当成右向量
    const GLfloat vAxisCross[3] = {
        frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
        frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
        frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
    };

    // 角度转弧度
    const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
    const GLfloat fCos = std::cos(rad);
    const GLfloat fSin = std::sin(rad);

    // vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
    frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
    frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
    frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];

    // 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
    // forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
    const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
                                  frame->vForward[1] * frame->vForward[1] +
                                  frame->vForward[2] * frame->vForward[2]);
    frame->vForward[0] /= len;
    frame->vForward[1] /= len;
    frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
    switch(key){
        case GLUT_KEY_UP:
            gltMoveFrameForward(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_DOWN:
            gltMoveFrameForward(&frameCamera, -0.1f);
            break;
        case GLUT_KEY_LEFT:
            gltRotateFrameY(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_RIGHT:
            gltRotateFrameY(&frameCamera, -0.1f);
            break;
    }
    glutPostRedisplay();
}

void timerFunc(const int _){
    glutPostRedisplay();
    glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}

int main(int args, char** argv){
    std::cout << "Starting program..." << std::endl;
    glutInit(&args, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    if(mainWindowId == 0){
        throw std::runtime_error("Failed to create main window");
    }
    
    // ======= 事件处理 =======
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    glutSpecialFunc(specialKeys);
    glutTimerFunc(33, timerFunc, 1); // 启动定时器,大约 30 FPS
    // ======= 渲染循环 =======

    setupRc();
    glutMainLoop();
    shutdownRc();
    
    return 0;
}

天空盒

#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif

#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <random>
#include <fstream>
#include <vector>
#include <numbers>

static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;

static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
    "../image/pos_x.tga",
    "../image/neg_x.tga",
    "../image/pos_y.tga",
    "../image/neg_y.tga",
    "../image/pos_z.tga",
    "../image/neg_z.tga"
};
static constexpr GLint CUBE[CUBE_FACES_COUNT] = { 
    GL_TEXTURE_CUBE_MAP_POSITIVE_X, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 
};

struct GLFrame {
    GLfloat vLocation[3]; // 位置  -> move x, y, z
    GLfloat vUp[3];       // 上方向 -> y
    GLfloat vForward[3];  // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};


static GLFrame frameCamera;



// Targa 图像文件头结构体
#pragma pack(push, 1)          // 关闭结构体对齐填充
struct TargaHeader {
    // 图像 ID 区长度,0 = 无
    GLubyte idLength;
    GLubyte colorMapType;  // 0 = 无调色板
    GLubyte dataTypeCode;     // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
    GLushort colorMapOrigin;  // 调色板起始索引
    GLushort colorMapLength;  // 调色板颜色的数量
    GLubyte colorMapDepth;  // 调色板每条颜色的位数
    GLushort xOrigin;   // 图像在屏幕上的 X 坐标
    GLushort yOrigin;   // 图像在屏幕上的 Y 坐标
    GLushort width;   // 图像宽度
    GLushort height;  // 图像高度
    // 每像素 24 位 = RGB 各 8 位
    GLubyte bitsPerPixel;
    // 0 表示自下而上存储
    GLubyte imageDescriptor;
};
#pragma pack(pop)             // 恢复结构体对齐填充
 
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
    os << "TargaHeader { "
       << "idLength=" << static_cast<int>(header.idLength) << ", "
       << "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
       << "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
       << "colorMapOrigin=" << header.colorMapOrigin << ", "
       << "colorMapLength=" << header.colorMapLength << ", "
       << "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
       << "xOrigin=" << header.xOrigin << ", "
       << "yOrigin=" << header.yOrigin << ", "
       << "width=" << header.width << ", "
       << "height=" << header.height << ", "
       << "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
       << "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
       << " }";
    return os;
}


void gltInitFrame(GLFrame *pFrame) {
    pFrame->vLocation[0] = 0.0f;
    pFrame->vLocation[1] = 0.0f;
    pFrame->vLocation[2] = 0.0f;
 
    pFrame->vUp[0] = 0.0f;
    pFrame->vUp[1] = 1.0f;
    pFrame->vUp[2] = 0.0f;
 
    pFrame->vForward[0] = 0.0f;
    pFrame->vForward[1] = 0.0f;
    pFrame->vForward[2] = -1.0f; // 为什么是-1?
}

void setupRc(){

    gltInitFrame(&frameCamera);

    glCullFace(GL_BACK); // 指定要剔除的面为背面
    glFrontFace(GL_CCW); // 指定顶点的逆时针顺序为正面
    glEnable(GL_CULL_FACE); // 启用面剔除
    glEnable(GL_DEPTH_TEST); // 启用深度测试

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    for(int i = 0; i < CUBE_FACES_COUNT; i++){
        std::ifstream file(CUBE_FACES[i], std::ios::binary);
        if(!file){
            throw std::runtime_error("Failed to open texture file: " + std::string(CUBE_FACES[i]));
        }
        TargaHeader targaHeader;
        file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
        if(!file){
            throw std::runtime_error("Failed to read Targa header from file: " + std::string(CUBE_FACES[i]));
        }
        std::cout << "tga file: " << CUBE_FACES[i] << ", header: " << targaHeader << std::endl;
        const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
        std::vector<char> imageData(imageSize);
        file.read(imageData.data(), imageSize);
        if(!file){
            throw std::runtime_error("Failed to read image data from file: " + std::string(CUBE_FACES[i]));
        }
        glTexImage2D(CUBE[i], 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
    }
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glEnable(GL_TEXTURE_CUBE_MAP);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
}

void shutdownRc(){
}

void drawSkyBox(){
    constexpr GLfloat SIZE = 15.0F; // Skybox size
    glBegin(GL_QUADS);

    // neg x
    glTexCoord3f(-1.0F, -1.0F, 1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glTexCoord3f(-1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glTexCoord3f(-1.0F,  1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    glTexCoord3f(-1.0F,  1.0F,  1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    // pos x
    glTexCoord3f(1.0F, -1.0F, -1.0F);
    glVertex3f(SIZE, -SIZE, -SIZE);

    glTexCoord3f(1.0F, -1.0F,  1.0F);
    glVertex3f(SIZE, -SIZE,  SIZE);

    glTexCoord3f(1.0F,  1.0F,  1.0F);
    glVertex3f(SIZE,  SIZE,  SIZE);

    glTexCoord3f(1.0F,  1.0F, -1.0F);
    glVertex3f(SIZE,  SIZE, -SIZE);

    // neg z
    glTexCoord3f(-1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glTexCoord3f( 1.0F, -1.0F, -1.0F);
    glVertex3f( SIZE, -SIZE, -SIZE);

    glTexCoord3f( 1.0F,  1.0F, -1.0F);
    glVertex3f( SIZE,  SIZE, -SIZE);

    glTexCoord3f(-1.0F,  1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    // pos z

    glTexCoord3f( 1.0F, -1.0F, 1.0F);
    glVertex3f( SIZE, -SIZE,  SIZE);

    glTexCoord3f(-1.0F, -1.0F, 1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glTexCoord3f(-1.0F,  1.0F, 1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    glTexCoord3f( 1.0F,  1.0F, 1.0F);
    glVertex3f( SIZE,  SIZE,  SIZE);

    // pos y

    glTexCoord3f(-1.0F, 1.0F,  1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    glTexCoord3f(-1.0F, 1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    glTexCoord3f( 1.0F, 1.0F, -1.0F);
    glVertex3f( SIZE,  SIZE, -SIZE);

    glTexCoord3f( 1.0F, 1.0F,  1.0F);
    glVertex3f( SIZE,  SIZE,  SIZE);

    // neg y
    glTexCoord3f(-1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glTexCoord3f(-1.0F, -1.0F,  1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glTexCoord3f( 1.0F, -1.0F,  1.0F);
    glVertex3f( SIZE, -SIZE,  SIZE);

    glTexCoord3f( 1.0F, -1.0F, -1.0F);
    glVertex3f( SIZE, -SIZE, -SIZE);

    glEnd();
}

void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
    const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
    const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
    for (GLint i = 0; i < stacks; ++i) {
        const GLfloat stackAngle1 = i * stackStep;
        const GLfloat stackAngle2 = (i + 1) * stackStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (GLint j = 0; j <= slices; ++j) {
            const GLfloat sliceAngle1 = j * sliceStep;
            const GLfloat sliceAngle2 = (j + 1) * sliceStep;
 
            const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
            const GLfloat ny1 = std::cos(stackAngle1);
            const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
            const GLfloat x1 = radius * nx1;
            const GLfloat y1 = radius * ny1;
            const GLfloat z1 = radius * nz1;
 
            const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
            const GLfloat ny2 = std::cos(stackAngle2);
            const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
            const GLfloat x2 = radius * nx2;
            const GLfloat y2 = radius * ny2;
            const GLfloat z2 = radius * nz2;
 
            // 每个 j 先发下圈顶点、再发上圈顶点,三角形绕序从球外看才是逆时针(GL_CCW)。
            // 若「先上后下」,整颗球的外表面会被当作背面剔除,实际渲染的是远侧内表面,
            // 反射向量在竖直方向上是反的,天空就会被贴到球的下半部分。
            glNormal3f(nx2, ny2, nz2);
            glTexCoord2f(static_cast<GLfloat>(j) / slices,
                         1.0F - static_cast<GLfloat>(i + 1) / stacks);
            glVertex3f(x2, y2, z2);

            glNormal3f(nx1, ny1, nz1);
            glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
                         1.0F - static_cast<GLfloat>(i) / stacks);
            glVertex3f(x1, y1, z1);
        }
        glEnd();
    }
}

void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
    
    const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
    const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
    for (int i = 0; i < numMajor; ++i) {
        const GLfloat u0 = i * majorStep;   
        const GLfloat u1 = u0 + majorStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (int j = 0; j <= numMinor; ++j) {
            const GLfloat v0 = j * minorStep;
 
            // 计算两个顶点的坐标
            const GLfloat n0[3] = {
                std::cos(v0) * std::cos(u0),
                std::cos(v0) * std::sin(u0),
                std::sin(v0)
            };
            const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
            const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
            const GLfloat z00 = minorRadius * std::sin(v0);
 
            const GLfloat n1[3] = {
                std::cos(v0) * std::cos(u1),
                std::cos(v0) * std::sin(u1),
                std::sin(v0)
            };
            const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
            const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
            const GLfloat z01 = minorRadius * std::sin(v0);
 
            glNormal3f(n0[0], n0[1], n0[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor); 
            glVertex3f(x00, y00, z00);
            glNormal3f(n1[0], n1[1], n1[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);  
            glVertex3f(x01, y01, z01);
        }
        glEnd();
    }
}

/**
 * 视图矩阵 = 上面的逆:旋转转置 + 平移取负
 * 把世界挪到相机眼前
 */
void gltApplyCameraTransform(const GLFrame *pCamera) {
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
 
    // 构建视图矩阵 (View matrix) 列主序 (column-major order)
    GLfloat m[16];   // 同样的轴,改填「行」,即转置
    m[0]=r[0];              m[4]=r[1];              m[8] =r[2];              m[12]=0.0F;
    m[1]=pCamera->vUp[0];   m[5]=pCamera->vUp[1];   m[9] =pCamera->vUp[2];   m[13]=0.0F;
    m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;              m[7]=0.0F;              m[11]=0.0F;              m[15]=1.0F;
 
    glMultMatrixf(m);
    glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}

/**
 * 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
 * 四列依次是: right、up、-forward、location
 * 物体→世界
 */
void gltApplyActorTransform(const GLFrame *frame){
    // 算出物体的右向量 (Right vector)
    GLfloat r[3];
    r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
    r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
    r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];

    // 构建物体矩阵 (Model matrix) 列主序 (column-major order)
    // 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
    // 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
    GLfloat m[16];
    m[0]=r[0];                  m[4]=frame->vUp[0];             m[8] =-frame->vForward[0];  m[12]=frame->vLocation[0];
    m[1]=r[1];                  m[5]=frame->vUp[1];             m[9] =-frame->vForward[1];  m[13]=frame->vLocation[1];
    m[2]=r[2];                  m[6]=frame->vUp[2];             m[10]=-frame->vForward[2];  m[14]=frame->vLocation[2];
    m[3]=0.0F;                  m[7]=0.0F;                      m[11]=0.0F;                 m[15]=1.0F;

    glMultMatrixf(m);
}

/**
 * 反射贴图用的纹理矩阵:相机视图旋转的逆(旋转矩阵的逆就是转置)。
 *
 * GL_REFLECTION_MAP 生成的 (s,t,r) 已经是 eye space 的方向,
 * 而 cubemap 的面(以及天空盒)是按世界方向对齐的,所以需要转回 world space:
 *     R_world = V⁻¹ · R_eye = Vᵀ · R_eye
 * 环境贴图视为无限远,只取旋转部分,不平移。
 *
 * 注意不要直接拿视图矩阵 V(world → eye)来乘:那样相机每旋转 θ,
 * 反射方向会被多转一次,和天空盒对不上。
 */
void gltGetCameraInverseMatrix(const GLFrame *pCamera, GLfloat *m){
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];

    // V 的三行分别是 right、up、-forward;转置后它们变成三列。
    // 列主序数组里直接按「列 = V 原来的行」填入即可。
    m[0]=r[0];                  m[4]=pCamera->vUp[0];   m[8] =-pCamera->vForward[0]; m[12]=0.0F;
    m[1]=r[1];                  m[5]=pCamera->vUp[1];   m[9] =-pCamera->vForward[1]; m[13]=0.0F;
    m[2]=r[2];                  m[6]=pCamera->vUp[2];   m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;                  m[7]=0.0F;              m[11]=0.0F;                  m[15]=1.0F;
}

void renderScene(){
    // Rendering code goes here
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    gltApplyCameraTransform(&frameCamera); // Apply the camera transformation to position the world relative to the camera
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);

    drawSkyBox();

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);

    glPushMatrix();
        glTranslatef(0.0f, 0.0f, -3.0f); // Example translation, adjust as needed
        
        glMatrixMode(GL_TEXTURE); // Switch to texture matrix mode to apply texture transformations if needed

        glPushMatrix(); // Save the current texture matrix
            // 反射坐标在 eye space,用相机旋转的逆转回 world space,才能和天空盒对齐
            GLfloat cameraInverse[16];
            gltGetCameraInverseMatrix(&frameCamera, cameraInverse);
            glMultMatrixf(cameraInverse);
            
            drawSphere(0.75F, 41, 41); // Assuming you have a function to draw a sphere

            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);

        glPopMatrix(); // Restore the texture matrix


    glPopMatrix();


    glPopMatrix();

    glutSwapBuffers();
}

void changeSize(const int w, int h){
    if(h == 0) h = 1;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
} 

void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
    frame->vLocation[0] += frame->vForward[0] * distance;
    frame->vLocation[1] += frame->vForward[1] * distance;
    frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
 * 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
 * @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
 *
 * 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
 *     v' = v·cosθ + (axis × v)·sinθ
 * 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
 * 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
 */
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
    // axis × forward —— 注意这是 -right(right = forward × up),
    // 这个负号决定了旋转方向,别误当成右向量
    const GLfloat vAxisCross[3] = {
        frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
        frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
        frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
    };

    // 角度转弧度
    const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
    const GLfloat fCos = std::cos(rad);
    const GLfloat fSin = std::sin(rad);

    // vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
    frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
    frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
    frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];

    // 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
    // forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
    const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
                                  frame->vForward[1] * frame->vForward[1] +
                                  frame->vForward[2] * frame->vForward[2]);
    frame->vForward[0] /= len;
    frame->vForward[1] /= len;
    frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
    switch(key){
        case GLUT_KEY_UP:
            gltMoveFrameForward(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_DOWN:
            gltMoveFrameForward(&frameCamera, -0.1f);
            break;
        case GLUT_KEY_LEFT:
            gltRotateFrameY(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_RIGHT:
            gltRotateFrameY(&frameCamera, -0.1f);
            break;
    }
    glutPostRedisplay();
}

void timerFunc(const int _){
    glutPostRedisplay();
    glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}

int main(int args, char** argv){
    std::cout << "Starting program..." << std::endl;
    glutInit(&args, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    if(mainWindowId == 0){
        throw std::runtime_error("Failed to create main window");
    }
    
    // ======= 事件处理 =======
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    glutSpecialFunc(specialKeys);
    glutTimerFunc(33, timerFunc, 1); // 启动定时器,大约 30 FPS
    // ======= 渲染循环 =======
    GLint iUnits;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);
    std::cout << "Max texture units: " << iUnits << std::endl;

    setupRc();
    glutMainLoop();
    shutdownRc();
    
    return 0;
}

多重纹理

#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif

#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <fstream>
#include <vector>
#include <numbers>

static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;

static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
    "../image/pos_x.tga",
    "../image/neg_x.tga",
    "../image/pos_y.tga",
    "../image/neg_y.tga",
    "../image/pos_z.tga",
    "../image/neg_z.tga"
};
static constexpr std::string COLOR_TEXTURE = "../image/tarnish.tga";
static constexpr GLint CUBE[CUBE_FACES_COUNT] = { 
    GL_TEXTURE_CUBE_MAP_POSITIVE_X, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 
};
static constexpr GLint TEXTURE_COUNT = 2;
static constexpr GLint CUBE_MAP = 0;
static constexpr GLint COLOR_MAP = 1;
struct GLFrame {
    GLfloat vLocation[3]; // 位置  -> move x, y, z
    GLfloat vUp[3];       // 上方向 -> y
    GLfloat vForward[3];  // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};


static GLFrame frameCamera;
static GLuint textures[TEXTURE_COUNT];


// Targa 图像文件头结构体
#pragma pack(push, 1)          // 关闭结构体对齐填充
struct TargaHeader {
    // 图像 ID 区长度,0 = 无
    GLubyte idLength;
    GLubyte colorMapType;  // 0 = 无调色板
    GLubyte dataTypeCode;     // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
    GLushort colorMapOrigin;  // 调色板起始索引
    GLushort colorMapLength;  // 调色板颜色的数量
    GLubyte colorMapDepth;  // 调色板每条颜色的位数
    GLushort xOrigin;   // 图像在屏幕上的 X 坐标
    GLushort yOrigin;   // 图像在屏幕上的 Y 坐标
    GLushort width;   // 图像宽度
    GLushort height;  // 图像高度
    // 每像素 24 位 = RGB 各 8 位
    GLubyte bitsPerPixel;
    // 0 表示自下而上存储
    GLubyte imageDescriptor;
};
#pragma pack(pop)             // 恢复结构体对齐填充
 
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
    os << "TargaHeader { "
       << "idLength=" << static_cast<int>(header.idLength) << ", "
       << "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
       << "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
       << "colorMapOrigin=" << header.colorMapOrigin << ", "
       << "colorMapLength=" << header.colorMapLength << ", "
       << "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
       << "xOrigin=" << header.xOrigin << ", "
       << "yOrigin=" << header.yOrigin << ", "
       << "width=" << header.width << ", "
       << "height=" << header.height << ", "
       << "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
       << "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
       << " }";
    return os;
}


void gltInitFrame(GLFrame *pFrame) {
    pFrame->vLocation[0] = 0.0f;
    pFrame->vLocation[1] = 0.0f;
    pFrame->vLocation[2] = 0.0f;
 
    pFrame->vUp[0] = 0.0f;
    pFrame->vUp[1] = 1.0f;
    pFrame->vUp[2] = 0.0f;
 
    pFrame->vForward[0] = 0.0f;
    pFrame->vForward[1] = 0.0f;
    pFrame->vForward[2] = -1.0f; // 为什么是-1?
}

void setupRc(){

    gltInitFrame(&frameCamera);

    glCullFace(GL_BACK); // 指定要剔除的面为背面
    glFrontFace(GL_CCW); // 指定顶点的逆时针顺序为正面
    glEnable(GL_CULL_FACE); // 启用面剔除
    glEnable(GL_DEPTH_TEST); // 启用深度测试

    glGenTextures(TEXTURE_COUNT, textures); // 生成纹理对象
    glBindTexture(GL_TEXTURE_CUBE_MAP, textures[CUBE_MAP]); // 绑定立方体贴图纹理对象

    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);

    for(int i = 0; i < CUBE_FACES_COUNT; i++){
        std::ifstream file(CUBE_FACES[i], std::ios::binary);
        if(!file){
            throw std::runtime_error("Failed to open texture file: " + std::string(CUBE_FACES[i]));
        }
        TargaHeader targaHeader;
        file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
        if(!file){
            throw std::runtime_error("Failed to read Targa header from file: " + std::string(CUBE_FACES[i]));
        }
        std::cout << "tga file: " << CUBE_FACES[i] << ", header: " << targaHeader << std::endl;
        const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
        std::vector<char> imageData(imageSize);
        file.read(imageData.data(), imageSize);
        if(!file){
            throw std::runtime_error("Failed to read image data from file: " + std::string(CUBE_FACES[i]));
        }
        glTexImage2D(CUBE[i], 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
    }
    glGenerateMipmap(GL_TEXTURE_CUBE_MAP);

    // 颜色纹理
    glBindTexture(GL_TEXTURE_2D, textures[COLOR_MAP]); // 绑定颜色纹理对象
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    std::ifstream file(COLOR_TEXTURE, std::ios::binary);
    if(!file){
        throw std::runtime_error("Failed to open texture file: " + std::string(COLOR_TEXTURE));
    }
    TargaHeader targaHeader;
    file.read(reinterpret_cast<char*>(&targaHeader), sizeof(TargaHeader));
    if(!file){
        throw std::runtime_error("Failed to read Targa header from file: " + std::string(COLOR_TEXTURE));
    }
    std::cout << "tga file: " << COLOR_TEXTURE << ", header: " << targaHeader << std::endl;
    const std::size_t imageSize = targaHeader.width * targaHeader.height * (targaHeader.bitsPerPixel / 8);
    std::vector<char> imageData(imageSize);
    file.read(imageData.data(), imageSize);
    if(!file){
        throw std::runtime_error("Failed to read image data from file: " + std::string(COLOR_TEXTURE));
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, targaHeader.width, targaHeader.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
    glGenerateMipmap(GL_TEXTURE_2D);
    

    // 颜色纹理
    glActiveTexture(GL_TEXTURE0);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, textures[COLOR_MAP]);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

    // 立方体纹理
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textures[CUBE_MAP]);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
    glEnable(GL_TEXTURE_CUBE_MAP);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}

void shutdownRc(){
    glDeleteTextures(TEXTURE_COUNT, textures);
}

void drawSkyBox(){
    constexpr GLfloat SIZE = 15.0F; // Skybox size
    glBegin(GL_QUADS);

    // neg x
    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F,  1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    // pos x
    glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
    glVertex3f(SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F,  1.0F);
    glVertex3f(SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, 1.0F,  1.0F,  1.0F);
    glVertex3f(SIZE,  SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, 1.0F,  1.0F, -1.0F);
    glVertex3f(SIZE,  SIZE, -SIZE);

    // neg z
    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, -1.0F, -1.0F);
    glVertex3f( SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F,  1.0F, -1.0F);
    glVertex3f( SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    // pos z

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, -1.0F, 1.0F);
    glVertex3f( SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F, 1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F,  1.0F, 1.0F);
    glVertex3f( SIZE,  SIZE,  SIZE);

    // pos y

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F,  1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, 1.0F, -1.0F);
    glVertex3f( SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, 1.0F,  1.0F);
    glVertex3f( SIZE,  SIZE,  SIZE);

    // neg y
    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F,  1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, -1.0F,  1.0F);
    glVertex3f( SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,   1.0F, -1.0F, -1.0F);
    glVertex3f( SIZE, -SIZE, -SIZE);

    glEnd();
}

void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
    const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
    const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
    for (GLint i = 0; i < stacks; ++i) {
        const GLfloat stackAngle1 = i * stackStep;
        const GLfloat stackAngle2 = (i + 1) * stackStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (GLint j = 0; j <= slices; ++j) {
            const GLfloat sliceAngle1 = j * sliceStep;
            const GLfloat sliceAngle2 = (j + 1) * sliceStep;
 
            const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
            const GLfloat ny1 = std::cos(stackAngle1);
            const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
            const GLfloat x1 = radius * nx1;
            const GLfloat y1 = radius * ny1;
            const GLfloat z1 = radius * nz1;
 
            const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
            const GLfloat ny2 = std::cos(stackAngle2);
            const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
            const GLfloat x2 = radius * nx2;
            const GLfloat y2 = radius * ny2;
            const GLfloat z2 = radius * nz2;
 
            // 每个 j 先发下圈顶点、再发上圈顶点,三角形绕序从球外看才是逆时针(GL_CCW)。
            // 若「先上后下」,整颗球的外表面会被当作背面剔除,实际渲染的是远侧内表面,
            // 反射向量在竖直方向上是反的,天空就会被贴到球的下半部分。
            glNormal3f(nx2, ny2, nz2);
            glTexCoord2f(static_cast<GLfloat>(j) / slices,
                         1.0F - static_cast<GLfloat>(i + 1) / stacks);
            glVertex3f(x2, y2, z2);

            glNormal3f(nx1, ny1, nz1);
            glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
                         1.0F - static_cast<GLfloat>(i) / stacks);
            glVertex3f(x1, y1, z1);
        }
        glEnd();
    }
}

void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
    
    const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
    const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
    for (int i = 0; i < numMajor; ++i) {
        const GLfloat u0 = i * majorStep;   
        const GLfloat u1 = u0 + majorStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (int j = 0; j <= numMinor; ++j) {
            const GLfloat v0 = j * minorStep;
 
            // 计算两个顶点的坐标
            const GLfloat n0[3] = {
                std::cos(v0) * std::cos(u0),
                std::cos(v0) * std::sin(u0),
                std::sin(v0)
            };
            const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
            const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
            const GLfloat z00 = minorRadius * std::sin(v0);
 
            const GLfloat n1[3] = {
                std::cos(v0) * std::cos(u1),
                std::cos(v0) * std::sin(u1),
                std::sin(v0)
            };
            const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
            const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
            const GLfloat z01 = minorRadius * std::sin(v0);
 
            glNormal3f(n0[0], n0[1], n0[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor); 
            glVertex3f(x00, y00, z00);
            glNormal3f(n1[0], n1[1], n1[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);  
            glVertex3f(x01, y01, z01);
        }
        glEnd();
    }
}

/**
 * 视图矩阵 = 上面的逆:旋转转置 + 平移取负
 * 把世界挪到相机眼前
 */
void gltApplyCameraTransform(const GLFrame *pCamera) {
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
 
    // 构建视图矩阵 (View matrix) 列主序 (column-major order)
    GLfloat m[16];   // 同样的轴,改填「行」,即转置
    m[0]=r[0];              m[4]=r[1];              m[8] =r[2];              m[12]=0.0F;
    m[1]=pCamera->vUp[0];   m[5]=pCamera->vUp[1];   m[9] =pCamera->vUp[2];   m[13]=0.0F;
    m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;              m[7]=0.0F;              m[11]=0.0F;              m[15]=1.0F;
 
    glMultMatrixf(m);
    glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}

/**
 * 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
 * 四列依次是: right、up、-forward、location
 * 物体→世界
 */
void gltApplyActorTransform(const GLFrame *frame){
    // 算出物体的右向量 (Right vector)
    GLfloat r[3];
    r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
    r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
    r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];

    // 构建物体矩阵 (Model matrix) 列主序 (column-major order)
    // 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
    // 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
    GLfloat m[16];
    m[0]=r[0];                  m[4]=frame->vUp[0];             m[8] =-frame->vForward[0];  m[12]=frame->vLocation[0];
    m[1]=r[1];                  m[5]=frame->vUp[1];             m[9] =-frame->vForward[1];  m[13]=frame->vLocation[1];
    m[2]=r[2];                  m[6]=frame->vUp[2];             m[10]=-frame->vForward[2];  m[14]=frame->vLocation[2];
    m[3]=0.0F;                  m[7]=0.0F;                      m[11]=0.0F;                 m[15]=1.0F;

    glMultMatrixf(m);
}

/**
 * 反射贴图用的纹理矩阵:相机视图旋转的逆(旋转矩阵的逆就是转置)。
 *
 * GL_REFLECTION_MAP 生成的 (s,t,r) 已经是 eye space 的方向,
 * 而 cubemap 的面(以及天空盒)是按世界方向对齐的,所以需要转回 world space:
 *     R_world = V⁻¹ · R_eye = Vᵀ · R_eye
 * 环境贴图视为无限远,只取旋转部分,不平移。
 *
 * 注意不要直接拿视图矩阵 V(world → eye)来乘:那样相机每旋转 θ,
 * 反射方向会被多转一次,和天空盒对不上。
 */
void gltGetCameraInverseMatrix(const GLFrame *pCamera, GLfloat *m){
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];

    // V 的三行分别是 right、up、-forward;转置后它们变成三列。
    // 列主序数组里直接按「列 = V 原来的行」填入即可。
    m[0]=r[0];                  m[4]=pCamera->vUp[0];   m[8] =-pCamera->vForward[0]; m[12]=0.0F;
    m[1]=r[1];                  m[5]=pCamera->vUp[1];   m[9] =-pCamera->vForward[1]; m[13]=0.0F;
    m[2]=r[2];                  m[6]=pCamera->vUp[2];   m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;                  m[7]=0.0F;              m[11]=0.0F;                  m[15]=1.0F;
}

void renderScene(){
    // Rendering code goes here
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    gltApplyCameraTransform(&frameCamera); // Apply the camera transformation to position the world relative to the camera
    
    glActiveTexture(GL_TEXTURE0); // Ensure the color texture is active before drawing the skybox
    glDisable(GL_TEXTURE_2D);
    glActiveTexture(GL_TEXTURE1); // Switch to the cubemap texture unit

    glEnable(GL_TEXTURE_CUBE_MAP);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
    drawSkyBox();

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_R);
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    glActiveTexture(GL_TEXTURE0); // Make sure the cubemap texture unit is active for subsequent operations
    glEnable(GL_TEXTURE_2D); // Re-enable the color texture unit for subsequent operations

    glPushMatrix();
        glTranslatef(0.0f, 0.0f, -3.0f); // Example translation, adjust as needed
        
        glActiveTexture(GL_TEXTURE1);
        glMatrixMode(GL_TEXTURE); // Switch to texture matrix mode to apply texture transformations if needed

        glPushMatrix(); // Save the current texture matrix
            // 反射坐标在 eye space,用相机旋转的逆转回 world space,才能和天空盒对齐
            GLfloat cameraInverse[16];
            gltGetCameraInverseMatrix(&frameCamera, cameraInverse);
            glMultMatrixf(cameraInverse);

            glColor3f(1.0f, 1.0f, 1.0f);
            drawSphere(0.75F, 41, 41); // Assuming you have a function to draw a sphere

            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);

        glPopMatrix(); // Restore the texture matrix


    glPopMatrix();


    glPopMatrix();

    glutSwapBuffers();
}

void changeSize(const int w, int h){
    if(h == 0) h = 1;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
} 

void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
    frame->vLocation[0] += frame->vForward[0] * distance;
    frame->vLocation[1] += frame->vForward[1] * distance;
    frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
 * 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
 * @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
 *
 * 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
 *     v' = v·cosθ + (axis × v)·sinθ
 * 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
 * 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
 */
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
    // axis × forward —— 注意这是 -right(right = forward × up),
    // 这个负号决定了旋转方向,别误当成右向量
    const GLfloat vAxisCross[3] = {
        frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
        frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
        frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
    };

    // 角度转弧度
    const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
    const GLfloat fCos = std::cos(rad);
    const GLfloat fSin = std::sin(rad);

    // vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
    frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
    frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
    frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];

    // 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
    // forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
    const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
                                  frame->vForward[1] * frame->vForward[1] +
                                  frame->vForward[2] * frame->vForward[2]);
    frame->vForward[0] /= len;
    frame->vForward[1] /= len;
    frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
    switch(key){
        case GLUT_KEY_UP:
            gltMoveFrameForward(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_DOWN:
            gltMoveFrameForward(&frameCamera, -0.1f);
            break;
        case GLUT_KEY_LEFT:
            gltRotateFrameY(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_RIGHT:
            gltRotateFrameY(&frameCamera, -0.1f);
            break;
    }
    glutPostRedisplay();
}

void timerFunc(const int _){
    glutPostRedisplay();
    glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}

int main(int args, char** argv){
    std::cout << "Starting program..." << std::endl;
    glutInit(&args, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    if(mainWindowId == 0){
        throw std::runtime_error("Failed to create main window");
    }
    
    // ======= 事件处理 =======
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    glutSpecialFunc(specialKeys);
    glutTimerFunc(33, timerFunc, 1); // 启动定时器,大约 30 FPS
    // ======= 渲染循环 =======
    GLint iUnits;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);
    std::cout << "Max texture units: " << iUnits << std::endl;

    setupRc();
    glutMainLoop();
    shutdownRc();
    
    return 0;
}

点块纹理

#include <OpenGL/glext.h>
#include <cmath>
#include <random>
#if defined(__APPLE__) && defined(__MACH__)
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif

#include <iostream>
#include <stdexcept>
#include <array>
#include <string>
#include <fstream>
#include <vector>
#include <numbers>

static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint WINDOW_WIDTH = 800;
static constexpr GLint WINDOW_HEIGHT = 600;

static constexpr GLint CUBE_FACES_COUNT = 6;
static constexpr std::array<std::string, CUBE_FACES_COUNT> CUBE_FACES = {
    "../image/pos_x.tga",
    "../image/neg_x.tga",
    "../image/pos_y.tga",
    "../image/neg_y.tga",
    "../image/pos_z.tga",
    "../image/neg_z.tga"
};
static constexpr std::string COLOR_TEXTURE = "../image/tarnish.tga";
static constexpr GLint CUBE[CUBE_FACES_COUNT] = { 
    GL_TEXTURE_CUBE_MAP_POSITIVE_X, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 
    GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 
    GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 
};
static constexpr GLint TEXTURE_COUNT = 2;
struct GLFrame {
    GLfloat vLocation[3]; // 位置  -> move x, y, z
    GLfloat vUp[3];       // 上方向 -> y
    GLfloat vForward[3];  // 前方向 -> z x-> 通过 yz叉积计算 -> 归一化为单位向量
};
static constexpr GLint SMALL_STAR = 100;
static constexpr GLint MEDIUM_STAR = 40;
static constexpr GLint LARGE_STAR = 15;

static GLfloat smallStars[SMALL_STAR][2];
static GLfloat mediumStars[MEDIUM_STAR][2];
static GLfloat largeStars[LARGE_STAR][2];


static GLuint textures[TEXTURE_COUNT];
static GLint drawMode = 1;
static int mainWindowId = 0;


// Targa 图像文件头结构体
#pragma pack(push, 1)          // 关闭结构体对齐填充
struct TargaHeader {
    // 图像 ID 区长度,0 = 无
    GLubyte idLength;
    GLubyte colorMapType;  // 0 = 无调色板
    GLubyte dataTypeCode;     // 图像数据类型代码 2 = 未压缩真彩图(非 RLE)
    GLushort colorMapOrigin;  // 调色板起始索引
    GLushort colorMapLength;  // 调色板颜色的数量
    GLubyte colorMapDepth;  // 调色板每条颜色的位数
    GLushort xOrigin;   // 图像在屏幕上的 X 坐标
    GLushort yOrigin;   // 图像在屏幕上的 Y 坐标
    GLushort width;   // 图像宽度
    GLushort height;  // 图像高度
    // 每像素 24 位 = RGB 各 8 位
    GLubyte bitsPerPixel;
    // 0 表示自下而上存储
    GLubyte imageDescriptor;
};
#pragma pack(pop)             // 恢复结构体对齐填充
 
std::ostream& operator<<(std::ostream& os, const TargaHeader& header) {
    os << "TargaHeader { "
       << "idLength=" << static_cast<int>(header.idLength) << ", "
       << "colorMapType=" << static_cast<int>(header.colorMapType) << ", "
       << "dataTypeCode=" << static_cast<int>(header.dataTypeCode) << ", "
       << "colorMapOrigin=" << header.colorMapOrigin << ", "
       << "colorMapLength=" << header.colorMapLength << ", "
       << "colorMapDepth=" << static_cast<int>(header.colorMapDepth) << ", "
       << "xOrigin=" << header.xOrigin << ", "
       << "yOrigin=" << header.yOrigin << ", "
       << "width=" << header.width << ", "
       << "height=" << header.height << ", "
       << "bitsPerPixel=" << static_cast<int>(header.bitsPerPixel) << ", "
       << "imageDescriptor=" << static_cast<int>(header.imageDescriptor)
       << " }";
    return os;
}


void gltInitFrame(GLFrame *pFrame) {
    pFrame->vLocation[0] = 0.0f;
    pFrame->vLocation[1] = 0.0f;
    pFrame->vLocation[2] = 0.0f;
 
    pFrame->vUp[0] = 0.0f;
    pFrame->vUp[1] = 1.0f;
    pFrame->vUp[2] = 0.0f;
 
    pFrame->vForward[0] = 0.0f;
    pFrame->vForward[1] = 0.0f;
    pFrame->vForward[2] = -1.0f; // 为什么是-1?
}

void setupRc(){
    // small
    std::random_device rd; // 硬件熵源,用于播种
    std::mt19937_64 gen(rd());  // Mersenne Twister 引擎
    std::uniform_int_distribution<std::int32_t> dis(
        std::numeric_limits<std::int32_t>::min(),
        std::numeric_limits<std::int32_t>::max()
    );
    for(int i = 0; i < SMALL_STAR; ++i){
        smallStars[i][0] = static_cast<GLfloat>(dis(gen) % WINDOW_WIDTH); // [-20, 20]
        smallStars[i][1] = static_cast<GLfloat>(dis(gen) % (WINDOW_HEIGHT - 100) + 100); // [-20, 20]
    }
        for(int i = 0; i < MEDIUM_STAR; ++i){
        mediumStars[i][0] = static_cast<GLfloat>(dis(gen) % WINDOW_WIDTH * 10) / 10.0F; // [-20, 20]
        mediumStars[i][1] = static_cast<GLfloat>(dis(gen) % (WINDOW_HEIGHT - 100) + 100); // [-20, 20]
    }
        for(int i = 0; i < LARGE_STAR; ++i){
        largeStars[i][0] = static_cast<GLfloat>(dis(gen) % WINDOW_WIDTH * 10) / 10.0F; // [-20, 20]
        largeStars[i][1] = static_cast<GLfloat>(dis(gen) % (WINDOW_HEIGHT - 100) * 10) / 10.0F + 100.0F; // [-20, 20]
    }
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
    glColor3f(0.0f, 0.0f, 0.0f);
    
    glGenTextures(TEXTURE_COUNT, textures);
    glBindTexture(GL_TEXTURE_2D, textures[0]);

    TargaHeader header;
    std::ifstream file("../image/star.tga", std::ios::binary);
    if(file){
        file.read(reinterpret_cast<char*>(&header), sizeof(TargaHeader));
    }
    std::vector<char> imageData(header.width * header.height * (header.bitsPerPixel / 8));
    if(file){
        file.read(imageData.data(), imageData.size());
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, header.width, header.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData.data());
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glBindTexture(GL_TEXTURE_2D, textures[1]);
    TargaHeader header2;
    std::ifstream file2("../image/moon.tga", std::ios::binary);
    if(file2){
        file2.read(reinterpret_cast<char*>(&header2), sizeof(TargaHeader));
    }
    std::vector<char> imageData2(header2.width * header2.height * (header2.bitsPerPixel / 8));
    if(file2){
        file2.read(imageData2.data(), imageData2.size());
    }
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, header2.width, header2.height, 0, GL_BGR, GL_UNSIGNED_BYTE, imageData2.data());
    glGenerateMipmap(GL_TEXTURE_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
    glTexEnvi(GL_POINT_SPRITE, GL_TEXTURE_ENV_MODE, GL_DECAL);
}

void shutdownRc(){
    glDeleteTextures(TEXTURE_COUNT, textures);
}

void drawSkyBox(){
    constexpr GLfloat SIZE = 15.0F; // Skybox size
    glBegin(GL_QUADS);

    // neg x
    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F,  1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    // pos x
    glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F, -1.0F);
    glVertex3f(SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, 1.0F, -1.0F,  1.0F);
    glVertex3f(SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, 1.0F,  1.0F,  1.0F);
    glVertex3f(SIZE,  SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, 1.0F,  1.0F, -1.0F);
    glVertex3f(SIZE,  SIZE, -SIZE);

    // neg z
    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, -1.0F, -1.0F);
    glVertex3f( SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F,  1.0F, -1.0F);
    glVertex3f( SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    // pos z

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, -1.0F, 1.0F);
    glVertex3f( SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, 1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F,  1.0F, 1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F,  1.0F, 1.0F);
    glVertex3f( SIZE,  SIZE,  SIZE);

    // pos y

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F,  1.0F);
    glVertex3f(-SIZE,  SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, 1.0F, -1.0F);
    glVertex3f(-SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, 1.0F, -1.0F);
    glVertex3f( SIZE,  SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, 1.0F,  1.0F);
    glVertex3f( SIZE,  SIZE,  SIZE);

    // neg y
    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F, -1.0F);
    glVertex3f(-SIZE, -SIZE, -SIZE);

    glMultiTexCoord3f(GL_TEXTURE1, -1.0F, -1.0F,  1.0F);
    glVertex3f(-SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,  1.0F, -1.0F,  1.0F);
    glVertex3f( SIZE, -SIZE,  SIZE);

    glMultiTexCoord3f(GL_TEXTURE1,   1.0F, -1.0F, -1.0F);
    glVertex3f( SIZE, -SIZE, -SIZE);

    glEnd();
}

void drawSphere(const GLfloat radius, const GLint slices, const GLint stacks) {
    const GLfloat sliceStep = 2.0F * PI / static_cast<GLfloat>(slices);
    const GLfloat stackStep = PI / static_cast<GLfloat>(stacks);
    for (GLint i = 0; i < stacks; ++i) {
        const GLfloat stackAngle1 = i * stackStep;
        const GLfloat stackAngle2 = (i + 1) * stackStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (GLint j = 0; j <= slices; ++j) {
            const GLfloat sliceAngle1 = j * sliceStep;
            const GLfloat sliceAngle2 = (j + 1) * sliceStep;
 
            const GLfloat nx1 = std::sin(stackAngle1) * std::cos(sliceAngle1);
            const GLfloat ny1 = std::cos(stackAngle1);
            const GLfloat nz1 = std::sin(stackAngle1) * std::sin(sliceAngle1);
            const GLfloat x1 = radius * nx1;
            const GLfloat y1 = radius * ny1;
            const GLfloat z1 = radius * nz1;
 
            const GLfloat nx2 = std::sin(stackAngle2) * std::cos(sliceAngle1);
            const GLfloat ny2 = std::cos(stackAngle2);
            const GLfloat nz2 = std::sin(stackAngle2) * std::sin(sliceAngle1);
            const GLfloat x2 = radius * nx2;
            const GLfloat y2 = radius * ny2;
            const GLfloat z2 = radius * nz2;
 
            // 每个 j 先发下圈顶点、再发上圈顶点,三角形绕序从球外看才是逆时针(GL_CCW)。
            // 若「先上后下」,整颗球的外表面会被当作背面剔除,实际渲染的是远侧内表面,
            // 反射向量在竖直方向上是反的,天空就会被贴到球的下半部分。
            glNormal3f(nx2, ny2, nz2);
            glTexCoord2f(static_cast<GLfloat>(j) / slices,
                         1.0F - static_cast<GLfloat>(i + 1) / stacks);
            glVertex3f(x2, y2, z2);

            glNormal3f(nx1, ny1, nz1);
            glTexCoord2f(static_cast<GLfloat>(j) / slices, // 左下角
                         1.0F - static_cast<GLfloat>(i) / stacks);
            glVertex3f(x1, y1, z1);
        }
        glEnd();
    }
}

void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const int numMajor, const int numMinor) {
    
    const GLfloat majorStep = 2.0F * PI / static_cast<GLfloat>(numMajor);
    const GLfloat minorStep = 2.0F * PI / static_cast<GLfloat>(numMinor);
    for (int i = 0; i < numMajor; ++i) {
        const GLfloat u0 = i * majorStep;   
        const GLfloat u1 = u0 + majorStep;
        glBegin(GL_TRIANGLE_STRIP);
        for (int j = 0; j <= numMinor; ++j) {
            const GLfloat v0 = j * minorStep;
 
            // 计算两个顶点的坐标
            const GLfloat n0[3] = {
                std::cos(v0) * std::cos(u0),
                std::cos(v0) * std::sin(u0),
                std::sin(v0)
            };
            const GLfloat x00 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u0);
            const GLfloat y00 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u0);
            const GLfloat z00 = minorRadius * std::sin(v0);
 
            const GLfloat n1[3] = {
                std::cos(v0) * std::cos(u1),
                std::cos(v0) * std::sin(u1),
                std::sin(v0)
            };
            const GLfloat x01 = (majorRadius + minorRadius * std::cos(v0)) * std::cos(u1);
            const GLfloat y01 = (majorRadius + minorRadius * std::cos(v0)) * std::sin(u1);
            const GLfloat z01 = minorRadius * std::sin(v0);
 
            glNormal3f(n0[0], n0[1], n0[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i) / numMajor); 
            glVertex3f(x00, y00, z00);
            glNormal3f(n1[0], n1[1], n1[2]);
            glTexCoord2f(static_cast<GLfloat>(j) / numMinor, static_cast<GLfloat>(i + 1) / numMajor);  
            glVertex3f(x01, y01, z01);
        }
        glEnd();
    }
}

/**
 * 视图矩阵 = 上面的逆:旋转转置 + 平移取负
 * 把世界挪到相机眼前
 */
void gltApplyCameraTransform(const GLFrame *pCamera) {
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];
 
    // 构建视图矩阵 (View matrix) 列主序 (column-major order)
    GLfloat m[16];   // 同样的轴,改填「行」,即转置
    m[0]=r[0];              m[4]=r[1];              m[8] =r[2];              m[12]=0.0F;
    m[1]=pCamera->vUp[0];   m[5]=pCamera->vUp[1];   m[9] =pCamera->vUp[2];   m[13]=0.0F;
    m[2]=-pCamera->vForward[0]; m[6]=-pCamera->vForward[1]; m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;              m[7]=0.0F;              m[11]=0.0F;              m[15]=1.0F;
 
    glMultMatrixf(m);
    glTranslatef(-pCamera->vLocation[0], -pCamera->vLocation[1], -pCamera->vLocation[2]);
}

/**
 * 物体矩阵 = 相机矩阵的逆:旋转的轴填「列」(不平移取负,平移直接进第 4 列)
 * 四列依次是: right、up、-forward、location
 * 物体→世界
 */
void gltApplyActorTransform(const GLFrame *frame){
    // 算出物体的右向量 (Right vector)
    GLfloat r[3];
    r[0] = frame->vForward[1] * frame->vUp[2] - frame->vForward[2] * frame->vUp[1];
    r[1] = frame->vForward[2] * frame->vUp[0] - frame->vForward[0] * frame->vUp[2];
    r[2] = frame->vForward[0] * frame->vUp[1] - frame->vForward[1] * frame->vUp[0];

    // 构建物体矩阵 (Model matrix) 列主序 (column-major order)
    // 第三列必须是 -forward: right × up = -forward,这样才是右手系(行列式 +1);
    // 若填 +forward 则行列式为负,几何体会被镜像,开着 GL_CULL_FACE 时整个模型会被剔除
    GLfloat m[16];
    m[0]=r[0];                  m[4]=frame->vUp[0];             m[8] =-frame->vForward[0];  m[12]=frame->vLocation[0];
    m[1]=r[1];                  m[5]=frame->vUp[1];             m[9] =-frame->vForward[1];  m[13]=frame->vLocation[1];
    m[2]=r[2];                  m[6]=frame->vUp[2];             m[10]=-frame->vForward[2];  m[14]=frame->vLocation[2];
    m[3]=0.0F;                  m[7]=0.0F;                      m[11]=0.0F;                 m[15]=1.0F;

    glMultMatrixf(m);
}

/**
 * 反射贴图用的纹理矩阵:相机视图旋转的逆(旋转矩阵的逆就是转置)。
 *
 * GL_REFLECTION_MAP 生成的 (s,t,r) 已经是 eye space 的方向,
 * 而 cubemap 的面(以及天空盒)是按世界方向对齐的,所以需要转回 world space:
 *     R_world = V⁻¹ · R_eye = Vᵀ · R_eye
 * 环境贴图视为无限远,只取旋转部分,不平移。
 *
 * 注意不要直接拿视图矩阵 V(world → eye)来乘:那样相机每旋转 θ,
 * 反射方向会被多转一次,和天空盒对不上。
 */
void gltGetCameraInverseMatrix(const GLFrame *pCamera, GLfloat *m){
    // 算出相机右向量 (Right vector)
    GLfloat r[3];
    r[0] = pCamera->vForward[1] * pCamera->vUp[2] - pCamera->vForward[2] * pCamera->vUp[1];
    r[1] = pCamera->vForward[2] * pCamera->vUp[0] - pCamera->vForward[0] * pCamera->vUp[2];
    r[2] = pCamera->vForward[0] * pCamera->vUp[1] - pCamera->vForward[1] * pCamera->vUp[0];

    // V 的三行分别是 right、up、-forward;转置后它们变成三列。
    // 列主序数组里直接按「列 = V 原来的行」填入即可。
    m[0]=r[0];                  m[4]=pCamera->vUp[0];   m[8] =-pCamera->vForward[0]; m[12]=0.0F;
    m[1]=r[1];                  m[5]=pCamera->vUp[1];   m[9] =-pCamera->vForward[1]; m[13]=0.0F;
    m[2]=r[2];                  m[6]=pCamera->vUp[2];   m[10]=-pCamera->vForward[2]; m[14]=0.0F;
    m[3]=0.0F;                  m[7]=0.0F;              m[11]=0.0F;                  m[15]=1.0F;
}

void renderScene(){

    // Rendering code goes here
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glColor3f(1.0F, 1.0F, 1.0F);

    if(drawMode == 3) {
        glEnable(GL_POINT_SPRITE);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, textures[0]);
        glEnable(GL_BLEND);
    }

    glPointSize(7.0f); // Example point size, adjust as needed
    glBegin(GL_POINTS);
        for (int i = 0; i < SMALL_STAR; ++i) {
            glVertex2fv(smallStars[i]);
        }
    glEnd();

    glPointSize(12.0F);
    glBegin(GL_POINTS);
        for (int i = 0; i < MEDIUM_STAR; ++i) {
            glVertex2fv(mediumStars[i]);
        }
    glEnd();

    glPointSize(20.0F);
    glBegin(GL_POINTS);
        for (int i = 0; i < LARGE_STAR; ++i) {
            glVertex2fv(largeStars[i]);
        }
    glEnd();

    glPointSize(120.0F);
    if(drawMode == 3) {
        glDisable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, textures[1]);
    }

    glBegin(GL_POINTS);
        glVertex2f(700.0F, 500.0F);
    glEnd();


    glDisable(GL_TEXTURE_2D);
    glDisable(GL_POINT_SPRITE);

    // Draw distant horizon
    glLineWidth(3.5F);
    glBegin(GL_LINE_STRIP);
        glVertex2f(0.0f, 25.0f);
        glVertex2f(50.0f, 100.0f);
        glVertex2f(100.0f, 25.0f);
        glVertex2f(225.0f, 115.0f);
        glVertex2f(300.0f, 50.0f);
        glVertex2f(375.0f, 100.0f);
        glVertex2f(460.0f, 25.0f);
        glVertex2f(525.0f, 100.0f);
        glVertex2f(600.0f, 20.0f);
        glVertex2f(675.0f, 70.0f);
        glVertex2f(750.0f, 25.0f);
        glVertex2f(800.0f, 90.0f);    
    glEnd();


    glutSwapBuffers();
}

void changeSize(const int w, int h){
    if(h == 0) h = 1;
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    // gluPerspective(45.0, static_cast<double>(w)/static_cast<double>(h), 1.0, 100.0);
    glOrtho(0.0, w, 0.0, h, -1.0, 1); // 使用正交投影替代透视投影
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
} 

void gltMoveFrameForward(GLFrame* frame, const GLfloat distance){
    frame->vLocation[0] += frame->vForward[0] * distance;
    frame->vLocation[1] += frame->vForward[1] * distance;
    frame->vLocation[2] += frame->vForward[2] * distance;
}
/**
 * 绕帧自身的 up 轴旋转(本地 Y 轴 / yaw),up 轴本身不变。
 * @param angle 单位是「弧度」,不是度。正值按右手法则绕 up 轴转(LEFT 键传 +0.1 ≈ 左转 5.7°)。
 *
 * 公式是 Rodrigues 旋转公式在「旋转轴 ⊥ 被旋转向量」时的化简形式:
 *     v' = v·cosθ + (axis × v)·sinθ
 * 完整形式还多一项 axis·(axis·v)·(1-cosθ),因为 up ⊥ forward 而恒为 0。
 * 前提: vUp 与 vForward 互相垂直且都是单位向量,否则这个简式不成立。
 */
void gltRotateFrameY(GLFrame* frame, const GLfloat angle){
    // axis × forward —— 注意这是 -right(right = forward × up),
    // 这个负号决定了旋转方向,别误当成右向量
    const GLfloat vAxisCross[3] = {
        frame->vUp[1] * frame->vForward[2] - frame->vUp[2] * frame->vForward[1],
        frame->vUp[2] * frame->vForward[0] - frame->vUp[0] * frame->vForward[2],
        frame->vUp[0] * frame->vForward[1] - frame->vUp[1] * frame->vForward[0]
    };

    // 角度转弧度
    const GLfloat rad = angle; // 如果 angle 是弧度,则直接使用;如果是度,需要转换为弧度:angle * M_PI / 180.0f
    const GLfloat fCos = std::cos(rad);
    const GLfloat fSin = std::sin(rad);

    // vAxisCross 已提前算好,每个分量只依赖自己的旧值,可以直接原地更新,省掉临时数组
    frame->vForward[0] = fCos * frame->vForward[0] + fSin * vAxisCross[0];
    frame->vForward[1] = fCos * frame->vForward[1] + fSin * vAxisCross[1];
    frame->vForward[2] = fCos * frame->vForward[2] + fSin * vAxisCross[2];

    // 反复旋转会累积浮点漂移(实测 100 万次后长度偏 +1.2%),归一化抹掉它。
    // forward 长度不为 1 会直接变成视图矩阵的缩放,整个世界会被放大/缩小
    const GLfloat len = std::sqrt(frame->vForward[0] * frame->vForward[0] +
                                  frame->vForward[1] * frame->vForward[1] +
                                  frame->vForward[2] * frame->vForward[2]);
    frame->vForward[0] /= len;
    frame->vForward[1] /= len;
    frame->vForward[2] /= len;
}
void specialKeys(int key, int x, int y){
    switch(key){
        case GLUT_KEY_UP:
            // gltMoveFrameForward(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_DOWN:
            // gltMoveFrameForward(&frameCamera, -0.1f);
            break;
        case GLUT_KEY_LEFT:
            // gltRotateFrameY(&frameCamera, 0.1f);
            break;
        case GLUT_KEY_RIGHT:
            // gltRotateFrameY(&frameCamera, -0.1f);
            break;
    }
    glutPostRedisplay();
}

void timerFunc(const int _){
    glutPostRedisplay();
    glutTimerFunc(33, timerFunc, 1); // 大约 30 FPS
}
void processMenu(int option){
    drawMode = option;
    switch(option){
        case 1:
            // Normal Point
            glDisable(GL_BLEND);
            glDisable(GL_LINE_SMOOTH);
            glDisable(GL_POINT_SMOOTH);
            glDisable(GL_TEXTURE_2D);
            glDisable(GL_POINT_SPRITE);
            break;
        case 2:
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
            glEnable(GL_POINT_SMOOTH);
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
            glEnable(GL_LINE_SMOOTH);
            glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
            glDisable(GL_TEXTURE_2D);
            glDisable(GL_POINT_SPRITE);
            break;
        case 3:
            // Point Sprites
            glEnable(GL_BLEND);
            glBlendFunc(GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR);
            glDisable(GL_LINE_SMOOTH);
            glDisable(GL_POINT_SMOOTH);
            glDisable(GL_POLYGON_SMOOTH);
            break;
    }
    glutSetWindow(mainWindowId);
    glutPostRedisplay();
}
int main(int args, char** argv){
    std::cout << "Starting program..." << std::endl;
    glutInit(&args, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(WINDOW_WIDTH, WINDOW_HEIGHT);
    mainWindowId = glutCreateWindow("OpenGL Window");
    if(mainWindowId == 0){
        throw std::runtime_error("Failed to create main window");
    }
    
    // ======= 事件处理 =======
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    glutSpecialFunc(specialKeys);
    glutCreateMenu(processMenu); // 如果有菜单状态处理函数
    glutAddMenuEntry("Normal Point", 1);
    glutAddMenuEntry("Antialiased Points", 2);
    glutAddMenuEntry("Point Sprites", 3);

    glutAttachMenu(GLUT_RIGHT_BUTTON); // 将菜单附加到鼠标右键
    // ======= 渲染循环 =======
    GLint iUnits;
    glGetIntegerv(GL_MAX_TEXTURE_UNITS, &iUnits);
    std::cout << "Max texture units: " << iUnits << std::endl;

    setupRc();
    glutMainLoop();
    shutdownRc();
    
    return 0;
}
posted @ 2026-10-17 21:28  爱情丶眨眼而去  阅读(6)  评论(0)    收藏  举报