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

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

总共22章等着我学习🥹
在OpenGL中使用颜色。

画一个黑色到白色渐变线


// mac 特有的头文件
#include <GLUT/glut.h>

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>

#include <cmath>
#include <iostream>
#include <numbers>


// 可视坐标大小
static constexpr GLfloat COORDINATE_SIZE = 120.0F;
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

// 4x4 矩阵乘法: result = a * b(列主序,兼容 OpenGL)
void multiplyMatrix4(const GLfloat a[16], const GLfloat b[16],
                     GLfloat result[16]) {
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            result[col * 4 + row] = a[0 * 4 + row] * b[col * 4 + 0] +
                                    a[1 * 4 + row] * b[col * 4 + 1] +
                                    a[2 * 4 + row] * b[col * 4 + 2] +
                                    a[3 * 4 + row] * b[col * 4 + 3];
        }
    }
}

void renderScene() {
    std::cout << std::format("renderScene: xRot={}, yRot={}, zRot={}", xRot, yRot, zRot) << "\n";
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    // --- 手动构建变换矩阵,替代 glTranslatef / glRotatef ---
    const GLfloat radX = xRot * PI / 180.0F;
    const GLfloat radY = yRot * PI / 180.0F;
    const GLfloat radZ = zRot * PI / 180.0F;

    const GLfloat cx = cosf(radX), sx = sinf(radX);
    const GLfloat cy = cosf(radY), sy = sinf(radY);
    const GLfloat cz = cosf(radZ), sz = sinf(radZ);

    // 平移矩阵 T(列主序)
    const GLfloat trans[16] = {
        1.0F, 0.0F, 0.0F,  0.0F, // col 0
        0.0F, 1.0F, 0.0F,  0.0F, // col 1
        0.0F, 0.0F, 1.0F,  0.0F, // col 2
        0.0F, 0.0F, -400.0F, 1.0F  // col 3 (tx, ty, tz, 1)
    };

    // 绕 X 轴旋转矩阵(列主序)
    const GLfloat rotX[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cx,   sx,   0.0F,
                              0.0F, -sx,  cx,   0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Y 轴旋转矩阵(列主序)
    const GLfloat rotY[16] = {cy, 0.0F, -sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
                              sy, 0.0F, cy,  0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Z 轴旋转矩阵(列主序)
    const GLfloat rotZ[16] = {cz,   sz,   0.0F, 0.0F, -sz,  cz,   0.0F, 0.0F,
                              0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 组合: T * Rx * Ry * Rz
    GLfloat temp1[16], temp2[16], finalMatrix[16];
    multiplyMatrix4(rotY, rotZ, temp1);         // temp1 = Ry * Rz
    multiplyMatrix4(rotX, temp1, temp2);        // temp2 = Rx * Ry * Rz
    multiplyMatrix4(trans, temp2, finalMatrix); // final = T * Rx * Ry * Rz

    glMultMatrixf(finalMatrix);

    // 绘制立方体
    glPointSize(50.0F);
    glBegin(GL_POINTS);
        glColor3ub(255, 255, 255);
        glVertex3f(0.0F, 0.0F, 0.0F);
    glEnd();
    glLineWidth(10.0F);
    glBegin(GL_LINES);
        glColor3ub(0, 0, 0);
        glVertex3f(0.0F, 0.0F, 0.0F);
        glColor3ub(255, 255, 255);
        glVertex3f(255.0F, 255.F, 255.0F);
    glEnd();


    glPopMatrix();

    glutSwapBuffers();
}

void setupRc() {
    // 设置清除颜色为黑色
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
    glEnable(GL_DEPTH_TEST); // 启用深度测试
    glShadeModel(GL_SMOOTH);
}

void changeSize(const GLsizei w, const GLsizei h) {
    std::cout << std::format("changeSize: w={}, h={}", w, h) << "\n";
    // 防止除以 0
    if (h == 0) {
        throw std::runtime_error("h == 0");
    }
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION); // 投影
    glLoadIdentity();
    const GLfloat aspectRatio =
        static_cast<GLfloat>(w) / static_cast<GLfloat>(h);
    gluPerspective(60.0F, aspectRatio, 1.0F, 500.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
        exit(0);
        break;
    case 'a':
    case 'A':
        zRot -= 5.0F; // 绕 Z 轴逆时针旋转
        glutPostRedisplay();
        break;
    case 'd':
    case 'D':
        zRot += 5.0F; // 绕 Z 轴顺时针旋转
        glutPostRedisplay();
        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:
        xRot -= 5.0F;
        break;
    case GLUT_KEY_DOWN:
        xRot += 5.0F;
        break;
    case GLUT_KEY_LEFT:
        yRot -= 5.0F;
        break;
    case GLUT_KEY_RIGHT:
        yRot += 5.0F;
        break;
    default:
        break;
    }
    // Refresh the Window
    glutPostRedisplay();
}

auto main(const int argc, const char **const argv) -> int {
    std::cout << "hello world\n";
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << "\n";
    }
    // 初始化 glut
    glutInit(const_cast<int *>(&argc), const_cast<char **>(argv));
    // 设置显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 设置窗口大小
    glutInitWindowSize(800, 600);
    // 创建窗口
    const int result = glutCreateWindow("Hello World");
    std::cout << "result: " << result << "\n";
    if (result == 0) {
        throw std::runtime_error("glutCreateWindow failed");
    }
    // 设置显示回调函数
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    // 注册 键盘事件 回调
    glutSpecialFunc(specialKeys);           // 特殊键事件
    glutKeyboardFunc(keyboard);             // 普通键事件
    setupRc();
    glutMainLoop();
    return 0;
}

画一个三角形


// mac 特有的头文件
#include <GLUT/glut.h>

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>

#include <cmath>
#include <iostream>
#include <numbers>


// 可视坐标大小
static constexpr GLfloat COORDINATE_SIZE = 120.0F;
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

// 4x4 矩阵乘法: result = a * b(列主序,兼容 OpenGL)
void multiplyMatrix4(const GLfloat a[16], const GLfloat b[16],
                     GLfloat result[16]) {
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            result[col * 4 + row] = a[0 * 4 + row] * b[col * 4 + 0] +
                                    a[1 * 4 + row] * b[col * 4 + 1] +
                                    a[2 * 4 + row] * b[col * 4 + 2] +
                                    a[3 * 4 + row] * b[col * 4 + 3];
        }
    }
}

void renderScene() {
    std::cout << std::format("renderScene: xRot={}, yRot={}, zRot={}", xRot, yRot, zRot) << "\n";
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    // --- 手动构建变换矩阵,替代 glTranslatef / glRotatef ---
    const GLfloat radX = xRot * PI / 180.0F;
    const GLfloat radY = yRot * PI / 180.0F;
    const GLfloat radZ = zRot * PI / 180.0F;

    const GLfloat cx = cosf(radX), sx = sinf(radX);
    const GLfloat cy = cosf(radY), sy = sinf(radY);
    const GLfloat cz = cosf(radZ), sz = sinf(radZ);

    // 平移矩阵 T(列主序)
    const GLfloat trans[16] = {
        1.0F, 0.0F, 0.0F,  0.0F, // col 0
        0.0F, 1.0F, 0.0F,  0.0F, // col 1
        0.0F, 0.0F, 1.0F,  0.0F, // col 2
        0.0F, 0.0F, -300.0F, 1.0F  // col 3 (tx, ty, tz, 1)
    };

    // 绕 X 轴旋转矩阵(列主序)
    const GLfloat rotX[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cx,   sx,   0.0F,
                              0.0F, -sx,  cx,   0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Y 轴旋转矩阵(列主序)
    const GLfloat rotY[16] = {cy, 0.0F, -sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
                              sy, 0.0F, cy,  0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Z 轴旋转矩阵(列主序)
    const GLfloat rotZ[16] = {cz,   sz,   0.0F, 0.0F, -sz,  cz,   0.0F, 0.0F,
                              0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 组合: T * Rx * Ry * Rz
    GLfloat temp1[16], temp2[16], finalMatrix[16];
    multiplyMatrix4(rotY, rotZ, temp1);         // temp1 = Ry * Rz
    multiplyMatrix4(rotX, temp1, temp2);        // temp2 = Rx * Ry * Rz
    multiplyMatrix4(trans, temp2, finalMatrix); // final = T * Rx * Ry * Rz

    glMultMatrixf(finalMatrix);

    // 绘制普通三角形
    glBegin(GL_TRIANGLES);
        glColor3ub(255, 0, 0);
        glVertex3f(0.0F, 100.F, 0.0F);
        glColor3ub(0, 255, 0);
        glVertex3f(-200.0F * tanf(30.0F * PI / 180.0F), -100.0F, 0.0F);
        glColor3ub(0, 0, 255);
        glVertex3f(200.0F * tanf(30.0F * PI / 180.0F), -100.0F, 0.0F);
    glEnd();

	// 绘制立方体 颜色立方体
    glBegin(GL_TRIANGLES);
        glColor3ub(255, 0, 0);
        glVertex3f(255.0F, 0.F, 0.0F);
        glColor3ub(0, 255, 0);
        glVertex3f(0.0F, 255.0F, 0.0F);
        glColor3ub(0, 0, 255);
        glVertex3f(0.0F, 0.0F, 255.0F);
    glEnd();

    glPopMatrix();

    glutSwapBuffers();
}

void setupRc() {
    // 设置清除颜色为黑色
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
    glEnable(GL_DEPTH_TEST); // 启用深度测试
    glShadeModel(GL_SMOOTH);
}

void changeSize(const GLsizei w, const GLsizei h) {
    std::cout << std::format("changeSize: w={}, h={}", w, h) << "\n";
    // 防止除以 0
    if (h == 0) {
        throw std::runtime_error("h == 0");
    }
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION); // 投影
    glLoadIdentity();
    const GLfloat aspectRatio =
        static_cast<GLfloat>(w) / static_cast<GLfloat>(h);
    gluPerspective(60.0F, aspectRatio, 1.0F, 500.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
        exit(0);
        break;
    case 'a':
    case 'A':
        zRot -= 5.0F; // 绕 Z 轴逆时针旋转
        glutPostRedisplay();
        break;
    case 'd':
    case 'D':
        zRot += 5.0F; // 绕 Z 轴顺时针旋转
        glutPostRedisplay();
        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:
        xRot -= 5.0F;
        break;
    case GLUT_KEY_DOWN:
        xRot += 5.0F;
        break;
    case GLUT_KEY_LEFT:
        yRot -= 5.0F;
        break;
    case GLUT_KEY_RIGHT:
        yRot += 5.0F;
        break;
    default:
        break;
    }
    // Refresh the Window
    glutPostRedisplay();
}

auto main(const int argc, const char **const argv) -> int {
    std::cout << "hello world\n";
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << "\n";
    }
    // 初始化 glut
    glutInit(const_cast<int *>(&argc), const_cast<char **>(argv));
    // 设置显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 设置窗口大小
    glutInitWindowSize(800, 600);
    // 创建窗口
    const int result = glutCreateWindow("Hello World");
    std::cout << "result: " << result << "\n";
    if (result == 0) {
        throw std::runtime_error("glutCreateWindow failed");
    }
    // 设置显示回调函数
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    // 注册 键盘事件 回调
    glutSpecialFunc(specialKeys);           // 特殊键事件
    glutKeyboardFunc(keyboard);             // 普通键事件
    setupRc();
    glutMainLoop();
    return 0;
}

画一个颜色立方体


// mac 特有的头文件
#include <GLUT/glut.h>

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>

#include <cmath>
#include <iostream>
#include <numbers>


// 可视坐标大小
static constexpr GLfloat COORDINATE_SIZE = 120.0F;
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

// 4x4 矩阵乘法: result = a * b(列主序,兼容 OpenGL)
void multiplyMatrix4(const GLfloat a[16], const GLfloat b[16],
                     GLfloat result[16]) {
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            result[col * 4 + row] = a[0 * 4 + row] * b[col * 4 + 0] +
                                    a[1 * 4 + row] * b[col * 4 + 1] +
                                    a[2 * 4 + row] * b[col * 4 + 2] +
                                    a[3 * 4 + row] * b[col * 4 + 3];
        }
    }
}

void renderScene() {
    std::cout << std::format("renderScene: xRot={}, yRot={}, zRot={}", xRot, yRot, zRot) << "\n";
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    // --- 手动构建变换矩阵,替代 glTranslatef / glRotatef ---
    const GLfloat radX = xRot * PI / 180.0F;
    const GLfloat radY = yRot * PI / 180.0F;
    const GLfloat radZ = zRot * PI / 180.0F;

    const GLfloat cx = cosf(radX), sx = sinf(radX);
    const GLfloat cy = cosf(radY), sy = sinf(radY);
    const GLfloat cz = cosf(radZ), sz = sinf(radZ);

    // 平移矩阵 T(列主序)
    const GLfloat trans[16] = {
        1.0F, 0.0F, 0.0F,  0.0F, // col 0
        0.0F, 1.0F, 0.0F,  0.0F, // col 1
        0.0F, 0.0F, 1.0F,  0.0F, // col 2
        0.0F, 0.0F, -400.0F, 1.0F  // col 3 (tx, ty, tz, 1)
    };

    // 绕 X 轴旋转矩阵(列主序)
    const GLfloat rotX[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cx,   sx,   0.0F,
                              0.0F, -sx,  cx,   0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Y 轴旋转矩阵(列主序)
    const GLfloat rotY[16] = {cy, 0.0F, -sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
                              sy, 0.0F, cy,  0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Z 轴旋转矩阵(列主序)
    const GLfloat rotZ[16] = {cz,   sz,   0.0F, 0.0F, -sz,  cz,   0.0F, 0.0F,
                              0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 组合: T * Rx * Ry * Rz
    GLfloat temp1[16], temp2[16], finalMatrix[16];
    multiplyMatrix4(rotY, rotZ, temp1);         // temp1 = Ry * Rz
    multiplyMatrix4(rotX, temp1, temp2);        // temp2 = Rx * Ry * Rz
    multiplyMatrix4(trans, temp2, finalMatrix); // final = T * Rx * Ry * Rz

    glMultMatrixf(finalMatrix);

    // 绘制立方体
    glBegin(GL_QUADS);
        // 前
        glColor3ub(255, 255, 255);
        glVertex3f(100.0F, 100.0F, 100.0F);

        glColor3ub(0, 255, 255);
        glVertex3f(-100.0F, 100.0F, 100.0F);

        glColor3ub(0, 0, 255);
        glVertex3f(-100.0F, -100.0F, 100.0F);

        glColor3ub(255, 0, 255);
        glVertex3f(100.0F, -100.0F, 100.0F);

        // 后
        glColor3ub(255, 255, 0);
        glVertex3f(100.0F, 100.0F, -100.0F);
        glColor3ub(0, 255, 0);
        glVertex3f(-100.0F, 100.0F, -100.0F);
        glColor3ub(0, 0, 0);
        glVertex3f(-100.0F, -100.0F, -100.0F);
        glColor3ub(255, 0, 0);
        glVertex3f(100.0F, -100.0F, -100.0F);
        // 顶面 (y = +100)
        glColor3ub(255, 255, 255);
        glVertex3f(100.0F, 100.0F, 100.0F);
        glColor3ub(255, 255, 0);
        glVertex3f(100.0F, 100.0F, -100.0F);
        glColor3ub(0, 255, 0);
        glVertex3f(-100.0F, 100.0F, -100.0F);
        glColor3ub(0, 255, 255);
        glVertex3f(-100.0F, 100.0F, 100.0F);

        // 底面 (y = -100)
        glColor3ub(255, 0, 255);
        glVertex3f(100.0F, -100.0F, 100.0F);
        glColor3ub(0, 0, 255);
        glVertex3f(-100.0F, -100.0F, 100.0F);
        glColor3ub(0, 0, 0);
        glVertex3f(-100.0F, -100.0F, -100.0F);
        glColor3ub(255, 0, 0);
        glVertex3f(100.0F, -100.0F, -100.0F);

        // 右面 (x = +100)
        glColor3ub(255, 255, 255);
        glVertex3f(100.0F, 100.0F, 100.0F);
        glColor3ub(255, 0, 255);
        glVertex3f(100.0F, -100.0F, 100.0F);
        glColor3ub(255, 0, 0);
        glVertex3f(100.0F, -100.0F, -100.0F);
        glColor3ub(255, 255, 0);
        glVertex3f(100.0F, 100.0F, -100.0F);

        // 左面 (x = -100)
        glColor3ub(0, 255, 255);
        glVertex3f(-100.0F, 100.0F, 100.0F);
        glColor3ub(0, 255, 0);
        glVertex3f(-100.0F, 100.0F, -100.0F);
        glColor3ub(0, 0, 0);
        glVertex3f(-100.0F, -100.0F, -100.0F);
        glColor3ub(0, 0, 255);
        glVertex3f(-100.0F, -100.0F, 100.0F);
    glEnd();


    glPopMatrix();

    glutSwapBuffers();
}

void setupRc() {
    // 设置清除颜色为黑色
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
    glEnable(GL_DEPTH_TEST); // 启用深度测试
    glShadeModel(GL_SMOOTH);
}

void changeSize(const GLsizei w, const GLsizei h) {
    std::cout << std::format("changeSize: w={}, h={}", w, h) << "\n";
    // 防止除以 0
    if (h == 0) {
        throw std::runtime_error("h == 0");
    }
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION); // 投影
    glLoadIdentity();
    const GLfloat aspectRatio =
        static_cast<GLfloat>(w) / static_cast<GLfloat>(h);
    gluPerspective(60.0F, aspectRatio, 1.0F, 500.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
        exit(0);
        break;
    case 'a':
    case 'A':
        zRot -= 5.0F; // 绕 Z 轴逆时针旋转
        glutPostRedisplay();
        break;
    case 'd':
    case 'D':
        zRot += 5.0F; // 绕 Z 轴顺时针旋转
        glutPostRedisplay();
        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:
        xRot -= 5.0F;
        break;
    case GLUT_KEY_DOWN:
        xRot += 5.0F;
        break;
    case GLUT_KEY_LEFT:
        yRot -= 5.0F;
        break;
    case GLUT_KEY_RIGHT:
        yRot += 5.0F;
        break;
    default:
        break;
    }
    // Refresh the Window
    glutPostRedisplay();
}

auto main(const int argc, const char **const argv) -> int {
    std::cout << "hello world\n";
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << "\n";
    }
    // 初始化 glut
    glutInit(const_cast<int *>(&argc), const_cast<char **>(argv));
    // 设置显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 设置窗口大小
    glutInitWindowSize(800, 600);
    // 创建窗口
    const int result = glutCreateWindow("Hello World");
    std::cout << "result: " << result << "\n";
    if (result == 0) {
        throw std::runtime_error("glutCreateWindow failed");
    }
    // 设置显示回调函数
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    // 注册 键盘事件 回调
    glutSpecialFunc(specialKeys);           // 特殊键事件
    glutKeyboardFunc(keyboard);             // 普通键事件
    setupRc();
    glutMainLoop();
    return 0;
}

画一个飞机


// mac 特有的头文件
#include <GLUT/glut.h>

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>

#include <cmath>
#include <iostream>
#include <numbers>


// 可视坐标大小
static constexpr GLfloat COORDINATE_SIZE = 120.0F;
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

// 4x4 矩阵乘法: result = a * b(列主序,兼容 OpenGL)
void multiplyMatrix4(const GLfloat a[16], const GLfloat b[16],
                     GLfloat result[16]) {
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            result[col * 4 + row] = a[0 * 4 + row] * b[col * 4 + 0] +
                                    a[1 * 4 + row] * b[col * 4 + 1] +
                                    a[2 * 4 + row] * b[col * 4 + 2] +
                                    a[3 * 4 + row] * b[col * 4 + 3];
        }
    }
}

void renderScene() {
    std::cout << std::format("renderScene: xRot={}, yRot={}, zRot={}", xRot, yRot, zRot) << "\n";
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    // --- 手动构建变换矩阵,替代 glTranslatef / glRotatef ---
    const GLfloat radX = xRot * PI / 180.0F;
    const GLfloat radY = yRot * PI / 180.0F;
    const GLfloat radZ = zRot * PI / 180.0F;

    const GLfloat cx = cosf(radX), sx = sinf(radX);
    const GLfloat cy = cosf(radY), sy = sinf(radY);
    const GLfloat cz = cosf(radZ), sz = sinf(radZ);

    // 平移矩阵 T(列主序)
    const GLfloat trans[16] = {
        1.0F, 0.0F, 0.0F,  0.0F, // col 0
        0.0F, 1.0F, 0.0F,  0.0F, // col 1
        0.0F, 0.0F, 1.0F,  0.0F, // col 2
        0.0F, 0.0F, -100.0F, 1.0F  // col 3 (tx, ty, tz, 1)
    };

    // 绕 X 轴旋转矩阵(列主序)
    const GLfloat rotX[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cx,   sx,   0.0F,
                              0.0F, -sx,  cx,   0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Y 轴旋转矩阵(列主序)
    const GLfloat rotY[16] = {cy, 0.0F, -sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
                              sy, 0.0F, cy,  0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Z 轴旋转矩阵(列主序)
    const GLfloat rotZ[16] = {cz,   sz,   0.0F, 0.0F, -sz,  cz,   0.0F, 0.0F,
                              0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 组合: T * Rx * Ry * Rz
    GLfloat temp1[16], temp2[16], finalMatrix[16];
    multiplyMatrix4(rotY, rotZ, temp1);         // temp1 = Ry * Rz
    multiplyMatrix4(rotX, temp1, temp2);        // temp2 = Rx * Ry * Rz
    multiplyMatrix4(trans, temp2, finalMatrix); // final = T * Rx * Ry * Rz

    glMultMatrixf(finalMatrix);

    // 绘制 喷气式飞机
    glBegin(GL_TRIANGLES);
        // 飞机头
        glColor3ub(255, 255, 255);
        glVertex3f(0.0F, 0.0F, 60.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F); 
        glVertex3f(15.0F, 0.0F, 30.0F);

        glColor3ub(0, 0, 0);
        glVertex3f(15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 15.0F, 30.0F); 
        glVertex3f(0.0F, 0.0F, 60.0F);

        glColor3ub(255, 0, 0);
        glVertex3f(0.0F, 0.0F, 60.0F);
        glVertex3f(0.0F, 15.0F, 30.0F); 
        glVertex3f(-15.0F, 0.0F, 30.0F);

        // 飞机体
        glColor3ub(0, 255, 0);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);

        glColor3ub(255, 255, 0);
        glVertex3f(0.0F, 0.0F, -56.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);

        glColor3ub(0, 255, 255);
        glVertex3f(15.0F, 0.0F, 30.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);

        // 机翼
        glColor3ub(128, 128, 128);
        glVertex3f(0.0F, 2.0F, 27.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);

        glColor3ub(64, 64, 64);
        glVertex3f(60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);

        glColor3ub(192, 192, 192);
        glVertex3f(60.0F, 2.0F, -8.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);

        glColor3ub(64, 64, 64);
        glVertex3f(0.0F, 2.0F, 27.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);

        // tail
        glColor3ub(255, 128, 255);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);

        glColor3ub(255, 128, 0);
        glVertex3f(0.0F, -0.5F, -40.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);

        glColor3ub(255, 128, 0);
        glVertex3f(0.0F, 4.0F, -57.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);

        glColor3ub(255, 255, 255);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);

        glColor3ub(255, 0, 0);
        glVertex3f(0.0F, 0.5F, -40.0F);
        glVertex3f(3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);

        glColor3ub(255, 0, 0);
        glVertex3f(0.0F, 25.0F, -65.0F);
        glVertex3f(-3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 0.5F, -40.0F);

        glColor3ub(128, 128, 128);
        glVertex3f(3.0F, 0.5F, -57.0F);
        glVertex3f(-3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);
    glEnd();


    glPopMatrix();

    glutSwapBuffers();
}

void setupRc() {
    // 设置清除颜色为黑色
    glClearColor(0.23F, 0.23F, 0.34F, 1.0F);
    glEnable(GL_DEPTH_TEST); // 启用深度测试
    glShadeModel(GL_SMOOTH);
}

void changeSize(const GLsizei w, const GLsizei h) {
    std::cout << std::format("changeSize: w={}, h={}", w, h) << "\n";
    // 防止除以 0
    if (h == 0) {
        throw std::runtime_error("h == 0");
    }
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION); // 投影
    glLoadIdentity();
    const GLfloat aspectRatio =
        static_cast<GLfloat>(w) / static_cast<GLfloat>(h);
    gluPerspective(60.0F, aspectRatio, 1.0F, 500.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
        exit(0);
        break;
    case 'a':
    case 'A':
        zRot -= 5.0F; // 绕 Z 轴逆时针旋转
        glutPostRedisplay();
        break;
    case 'd':
    case 'D':
        zRot += 5.0F; // 绕 Z 轴顺时针旋转
        glutPostRedisplay();
        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:
        xRot -= 5.0F;
        break;
    case GLUT_KEY_DOWN:
        xRot += 5.0F;
        break;
    case GLUT_KEY_LEFT:
        yRot -= 5.0F;
        break;
    case GLUT_KEY_RIGHT:
        yRot += 5.0F;
        break;
    default:
        break;
    }
    // Refresh the Window
    glutPostRedisplay();
}

auto main(const int argc, const char **const argv) -> int {
    std::cout << "hello world\n";
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << "\n";
    }
    // 初始化 glut
    glutInit(const_cast<int *>(&argc), const_cast<char **>(argv));
    // 设置显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 设置窗口大小
    glutInitWindowSize(800, 600);
    // 创建窗口
    const int result = glutCreateWindow("Hello World");
    std::cout << "result: " << result << "\n";
    if (result == 0) {
        throw std::runtime_error("glutCreateWindow failed");
    }
    // 设置显示回调函数
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    // 注册 键盘事件 回调
    glutSpecialFunc(specialKeys);           // 特殊键事件
    glutKeyboardFunc(keyboard);             // 普通键事件
    setupRc();
    glutMainLoop();
    return 0;
}

画一个有亮度的飞机


// mac 特有的头文件
#include <GLUT/glut.h>

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>

#include <cmath>
#include <iostream>
#include <numbers>


// 可视坐标大小
static constexpr GLfloat COORDINATE_SIZE = 120.0F;
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

// 4x4 矩阵乘法: result = a * b(列主序,兼容 OpenGL)
void multiplyMatrix4(const GLfloat a[16], const GLfloat b[16],
                     GLfloat result[16]) {
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            result[col * 4 + row] = a[0 * 4 + row] * b[col * 4 + 0] +
                                    a[1 * 4 + row] * b[col * 4 + 1] +
                                    a[2 * 4 + row] * b[col * 4 + 2] +
                                    a[3 * 4 + row] * b[col * 4 + 3];
        }
    }
}

// 用三角形三个顶点求面法线(右手定则叉乘 + 归一化),并设为当前法线。
// 顶点顺序需与 glVertex3f 一致:CCW 正面时法线指向观察者。
void setFaceNormal(const GLfloat ax, const GLfloat ay, const GLfloat az,
                   const GLfloat bx, const GLfloat by, const GLfloat bz,
                   const GLfloat cx, const GLfloat cy, const GLfloat cz) {
    const GLfloat ux = bx - ax, uy = by - ay, uz = bz - az;
    const GLfloat vx = cx - ax, vy = cy - ay, vz = cz - az;
    GLfloat nx = uy * vz - uz * vy;
    GLfloat ny = uz * vx - ux * vz;
    GLfloat nz = ux * vy - uy * vx;
    const GLfloat len = std::sqrt(nx * nx + ny * ny + nz * nz);
    if (len > 0.0F) {
        nx /= len;
        ny /= len;
        nz /= len;
    }
    glNormal3f(nx, ny, nz);
}

void renderScene() {
    std::cout << std::format("renderScene: xRot={}, yRot={}, zRot={}", xRot, yRot, zRot) << "\n";
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    // --- 手动构建变换矩阵,替代 glTranslatef / glRotatef ---
    const GLfloat radX = xRot * PI / 180.0F;
    const GLfloat radY = yRot * PI / 180.0F;
    const GLfloat radZ = zRot * PI / 180.0F;

    const GLfloat cx = cosf(radX), sx = sinf(radX);
    const GLfloat cy = cosf(radY), sy = sinf(radY);
    const GLfloat cz = cosf(radZ), sz = sinf(radZ);

    // 平移矩阵 T(列主序)
    const GLfloat trans[16] = {
        1.0F, 0.0F, 0.0F,  0.0F, // col 0
        0.0F, 1.0F, 0.0F,  0.0F, // col 1
        0.0F, 0.0F, 1.0F,  0.0F, // col 2
        0.0F, 0.0F, -100.0F, 1.0F  // col 3 (tx, ty, tz, 1)
    };

    // 绕 X 轴旋转矩阵(列主序)
    const GLfloat rotX[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cx,   sx,   0.0F,
                              0.0F, -sx,  cx,   0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Y 轴旋转矩阵(列主序)
    const GLfloat rotY[16] = {cy, 0.0F, -sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
                              sy, 0.0F, cy,  0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Z 轴旋转矩阵(列主序)
    const GLfloat rotZ[16] = {cz,   sz,   0.0F, 0.0F, -sz,  cz,   0.0F, 0.0F,
                              0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 组合: T * Rx * Ry * Rz
    GLfloat temp1[16], temp2[16], finalMatrix[16];
    multiplyMatrix4(rotY, rotZ, temp1);         // temp1 = Ry * Rz
    multiplyMatrix4(rotX, temp1, temp2);        // temp2 = Rx * Ry * Rz
    multiplyMatrix4(trans, temp2, finalMatrix); // final = T * Rx * Ry * Rz

    glMultMatrixf(finalMatrix);

    // 绘制 喷气式飞机
    glBegin(GL_TRIANGLES);
        // 飞机头
        glColor3ub(255, 255, 255);

        setFaceNormal(0.0F, 0.0F, 60.0F,
                      -15.0F, 0.0F, 30.0F,
                      15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, 60.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);

        glColor3ub(0, 0, 0);
        setFaceNormal(15.0F, 0.0F, 30.0F,
                      0.0F, 15.0F, 30.0F,
                      0.0F, 0.0F, 60.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, 60.0F);

        glColor3ub(255, 0, 0);
        setFaceNormal(0.0F, 0.0F, 60.0F,
                      0.0F, 15.0F, 30.0F,
                      -15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, 60.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);

        // 飞机体
        glColor3ub(0, 255, 0);
        setFaceNormal(-15.0F, 0.0F, 30.0F,
                      0.0F, 15.0F, 30.0F,
                      0.0F, 0.0F, -56.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);

        glColor3ub(255, 255, 0);
        setFaceNormal(0.0F, 0.0F, -56.0F,
                      0.0F, 15.0F, 30.0F,
                      15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);

        glColor3ub(0, 255, 255);
        setFaceNormal(15.0F, 0.0F, 30.0F,
                      -15.0F, 0.0F, 30.0F,
                      0.0F, 0.0F, -56.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);

        // 机翼
        glColor3ub(128, 128, 128);
        setFaceNormal(0.0F, 2.0F, 27.0F,
                      -60.0F, 2.0F, -8.0F,
                      60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);

        glColor3ub(64, 64, 64);
        setFaceNormal(60.0F, 2.0F, -8.0F,
                      0.0F, 7.0F, -8.0F,
                      0.0F, 2.0F, 27.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);

        glColor3ub(192, 192, 192);
        setFaceNormal(60.0F, 2.0F, -8.0F,
                      -60.0F, 2.0F, -8.0F,
                      0.0F, 7.0F, -8.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);

        glColor3ub(64, 64, 64);
        setFaceNormal(0.0F, 2.0F, 27.0F,
                      0.0F, 7.0F, -8.0F,
                      -60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);

        // tail
        glColor3ub(255, 128, 255);
        setFaceNormal(-30.0F, -0.5F, -57.0F,
                      30.0F, -0.5F, -57.0F,
                      0.0F, -0.5F, -40.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);

        glColor3ub(255, 128, 0);
        setFaceNormal(0.0F, -0.5F, -40.0F,
                      30.0F, -0.5F, -57.0F,
                      0.0F, 4.0F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);

        glColor3ub(255, 128, 0);
        setFaceNormal(0.0F, 4.0F, -57.0F,
                      -30.0F, -0.5F, -57.0F,
                      0.0F, -0.5F, -40.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);

        glColor3ub(255, 255, 255);
        setFaceNormal(30.0F, -0.5F, -57.0F,
                      -30.0F, -0.5F, -57.0F,
                      0.0F, 4.0F, -57.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);

        glColor3ub(255, 0, 0);
        setFaceNormal(0.0F, 0.5F, -40.0F,
                      3.0F, 0.5F, -57.0F,
                      0.0F, 25.0F, -65.0F);
        glVertex3f(0.0F, 0.5F, -40.0F);
        glVertex3f(3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);

        glColor3ub(255, 0, 0);
        setFaceNormal(0.0F, 25.0F, -65.0F,
                      -3.0F, 0.5F, -57.0F,
                      0.0F, 0.5F, -40.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);
        glVertex3f(-3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 0.5F, -40.0F);

        glColor3ub(128, 128, 128);
        setFaceNormal(3.0F, 0.5F, -57.0F,
                      -3.0F, 0.5F, -57.0F,
                      0.0F, 25.0F, -65.0F);
        glVertex3f(3.0F, 0.5F, -57.0F);
        glVertex3f(-3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);
    glEnd();


    glPopMatrix();

    glutSwapBuffers();
}

void setupRc() {
    // 设置清除颜色为黑色
    glClearColor(0.23F, 0.23F, 0.34F, 1.0F);
    glEnable(GL_DEPTH_TEST); // 启用深度测试
    glShadeModel(GL_SMOOTH);
    glEnable(GL_CULL_FACE);
    
    glEnable(GL_LIGHTING);
    constexpr GLfloat ambientLight[] = {0.3F, 0.3F, 0.3F, 1.0F};
    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    constexpr GLfloat diffuseLight[] = {0.7F, 0.7F, 0.7F, 1.0F};
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    constexpr GLfloat lightPos[] = {0.0F, 50.0F, -100.0F, 1.0F};
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    constexpr GLfloat specularLight[] = {1.0F, 1.0F, 1.0F, 1.0F};
    glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);
    glEnable(GL_LIGHT0);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    constexpr GLfloat specref[] = {1.0F, 1.0F, 1.0F, 1.0F};
    glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
    glMateriali(GL_FRONT, GL_SHININESS, 128);
}

void changeSize(const GLsizei w, const GLsizei h) {
    std::cout << std::format("changeSize: w={}, h={}", w, h) << "\n";
    // 防止除以 0
    if (h == 0) {
        throw std::runtime_error("h == 0");
    }
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION); // 投影
    glLoadIdentity();
    const GLfloat aspectRatio =
        static_cast<GLfloat>(w) / static_cast<GLfloat>(h);
    gluPerspective(60.0F, aspectRatio, 1.0F, 500.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
        exit(0);
        break;
    case 'a':
    case 'A':
        zRot -= 5.0F; // 绕 Z 轴逆时针旋转
        glutPostRedisplay();
        break;
    case 'd':
    case 'D':
        zRot += 5.0F; // 绕 Z 轴顺时针旋转
        glutPostRedisplay();
        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:
        xRot -= 5.0F;
        break;
    case GLUT_KEY_DOWN:
        xRot += 5.0F;
        break;
    case GLUT_KEY_LEFT:
        yRot -= 5.0F;
        break;
    case GLUT_KEY_RIGHT:
        yRot += 5.0F;
        break;
    default:
        break;
    }
    // Refresh the Window
    glutPostRedisplay();
}

auto main(const int argc, const char **const argv) -> int {
    std::cout << "hello world\n";
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << "\n";
    }
    // 初始化 glut
    glutInit(const_cast<int *>(&argc), const_cast<char **>(argv));
    // 设置显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 设置窗口大小
    glutInitWindowSize(800, 600);
    // 创建窗口
    const int result = glutCreateWindow("Hello World");
    std::cout << "result: " << result << "\n";
    if (result == 0) {
        throw std::runtime_error("glutCreateWindow failed");
    }
    // 设置显示回调函数
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    // 注册 键盘事件 回调
    glutSpecialFunc(specialKeys);           // 特殊键事件
    glutKeyboardFunc(keyboard);             // 普通键事件
    setupRc();
    glutMainLoop();
    return 0;
}

灯绕球旋转


// mac 特有的头文件
#include <GLUT/glut.h>

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>

#include <cmath>
#include <iostream>
#include <numbers>


// 可视坐标大小
static constexpr GLfloat COORDINATE_SIZE = 120.0F;
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

 static int mainWindowId = 0;  // 全局

static GLboolean isShade = GL_TRUE; // 是否平滑着色 (GL_TRUE = 平滑, GL_FALSE = 平面)
static GLubyte iTess = 0;
static GLfloat lightPos[] = {0.0F, 0.0F, 75.0F, 1.0F};

// 4x4 矩阵乘法: result = a * b(列主序,兼容 OpenGL)
void multiplyMatrix4(const GLfloat a[16], const GLfloat b[16],
                     GLfloat result[16]) {
    for (int col = 0; col < 4; ++col) {
        for (int row = 0; row < 4; ++row) {
            result[col * 4 + row] = a[0 * 4 + row] * b[col * 4 + 0] +
                                    a[1 * 4 + row] * b[col * 4 + 1] +
                                    a[2 * 4 + row] * b[col * 4 + 2] +
                                    a[3 * 4 + row] * b[col * 4 + 3];
        }
    }
}

// 用三角形三个顶点求面法线(右手定则叉乘 + 归一化),并设为当前法线。
// 顶点顺序需与 glVertex3f 一致:CCW 正面时法线指向观察者。
void setFaceNormal(const GLfloat ax, const GLfloat ay, const GLfloat az,
                   const GLfloat bx, const GLfloat by, const GLfloat bz,
                   const GLfloat cx, const GLfloat cy, const GLfloat cz) {
    const GLfloat ux = bx - ax, uy = by - ay, uz = bz - az;
    const GLfloat vx = cx - ax, vy = cy - ay, vz = cz - az;
    GLfloat nx = uy * vz - uz * vy;
    GLfloat ny = uz * vx - ux * vz;
    GLfloat nz = ux * vy - uy * vx;
    const GLfloat len = std::sqrt(nx * nx + ny * ny + nz * nz);
    if (len > 0.0F) {
        nx /= len;
        ny /= len;
        nz /= len;
    }
    glNormal3f(nx, ny, nz);
}

void renderScene() {
    std::cout << std::format("renderScene: xRot={}, yRot={}, zRot={}", xRot, yRot, zRot) << "\n";

    if (isShade) {
        glShadeModel(GL_SMOOTH);
    } else {
        glShadeModel(GL_FLAT);
    }

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glPushMatrix();

    // --- 手动构建变换矩阵,替代 glTranslatef / glRotatef ---
    const GLfloat radX = xRot * PI / 180.0F;
    const GLfloat radY = yRot * PI / 180.0F;
    const GLfloat radZ = zRot * PI / 180.0F;

    const GLfloat cx = cosf(radX), sx = sinf(radX);
    const GLfloat cy = cosf(radY), sy = sinf(radY);
    const GLfloat cz = cosf(radZ), sz = sinf(radZ);

    // 平移矩阵 T(列主序)
    const GLfloat trans[16] = {
        1.0F, 0.0F, 0.0F,  0.0F, // col 0
        0.0F, 1.0F, 0.0F,  0.0F, // col 1
        0.0F, 0.0F, 1.0F,  0.0F, // col 2
        0.0F, 0.0F, 1.0F, 1.0F  // col 3 (tx, ty, tz, 1)
    };

    // 绕 X 轴旋转矩阵(列主序)
    const GLfloat rotX[16] = {1.0F, 0.0F, 0.0F, 0.0F, 0.0F, cx,   sx,   0.0F,
                              0.0F, -sx,  cx,   0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Y 轴旋转矩阵(列主序)
    const GLfloat rotY[16] = {cy, 0.0F, -sy, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F,
                              sy, 0.0F, cy,  0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 绕 Z 轴旋转矩阵(列主序)
    const GLfloat rotZ[16] = {cz,   sz,   0.0F, 0.0F, -sz,  cz,   0.0F, 0.0F,
                              0.0F, 0.0F, 1.0F, 0.0F, 0.0F, 0.0F, 0.0F, 1.0F};

    // 组合: T * Rx * Ry * Rz
    GLfloat temp1[16], temp2[16], finalMatrix[16];
    multiplyMatrix4(rotY, rotZ, temp1);         // temp1 = Ry * Rz
    multiplyMatrix4(rotX, temp1, temp2);        // temp2 = Rx * Ry * Rz
    multiplyMatrix4(trans, temp2, finalMatrix); // final = T * Rx * Ry * Rz

    glMultMatrixf(finalMatrix);

    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    constexpr GLfloat spotDir[] = { 0.0F, 0.0F, -1.0F }; // 聚光灯方向-z
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);

    glPushAttrib(GL_LIGHTING_BIT);
        glDisable(GL_LIGHTING);
        glColor3ub(255, 0, 0); // 红色
        glTranslatef(lightPos[0], lightPos[1], lightPos[2]);
        glutSolidCone(4.0F, 6.0F, 15, 15); // 绘制光源位置的小球
        glColor3ub(255, 255, 0); // 黄色
        glutSolidSphere(3.0F, 15, 15); // 绘制光源位置的小球
    glPopAttrib();

    glPopMatrix();

    // 设置大球材质颜色为蓝色(否则会沿用灯泡的黄色)
    glColor3ub(0, 0, 255);

    switch (iTess) {
        case 0:
            // 绘制默认模型
            glutSolidSphere(30.0F, 7, 7);
            break;
        case 1:
            // 绘制细分模型 1
            glutSolidSphere(30.0F, 15, 15);
            break;
        case 2:
            // 绘制细分模型 2
            glutSolidSphere(30.0F, 50, 50);
            break;
        default:
            break;
    }

    glutSwapBuffers();
}

void setupRc() {
    // 设置清除颜色为黑色
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
    glEnable(GL_DEPTH_TEST); // 启用深度测试
    glShadeModel(GL_SMOOTH);
    glEnable(GL_CULL_FACE);
    
    glEnable(GL_LIGHTING);
    constexpr GLfloat ambientLight[] = {0.5F, 0.5F, 0.5F, 1.0F};
    // 全局环境光(原版用 GL_LIGHT_MODEL_AMBIENT):聚光锥外的暗部也能看见
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);

    constexpr GLfloat lightPos[] = {0.0F, 0.0F, 75.0F, 1.0F};
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    constexpr GLfloat specularLight[] = {1.0F, 1.0F, 1.0F, 1.0F};
    // 镜面反射光
    glLightfv(GL_LIGHT0, GL_SPECULAR, specularLight);

    constexpr GLfloat spotDir[] = { 0.0F, 0.0F, -1.0F }; // 聚光灯方向-z
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);
    glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 60.0F);


    glEnable(GL_LIGHT0);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    constexpr GLfloat specref[] = {1.0F, 1.0F, 1.0F, 1.0F};
    glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
    glMateriali(GL_FRONT, GL_SHININESS, 128);
}

void changeSize(const GLsizei w, const GLsizei h) {
    std::cout << std::format("changeSize: w={}, h={}", w, h) << "\n";
    // 防止除以 0
    if (h == 0) {
        throw std::runtime_error("h == 0");
    }
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION); // 投影
    glLoadIdentity();
    const GLfloat aspectRatio =
        static_cast<GLfloat>(w) / static_cast<GLfloat>(h);
    gluPerspective(35.0F, aspectRatio, 1.0F, 500.0F); // 原版 Spot.c 的视角
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -250.0f);
}

// 键盘回调:按 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";
        exit(0);
        break;
    case 'a':
    case 'A':
        zRot -= 5.0F; // 绕 Z 轴逆时针旋转
        glutPostRedisplay();
        break;
    case 'd':
    case 'D':
        zRot += 5.0F; // 绕 Z 轴顺时针旋转
        glutPostRedisplay();
        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:
        xRot -= 5.0F;
        break;
    case GLUT_KEY_DOWN:
        xRot += 5.0F;
        break;
    case GLUT_KEY_LEFT:
        yRot -= 5.0F;
        break;
    case GLUT_KEY_RIGHT:
        yRot += 5.0F;
        break;
    default:
        break;
    }
    // Refresh the Window
    glutPostRedisplay();
}
void processMenu(const int value) {
    std::cout << std::format("processMenu: value={}", value) << "\n";
    switch (value) {
    case 1:
        isShade = GL_FALSE; // 平面着色
        break;
    case 2:
        isShade = GL_TRUE; // 平滑着色
        break;
    case 3:
        iTess = 0;
        break;
    case 4:
        iTess = 1;
        break;
    case 5:
        iTess = 2;
        break;
    default:
        break;
    }
    glutSetWindow(mainWindowId);
    glutPostRedisplay();
}
auto main(const int argc, const char **const argv) -> int {
    std::cout << "hello world\n";
    for (int i = 0; i < argc; ++i) {
        std::cout << "argv[" << i << "]: " << argv[i] << "\n";
    }
    // 初始化 glut
    glutInit(const_cast<int *>(&argc), const_cast<char **>(argv));
    // 设置显示模式
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    // 设置窗口大小
    glutInitWindowSize(800, 600);
    // 创建窗口
    mainWindowId = glutCreateWindow("Hello World");
    std::cout << "mainWindowId: " << mainWindowId << "\n";
    if (mainWindowId == 0) {
        throw std::runtime_error("glutCreateWindow failed");
    }
    // 设置显示回调函数
    glutDisplayFunc(renderScene);
    glutReshapeFunc(changeSize);
    // 注册 键盘事件 回调
    glutSpecialFunc(specialKeys);           // 特殊键事件
    glutKeyboardFunc(keyboard);             // 普通键事件
    const int menuId = glutCreateMenu(processMenu);
    std::cout << "menuId: " << menuId << "\n";
    glutSetMenu(menuId);
    glutAddMenuEntry("Flat Shading", 1);
    glutAddMenuEntry("Smooth Shading", 2);
    glutAddMenuEntry("VL Tess", 3);
    glutAddMenuEntry("MD Tess", 4);
    glutAddMenuEntry("VH Tess", 5);
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    setupRc();
    glutMainLoop();
    return 0;
}

飞机阴影

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif

#include <iostream>

// 常量
static constexpr GLfloat ambientLight[] = { 0.3F, 0.3F, 0.3F, 1.0F };
static constexpr GLfloat diffuseLight[] = { 0.7F, 0.7F, 0.7F, 1.0F };
static constexpr GLfloat specular[] = { 1.0F, 1.0F, 1.0F, 1.0F };
static constexpr GLfloat lightPos[] = { -75.0F, 150.0F, -50.0F, 0.0F };
static constexpr GLfloat specref[] = { 1.0F, 1.0F, 1.0F, 1.0F };

// 地面平面方程:绿色地面 y = -100 → 0·x + 1·y + 0·z + 100 = 0(注意 d 是 +100)
static constexpr GLfloat groundPlaneEq[] = { 0.0F, 1.0F, 0.0F, 100.0F };

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度

/**
 * 
 */
void changeSize(const int width, const int height){
    if(height == 0) {
        throw std::runtime_error("Height cannot be zero.");
    }
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    const GLfloat aspectRatio = static_cast<GLfloat>(width) / static_cast<GLfloat>(height);
    gluPerspective(60.0F, aspectRatio, 200.0F, 500.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
            exit(0);
            break;
        case 'a':
        case 'A':
            zRot -= 5.0F; // 绕 Z 轴逆时针旋转
            glutPostRedisplay();
            break;
        case 'd':
        case 'D':
            zRot += 5.0F; // 绕 Z 轴顺时针旋转
            glutPostRedisplay();
            break;
        default:
            break;
    }
}

void specialKeys(const int key, const int x, const int y){
    switch (key) {
        case GLUT_KEY_UP:
            xRot -= 5.0F;
            break;
        case GLUT_KEY_DOWN:
            xRot += 5.0F;
            break;
        case GLUT_KEY_LEFT:
            yRot -= 5.0F;
            break;
        case GLUT_KEY_RIGHT:
            yRot += 5.0F;
            break;
        default:
            return; // 不处理其他按键
    }
    glutPostRedisplay();
}
void setFaceNormal(const GLfloat ax, const GLfloat ay, const GLfloat az,
                   const GLfloat bx, const GLfloat by, const GLfloat bz,
                   const GLfloat cx, const GLfloat cy, const GLfloat cz) {
    const GLfloat ux = bx - ax, uy = by - ay, uz = bz - az;
    const GLfloat vx = cx - ax, vy = cy - ay, vz = cz - az;
    GLfloat nx = uy * vz - uz * vy;
    GLfloat ny = uz * vx - ux * vz;
    GLfloat nz = ux * vy - uy * vx;
    const GLfloat len = std::sqrt(nx * nx + ny * ny + nz * nz);
    if (len > 0.0F) {
        nx /= len;
        ny /= len;
        nz /= len;
    }
    glNormal3f(nx, ny, nz);
}
void drawJet(const GLboolean isShadow = GL_FALSE) {
    if (isShadow) {
        glColor3ub(0, 0, 0);
    } else {
        glColor3ub(128, 128, 128);
    }
    glBegin(GL_TRIANGLES);
        // 飞机头
        setFaceNormal(0.0F, 0.0F, 60.0F,
                      -15.0F, 0.0F, 30.0F,
                      15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, 60.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);

        setFaceNormal(15.0F, 0.0F, 30.0F,
                      0.0F, 15.0F, 30.0F,
                      0.0F, 0.0F, 60.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, 60.0F);

        setFaceNormal(0.0F, 0.0F, 60.0F,
                      0.0F, 15.0F, 30.0F,
                      -15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, 60.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);

        // 飞机体
        setFaceNormal(-15.0F, 0.0F, 30.0F,
                      0.0F, 15.0F, 30.0F,
                      0.0F, 0.0F, -56.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);

        setFaceNormal(0.0F, 0.0F, -56.0F,
                      0.0F, 15.0F, 30.0F,
                      15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);
        glVertex3f(0.0F, 15.0F, 30.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);

        setFaceNormal(15.0F, 0.0F, 30.0F,
                      -15.0F, 0.0F, 30.0F,
                      0.0F, 0.0F, -56.0F);
        glVertex3f(15.0F, 0.0F, 30.0F);
        glVertex3f(-15.0F, 0.0F, 30.0F);
        glVertex3f(0.0F, 0.0F, -56.0F);

        // 机翼
        setFaceNormal(0.0F, 2.0F, 27.0F,
                      -60.0F, 2.0F, -8.0F,
                      60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);

        setFaceNormal(60.0F, 2.0F, -8.0F,
                      0.0F, 7.0F, -8.0F,
                      0.0F, 2.0F, 27.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);

        setFaceNormal(60.0F, 2.0F, -8.0F,
                      -60.0F, 2.0F, -8.0F,
                      0.0F, 7.0F, -8.0F);
        glVertex3f(60.0F, 2.0F, -8.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);

        setFaceNormal(0.0F, 2.0F, 27.0F,
                      0.0F, 7.0F, -8.0F,
                      -60.0F, 2.0F, -8.0F);
        glVertex3f(0.0F, 2.0F, 27.0F);
        glVertex3f(0.0F, 7.0F, -8.0F);
        glVertex3f(-60.0F, 2.0F, -8.0F);

        // tail
        setFaceNormal(-30.0F, -0.5F, -57.0F,
                      30.0F, -0.5F, -57.0F,
                      0.0F, -0.5F, -40.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);

        setFaceNormal(0.0F, -0.5F, -40.0F,
                      30.0F, -0.5F, -57.0F,
                      0.0F, 4.0F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);

        setFaceNormal(0.0F, 4.0F, -57.0F,
                      -30.0F, -0.5F, -57.0F,
                      0.0F, -0.5F, -40.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, -0.5F, -40.0F);

        setFaceNormal(30.0F, -0.5F, -57.0F,
                      -30.0F, -0.5F, -57.0F,
                      0.0F, 4.0F, -57.0F);
        glVertex3f(30.0F, -0.5F, -57.0F);
        glVertex3f(-30.0F, -0.5F, -57.0F);
        glVertex3f(0.0F, 4.0F, -57.0F);

        setFaceNormal(0.0F, 0.5F, -40.0F,
                      3.0F, 0.5F, -57.0F,
                      0.0F, 25.0F, -65.0F);
        glVertex3f(0.0F, 0.5F, -40.0F);
        glVertex3f(3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);

        setFaceNormal(0.0F, 25.0F, -65.0F,
                      -3.0F, 0.5F, -57.0F,
                      0.0F, 0.5F, -40.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);
        glVertex3f(-3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 0.5F, -40.0F);

        setFaceNormal(3.0F, 0.5F, -57.0F,
                      -3.0F, 0.5F, -57.0F,
                      0.0F, 25.0F, -65.0F);
        glVertex3f(3.0F, 0.5F, -57.0F);
        glVertex3f(-3.0F, 0.5F, -57.0F);
        glVertex3f(0.0F, 25.0F, -65.0F);
    glEnd();
}
/**
 * 计算平面投影阴影矩阵:把顶点沿光线方向投影到平面 planeEq 上
 *
 * planeEq : 平面方程 a·x + b·y + c·z + d = 0
 * lightPos: 与 glLightfv(GL_POSITION) 相同的齐次坐标
 *           w = 1 点光源(阴影有透视近大远小)
 *           w = 0 平行光/方向光(阳光,阴影是平行投影)
 * m       : 输出 16 元素列主序矩阵,可直接传给 glMultMatrixf
 *
 * 原理:顶点 p 沿光线方向的影子 s = p + t·L 满足 n·s + d = 0,
 *       解得 t = -(n·p + d)/(n·L),代回可得齐次形式
 *       s' = dot·p - (n·p)·L,其中 dot = n·L + d·Lw。
 *       这是 p 的线性(射影)变换,故可写成 4×4 矩阵:
 *       M[i][j] = dot·δ(i,j) - L[i]·n[j],按列主序展开。
 */
void makeShadowMatrix(GLfloat m[16], const GLfloat planeEq[4], const GLfloat lightPos[4]) {
    const GLfloat dot = planeEq[0] * lightPos[0] + planeEq[1] * lightPos[1] +
                        planeEq[2] * lightPos[2] + planeEq[3] * lightPos[3];

    m[0]  = dot - lightPos[0] * planeEq[0];   // 第 1 列(列主序)
    m[1]  =     - lightPos[1] * planeEq[0];
    m[2]  =     - lightPos[2] * planeEq[0];
    m[3]  =     - lightPos[3] * planeEq[0];
    m[4]  =     - lightPos[0] * planeEq[1];   // 第 2 列
    m[5]  = dot - lightPos[1] * planeEq[1];
    m[6]  =     - lightPos[2] * planeEq[1];
    m[7]  =     - lightPos[3] * planeEq[1];
    m[8]  =     - lightPos[0] * planeEq[2];   // 第 3 列
    m[9]  =     - lightPos[1] * planeEq[2];
    m[10] = dot - lightPos[2] * planeEq[2];
    m[11] =     - lightPos[3] * planeEq[2];
    m[12] =     - lightPos[0] * planeEq[3];   // 第 4 列
    m[13] =     - lightPos[1] * planeEq[3];
    m[14] =     - lightPos[2] * planeEq[3];
    m[15] = dot - lightPos[3] * planeEq[3];
}

void renderScene(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glBegin(GL_QUADS);
        glColor3ub(0, 32, 0);
        glVertex3f(2000.0F, -100.0F, -500.0F);   // 远边(暗)
        glVertex3f(-2000.0F, -100.0F, -500.0F);
        glColor3ub(0, 255, 0);
        glVertex3f(-2000.0F, -100.0F, -200.0F);   // 近边(亮)
        glVertex3f(2000.0F, -100.0F, -200.0F);
    glEnd();

    glPushMatrix();

    glEnable(GL_LIGHTING);
    glEnable(GL_DEPTH_TEST);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    
    glTranslatef(0.0F, 0.0F, -350.0F);
    glRotatef(xRot, 1.0F, 0.0F, 0.0F);
    glRotatef(yRot, 0.0F, 1.0F, 0.0F);
    glRotatef(zRot, 0.0F, 0.0F, 1.0F);

    drawJet();

    glPopMatrix();

    // 绘制阴影
    glDisable(GL_DEPTH_TEST); // 不关闭的话阴影显示不全
    glDisable(GL_LIGHTING);
    glPushMatrix();

    // 根据 绿色地面 和 光源位置 计算阴影矩阵
    GLfloat shadowMat[16];
    makeShadowMatrix(shadowMat, groundPlaneEq, lightPos);
    glMultMatrixf(shadowMat);

    glTranslatef(0.0F, 0.0F, -350.0F);
    glRotatef(xRot, 1.0F, 0.0F, 0.0F);
    glRotatef(yRot, 0.0F, 1.0F, 0.0F);
    glRotatef(zRot, 0.0F, 0.0F, 1.0F);
    
    drawJet(GL_TRUE);

    glPopMatrix();

    // 光源
    glPushMatrix();
    glTranslatef(lightPos[0], lightPos[1], -350.0F);
    glColor3ub(255, 255, 0);
    glutSolidSphere(5.0F, 15, 15);
    glPopMatrix();

    glutSwapBuffers();
}

void setupRc(){
    glClearColor(0.0F, 0.0F, 1.0F, 1.0F);
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);

    glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
    glEnable(GL_LIGHT0);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
    glMateriali(GL_FRONT, GL_SHININESS, 128);
}

auto main(const int argc, const char** argv) -> int {
    glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    std::cout << "Main window ID: " << mainWindowId << std::endl;
    glutReshapeFunc(changeSize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeys);
    glutDisplayFunc(renderScene);
    setupRc();
    glutMainLoop();
    return 0;
}

视锥如下:
image
视点可以根据gluLookAt调整。
涉及线性代数数学知识,已知,点\(A(x_1, y_1,z_1),B(x_2,y_2,z_2),C(x_3,y_3,z_3)\)三点不共线,求平面方程
方法1:用叉积求法向量

\[\overrightarrow{AB}=(x_2-x_1,y_2-y_1,z_2-z_1)\\ \overrightarrow{AC}=(x_3-x_1,y_3-y_1,z_3-z_1) \\ \vec{n}=\overrightarrow{AB}\times\overrightarrow{AC}= \begin{vmatrix} \boldsymbol{i} & \boldsymbol{j} & \boldsymbol{k} \\ x_2-x_1 & y_2-y_1 & z_2-z_1 \\ x_3-x_1 & y_3-y_1 & z_3-z_1 \end{vmatrix} \\ \overrightarrow{AP} = (x-x_1,y-y_1,z-z_1)\\ \vec{n} \cdot \overrightarrow{AP} = 0 代入可得平面方程\\ ax+by+cz+d=0 \\ d=−(ax_1+by_1+cz_1) \]

方法2: 行列式

\[\begin{aligned} \overrightarrow{AB}&=(x_2-x_1,y_2-y_1,z_2-z_1) \\ \overrightarrow{AC}&=(x_3-x_1,y_3-y_1,z_3-z_1) \\ \overrightarrow{AP}&= (x-x_1,y-y_1,z-z_1) \\ \end{aligned} \\ \overrightarrow{AP} \cdot (\overrightarrow{AB} \times \overrightarrow{AC})= \begin{vmatrix} x-x_1 & y-y_1 & z-z_1 \\ x_2-x_1 & y_2-y_1 & z_2-z_1 \\ x_3-x_1 & y_3-y_1 & z_3-z_1 \end{vmatrix} = 0 \\ 横列式变换 \implies \begin{vmatrix} x & y & z & 1 \\ x1 & y1 & z1 & 1 \\ x2 & y2 & z2 & 1 \\ x3 & y3 & z3 & 1 \end{vmatrix} = 0 \]

这个矩阵就是把你刚才的投影公式,用齐次坐标矩阵乘法表达出来的紧凑形式。我来一步步推导,你就能看清它是怎么来的。


1. 回顾我们之前的公式

平面方程:\(ax + by + cz + d = 0\),写成向量点积:

\[\vec{n} \cdot \vec{x} + d = 0 \]

其中 \(\vec{n} = (a,b,c)\)\(\vec{x} = (x,y,z)\)

用齐次坐标表示平面:

\[\vec{p} = (a,b,c,d) \]

\(\vec{x} = (x,y,z,w)\) 在平面上满足:

\[\vec{p} \cdot \vec{x} = ax + by + cz + dw = 0 \]

(对于欧氏点,\(w=1\)


2. 投影公式的齐次形式

光源:\(L = (l_x, l_y, l_z, l_w)\)(欧氏点通常 \(l_w=1\)

物体点:\(Q = (x, y, z, w)\)(欧氏点 \(w=1\)

射线参数方程:

\[R(t) = L + t(Q - L) \]

要求它在平面上:

\[\vec{p} \cdot R(t) = 0 \]

代入:

\[\vec{p} \cdot [L + t(Q - L)] = 0 \]

\[\vec{p} \cdot L + t(\vec{p} \cdot Q - \vec{p} \cdot L) = 0 \]

\[t = -\frac{\vec{p} \cdot L}{\vec{p} \cdot Q - \vec{p} \cdot L} \]

注意:因为 \(Q\) 在平面上时 \(\vec{p} \cdot Q = 0\),所以 \(t=1\),点不变(合理)。


3. 关键技巧:令 \(t = -\frac{\vec{p} \cdot L}{\vec{p} \cdot Q - \vec{p} \cdot L}\)

阴影点:

\[S = L + t(Q - L) \]

代入 $$t$$:

\[S = L - \frac{\vec{p} \cdot L}{\vec{p} \cdot Q - \vec{p} \cdot L}(Q - L) \]

通分:

\[S = \frac{(\vec{p} \cdot Q - \vec{p} \cdot L)L - (\vec{p} \cdot L)(Q - L)}{\vec{p} \cdot Q - \vec{p} \cdot L} \]

化简分子:

\[= \frac{(\vec{p} \cdot Q)L - (\vec{p} \cdot L)L - (\vec{p} \cdot L)Q + (\vec{p} \cdot L)L}{\vec{p} \cdot Q - \vec{p} \cdot L} \]

\[= \frac{(\vec{p} \cdot Q)L - (\vec{p} \cdot L)Q}{\vec{p} \cdot Q - \vec{p} \cdot L} \]

关键发现:分母的\(\vec{p} \cdot Q - \vec{p} \cdot L\) 也可以写成 \(\vec{p} \cdot (Q - L)\),但为了矩阵形式,我们保持原样。


4. 齐次坐标下的线性变换

上面的表达式是分式线性的,但在齐次坐标下可以写成线性变换

定义变换 \(M\),使得:

\[S = \frac{(\vec{p} \cdot Q)L - (\vec{p} \cdot L)Q}{\vec{p} \cdot Q - \vec{p} \cdot L} \]

写成矩阵形式:

\[S = \frac{M \cdot Q}{\vec{p} \cdot Q} \]

但这里分母是 \(\vec{p} \cdot Q - \vec{p} \cdot L\),不是\(\vec{p} \cdot Q\)

关键:我们要找一个矩阵 \(M\),使得:

\[M \cdot Q = (\vec{p} \cdot Q)L - (\vec{p} \cdot L)Q \]

而这个结果自动满足

\[\vec{p} \cdot (M \cdot Q) = 0 \]

也就是说,$$M \cdot Q$$ 已经是在平面上的点了(齐次坐标意义下)。


5. 矩阵\(M\) 的推导

目标是:

\[M \cdot Q = (\vec{p} \cdot Q)L - (\vec{p} \cdot L)Q \]

把右边的两个项展开成矩阵形式:

第一项:((\vec{p} \cdot Q)L)

这是把标量 \((\vec{p} \cdot Q)\) 乘以向量 \(L\)

在矩阵形式中,相当于:

\[L \cdot \vec{p}^T \]

作用于 (Q):

\[(L \cdot \vec{p}^T) Q = L(\vec{p} \cdot Q) \]

第二项:\((\vec{p} \cdot L)Q\)

这是把标量 \((\vec{p} \cdot L)\) 乘以向量 \(Q\)

在矩阵形式中,相当于:

\[(\vec{p} \cdot L) I \]

作用于 (Q):

\[(\vec{p} \cdot L) I \cdot Q = (\vec{p} \cdot L)Q \]

所以:

\[M = L \cdot \vec{p}^T - (\vec{p} \cdot L) I \]


6. 展开成 4×4 矩阵

设:

\[L = (l_x, l_y, l_z, l_w)^T \]

\[\vec{p} = (a, b, c, d)^T \]

\[\text{dot} = \vec{p} \cdot L = a l_x + b l_y + c l_z + d l_w \]

\(L \cdot \vec{p}^T\) 是一个外积(4×4 矩阵):

\[L \cdot \vec{p}^T = \begin{pmatrix} l_x a & l_x b & l_x c & l_x d \\ l_y a & l_y b & l_y c & l_y d \\ l_z a & l_z b & l_z c & l_z d \\ l_w a & l_w b & l_w c & l_w d \end{pmatrix} \]

减去 \((\vec{p} \cdot L) I\)

\[M = \begin{pmatrix} l_x a - \text{dot} & l_x b & l_x c & l_x d \\ l_y a & l_y b - \text{dot} & l_y c & l_y d \\ l_z a & l_z b & l_z c - \text{dot} & l_z d \\ l_w a & l_w b & l_w c & l_w d - \text{dot} \end{pmatrix} \]


7. 对比代码中的矩阵(列主序)

代码中的矩阵是列主序(OpenGL 风格),所以存储顺序是:

m[0]  m[4]  m[8]  m[12]
m[1]  m[5]  m[9]  m[13]
m[2]  m[6]  m[10] m[14]
m[3]  m[7]  m[11] m[15]

对照我们的矩阵:

第 1 列(对应 (x) 分量):

  • m[0] = l_x*a - dot
  • m[1] = l_y*a
  • m[2] = l_z*a
  • m[3] = l_w*a

第 2 列(对应 (y) 分量):

  • m[4] = l_x*b
  • m[5] = l_y*b - dot
  • m[6] = l_z*b
  • m[7] = l_w*b

第 3 列(对应 (z) 分量):

  • m[8] = l_x*c
  • m[9] = l_y*c
  • m[10] = l_z*c - dot
  • m[11] = l_w*c

第 4 列(对应 (w) 分量):

  • m[12] = l_x*d
  • m[13] = l_y*d
  • m[14] = l_z*d
  • m[15] = l_w*d - dot

8. 和代码对比

代码中(注意符号和我推导的相反,但效果一样,因为整体乘以 -1 不改变投影):

m[0]  = dot - lightPos[0] * planeEq[0];   // dot - l_x*a
m[4]  =     - lightPos[0] * planeEq[1];   // -l_x*b
m[8]  =     - lightPos[0] * planeEq[2];   // -l_x*c
m[12] =     - lightPos[0] * planeEq[3];   // -l_x*d

这个矩阵是负的我们的 \(M\)

\[M_{\text{code}} = -M = (\vec{p} \cdot L) I - L \cdot \vec{p}^T \]


总结

推导步骤 公式
投影公式 \(S = L + t(Q-L)\), \(t = -\frac{\vec{p}\cdot L}{\vec{p}\cdot Q - \vec{p}\cdot L}\)
化简 \(S = \frac{(\vec{p}\cdot Q)L - (\vec{p}\cdot L)Q}{\vec{p}\cdot Q - \vec{p}\cdot L}\)
矩阵形式 \(M = L \cdot \vec{p}^T - (\vec{p}\cdot L) I\)
代码中的矩阵 \(M_{\text{code}} = (\vec{p}\cdot L) I - L \cdot \vec{p}^T\)(取负,不影响投影)

之所以能写成矩阵,是因为在齐次坐标下,投影变换是一个线性变换,而之前我们用的 \((x,y,z)\) 坐标是非线性的(有除法)。这就是为什么图形学中普遍使用齐次坐标和 4×4 矩阵的原因。

球体世界阴影

image

#include <OpenGL/gl.h>
#include <OpenGL/gltypes.h>
#include <OpenGL/glu.h>
#include <numbers>
#ifdef __APPLE__
#include <GLUT/glut.h>
#include <GLKit/GLKMath.h>
#else
extern "C" {
#include <GL/freeglut.h>
}
#endif

#include <cmath>
#include <iostream>

struct GLFrame {
    GLfloat vLocation[3]; // 位置  -> move x, y, z
    GLfloat vUp[3];       // +y
    GLfloat vForward[3];  // -z 
    // GLfloat right[3]; // +x-> 通过 yz叉积计算 -> vUp, vForward均为单位向量
};


// 常量
static constexpr GLfloat PI = std::numbers::pi_v<GLfloat>;
static constexpr GLint NUM_SPHERES = 30;

static constexpr GLfloat ambientLight[] = { 0.0F, 0.0F, 0.0F, 0.0F };
static constexpr GLfloat diffuseLight[] = { 1.0F, 1.0F, 1.0F, 1.0F };
static constexpr GLfloat specular[] = { 1.0F, 1.0F, 1.0F, 1.0F };
static constexpr GLfloat lightPos[] = { -100.0F, 100.0F, 50.0F, 1.0F };
static constexpr GLfloat specref[] = { 1.0F, 1.0F, 1.0F, 1.0F };

// 变量
static GLfloat xRot = 0.0F; // x旋转角度
static GLfloat yRot = 0.0F; // y旋转角度
static GLfloat zRot = 0.0F; // z旋转角度
GLFrame frameCamera; // 摄像机帧
GLFrame spheres[NUM_SPHERES]; // 球体帧
GLfloat shadowMat[16];

void gltInitFrame(GLFrame *frame) {
    frame->vLocation[0] = 0.0F;
    frame->vLocation[1] = 0.0F;
    frame->vLocation[2] = 0.0F;

    frame->vUp[0] = 0.0F;
    frame->vUp[1] = 1.0F;
    frame->vUp[2] = 0.0F;

    frame->vForward[0] = 0.0F;
    frame->vForward[1] = 0.0F;
    frame->vForward[2] = -1.0F; // 相机朝向为-z
}

void changeSize(const int width, const int height){
    std::cout << std::format("changeSize: width={}, height={}", width, height) << "\n";
    if(height == 0) {
        throw std::runtime_error("Height cannot be zero.");
    }
    glViewport(0, 0, width, height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    const GLfloat aspectRatio = static_cast<GLfloat>(width) / static_cast<GLfloat>(height);
    gluPerspective(35.0F, aspectRatio, 1.0F, 50.0F);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}

// 键盘回调:按 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";
            exit(0);
            break;
        case 'a':
        case 'A':
            zRot -= 5.0F; // 绕 Z 轴逆时针旋转
            glutPostRedisplay();
            break;
        case 'd':
        case 'D':
            zRot += 5.0F; // 绕 Z 轴顺时针旋转
            glutPostRedisplay();
            break;
        default:
            break;
    }
}

// 绕 vUp 轴左右转向(yaw)。正角度 = 向左转(右手系绕 +Y 逆时针)。
// Rodrigues 公式:v' = v·cosθ + (vUp × v)·sinθ(vUp ⟂ vForward,u·(u·v)(1-cosθ) 项为 0)
void rotateCamera(const GLfloat degrees) {
    constexpr GLfloat radPerDeg = 3.14159265F / 180.0F;
    const GLfloat theta = degrees * radPerDeg;
    const GLfloat c = std::cos(theta);
    const GLfloat s = std::sin(theta);
    const GLfloat fx = frameCamera.vForward[0];
    const GLfloat fy = frameCamera.vForward[1];
    const GLfloat fz = frameCamera.vForward[2];

    frameCamera.vForward[0] = fx * c + (frameCamera.vUp[1] * fz - frameCamera.vUp[2] * fy) * s;
    frameCamera.vForward[1] = fy * c + (frameCamera.vUp[2] * fx - frameCamera.vUp[0] * fz) * s;
    frameCamera.vForward[2] = fz * c + (frameCamera.vUp[0] * fy - frameCamera.vUp[1] * fx) * s;

    // 浮点累加会漂移,重新归一化保持单位长度(前进步长 0.1 才精确)
    const GLfloat len = std::sqrt(frameCamera.vForward[0] * frameCamera.vForward[0] +
                                  frameCamera.vForward[1] * frameCamera.vForward[1] +
                                  frameCamera.vForward[2] * frameCamera.vForward[2]);
    if (len > 0.0F) {
        frameCamera.vForward[0] /= len;
        frameCamera.vForward[1] /= len;
        frameCamera.vForward[2] /= len;
    }
}

void specialKeys(const int key, const int x, const int y){
    switch (key) {
        case GLUT_KEY_UP:
            frameCamera.vLocation[0] += frameCamera.vForward[0] * 0.1F; // 0
            frameCamera.vLocation[1] += frameCamera.vForward[1] * 0.1F; // 0
            frameCamera.vLocation[2] += frameCamera.vForward[2] * 0.1F; // 0.1
            break;
        case GLUT_KEY_DOWN:
            frameCamera.vLocation[0] -= frameCamera.vForward[0] * 0.1F; // 0    
            frameCamera.vLocation[1] -= frameCamera.vForward[1] * 0.1F; // 
            frameCamera.vLocation[2] -= frameCamera.vForward[2] * 0.1F; // -0.1
            break;
        case GLUT_KEY_LEFT:
            rotateCamera(5.0F);   // 向左转(正角度 = 绕 vUp 逆时针)
            break;
        case GLUT_KEY_RIGHT:
            rotateCamera(-5.0F);  // 向右转
            break;
        default:
            return; // 不处理其他按键
    }
    glutPostRedisplay();
}
void setFaceNormal(const GLfloat ax, const GLfloat ay, const GLfloat az,
                   const GLfloat bx, const GLfloat by, const GLfloat bz,
                   const GLfloat cx, const GLfloat cy, const GLfloat cz) {
    const GLfloat ux = bx - ax, uy = by - ay, uz = bz - az;
    const GLfloat vx = cx - ax, vy = cy - ay, vz = cz - az;
    GLfloat nx = uy * vz - uz * vy;
    GLfloat ny = uz * vx - ux * vz;
    GLfloat nz = ux * vy - uy * vx;
    const GLfloat len = std::sqrt(nx * nx + ny * ny + nz * nz);
    if (len > 0.0F) {
        nx /= len;
        ny /= len;
        nz /= len;
    }
    glNormal3f(nx, ny, nz);
}

/**
 * 计算平面投影阴影矩阵:把顶点沿光线方向投影到平面 planeEq 上
 *
 * planeEq : 平面方程 a·x + b·y + c·z + d = 0
 * lightPos: 与 glLightfv(GL_POSITION) 相同的齐次坐标
 *           w = 1 点光源(阴影有透视近大远小)
 *           w = 0 平行光/方向光(阳光,阴影是平行投影)
 * m       : 输出 16 元素列主序矩阵,可直接传给 glMultMatrixf
 *
 * 原理:顶点 p 沿光线方向的影子 s = p + t·L 满足 n·s + d = 0,
 *       解得 t = -(n·p + d)/(n·L),代回可得齐次形式
 *       s' = dot·p - (n·p)·L,其中 dot = n·L + d·Lw。
 *       这是 p 的线性(射影)变换,故可写成 4×4 矩阵:
 *       M[i][j] = dot·δ(i,j) - L[i]·n[j],按列主序展开。
 */
void makeShadowMatrix(GLfloat m[16], const GLfloat planeEq[4], const GLfloat lightPos[4]) {
    const GLfloat dot = planeEq[0] * lightPos[0] + planeEq[1] * lightPos[1] +
                        planeEq[2] * lightPos[2] + planeEq[3] * lightPos[3];

    m[0]  = dot - lightPos[0] * planeEq[0];   // 第 1 列(列主序)
    m[1]  =     - lightPos[1] * planeEq[0];
    m[2]  =     - lightPos[2] * planeEq[0];
    m[3]  =     - lightPos[3] * planeEq[0];
    m[4]  =     - lightPos[0] * planeEq[1];   // 第 2 列
    m[5]  = dot - lightPos[1] * planeEq[1];
    m[6]  =     - lightPos[2] * planeEq[1];
    m[7]  =     - lightPos[3] * planeEq[1];
    m[8]  =     - lightPos[0] * planeEq[2];   // 第 3 列
    m[9]  =     - lightPos[1] * planeEq[2];
    m[10] = dot - lightPos[2] * planeEq[2];
    m[11] =     - lightPos[3] * planeEq[2];
    m[12] =     - lightPos[0] * planeEq[3];   // 第 4 列
    m[13] =     - lightPos[1] * planeEq[3];
    m[14] =     - lightPos[2] * planeEq[3];
    m[15] = dot - lightPos[3] * planeEq[3];
}
void drawGround(){
    glColor3f(1.0F, 1.0F, 1.0F); // 绿色
    constexpr GLfloat y = -0.4F;
    constexpr GLfloat edge = 20.0F;
    constexpr GLfloat step = 1.0F;
    for (GLfloat i = -edge; i <= edge; i += step) {
        glBegin(GL_TRIANGLE_STRIP);
        glNormal3f(0.0F, 1.0F, 0.0F); // All Point up
        for (GLfloat j = edge; j >= -edge; j -= step) {
            glVertex3f(i, y, j);
            glVertex3f(i + step, y, j);
        }
        glEnd();
    }
}

/**
 * 物体→世界
 * 叉积有方向前面为右手法则,右向量 = Forward × Up
 * 右手法则:四指从前向上卷,拇指指向右手的右边,叉积的方向就是拇指的方向。
 */
void gltGetMatrixFromFrame(const GLFrame *pFrame, GLfloat m[16]) {
    GLfloat r[3];   // 右向量 X = Forward × Up
    r[0] = pFrame->vForward[1] * pFrame->vUp[2] - pFrame->vForward[2] * pFrame->vUp[1];
    r[1] = pFrame->vForward[2] * pFrame->vUp[0] - pFrame->vForward[0] * pFrame->vUp[2];
    r[2] = pFrame->vForward[0] * pFrame->vUp[1] - pFrame->vForward[1] * pFrame->vUp[0];

    // 列主序
    // 第3列取 -vForward:OpenGL 的 +Z 指向观察者背后
    m[0]=r[0]; m[4]=pFrame->vUp[0]; m[8] =-pFrame->vForward[0]; m[12]=pFrame->vLocation[0];
    m[1]=r[1]; m[5]=pFrame->vUp[1]; m[9] =-pFrame->vForward[1]; m[13]=pFrame->vLocation[1];
    m[2]=r[2]; m[6]=pFrame->vUp[2]; m[10]=-pFrame->vForward[2]; m[14]=pFrame->vLocation[2];
    m[3]=0.0F; m[7]=0.0F;           m[11]=0.0F;                 m[15]=1.0F;
}

/**
 * 视图矩阵 = 上面的逆:旋转转置 + 平移取负
 * 把世界挪到相机眼前
 */
void gltApplyCameraTransform(const GLFrame *pCamera) {
    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];

    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]);
}
void drawSpheres() {
    for (int i = 0; i < NUM_SPHERES; ++i) {
        glPushMatrix();
        GLfloat m[16];
        gltGetMatrixFromFrame(&spheres[i], m);
        glMultMatrixf(m);
        glutSolidSphere(0.3F, 17, 9);
        glPopMatrix();
    }
}
void drawTorus(const GLfloat majorRadius, const GLfloat minorRadius, const GLint numMajor, const GLint numMinor) {
    glPushMatrix();
    glTranslatef(0.0F, 0.1F, -2.5F);
    for (GLint i = 0; i < numMajor; ++i) {
        const GLfloat u0 = (GLfloat)i / numMajor * 2.0F * PI;
        const GLfloat u1 = (GLfloat)(i + 1) / numMajor * 2.0F * PI;
 
        glBegin(GL_TRIANGLE_STRIP);
        for (GLint j = 0; j <= numMinor; ++j) {
            GLfloat v = (GLfloat)j / numMinor * 2.0F * PI;
            GLfloat cosV = cosf(v), sinV = sinf(v);
 
            // 点1 (u0 经线)
            GLfloat cu0 = cosf(u0), su0 = sinf(u0);
            glNormal3f(cu0 * cosV, su0 * cosV, sinV);
            glVertex3f(cu0 * (majorRadius + minorRadius * cosV),
                       su0 * (majorRadius + minorRadius * cosV),
                       minorRadius * sinV);
 
            // 点2 (u1 经线)
            GLfloat cu1 = cosf(u1), su1 = sinf(u1);
            glNormal3f(cu1 * cosV, su1 * cosV, sinV);
            glVertex3f(cu1 * (majorRadius + minorRadius * cosV),
                       su1 * (majorRadius + minorRadius * cosV),
                       minorRadius * sinV);
        }
        glEnd();
    }
    glPopMatrix();
}
void drawBall(){
    glPushMatrix();
    glTranslatef(1.0F, 0.1F, -2.5F);
        glutSolidSphere(0.1F, 17, 9);
    glPopMatrix();
}
void renderScene(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
        gltApplyCameraTransform(&frameCamera);
        glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
        // 绘制地面
        glColor3f(0.60F, 0.40F, 0.10F);
        drawGround();

        // 阴影
        glDisable(GL_LIGHTING);
        glDisable(GL_DEPTH_TEST);
        glPushMatrix();
            glMultMatrixf(shadowMat);
            glColor3f(0.0F, 0.0F, 0.0F);
            drawSpheres();
            drawTorus(0.35, 0.15, 61, 37);
            drawBall();
        glPopMatrix();

        glEnable(GL_LIGHTING);
        glEnable(GL_DEPTH_TEST);
        // 球体、圆环和小球
        glColor3f(0.0F, 1.0F, 0.0F);
        drawSpheres();
        glColor3f(1.0F, 0.0F, 0.0F);
        drawTorus(0.35, 0.15, 61, 37);
        glColor3f(0.0F, 0.0F, 1.0F);
        drawBall();
        
    glPopMatrix();

    glutSwapBuffers();
}

void setupRc(){
    constexpr GLfloat fNoLight[] = { 0.0F, 0.0F, 0.0F, 0.0F };
    constexpr GLfloat fLowLight[] = { 0.25F, 0.25F, 0.25F, 1.0F };

    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glClearColor(0.25F, 0.25F, 0.25F, 1.0F);
    
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, fNoLight);
    glLightfv(GL_LIGHT0, GL_AMBIENT, fLowLight);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
    glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHTING);

    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glMaterialfv(GL_FRONT, GL_SPECULAR, specref);
    glMateriali(GL_FRONT, GL_SHININESS, 128);

    // 阴影
    constexpr GLfloat planeEq[4] = { 0.0F, 1.0F, 0.0F, 0.4F };
    makeShadowMatrix(shadowMat, planeEq, lightPos);

    // 初始化摄像机和球体帧
    gltInitFrame(&frameCamera);
    for (int i = 0; i < NUM_SPHERES; ++i) {
        gltInitFrame(&spheres[i]);
        // -20.0 到 19.9
        spheres[i].vLocation[0] = static_cast<GLfloat>((rand() % 400 - 200) * 0.1F);
        spheres[i].vLocation[1] = 0.0F;
        spheres[i].vLocation[2] = static_cast<GLfloat>((rand() % 400 - 200) * 0.1F);
    }
}

auto main(const int argc, const char** argv) -> int {
    glutInit(const_cast<int*>(&argc), const_cast<char**>(argv));
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
    glutInitWindowSize(800, 600);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    std::cout << "Main window ID: " << mainWindowId << std::endl;
    glutReshapeFunc(changeSize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeys);
    glutDisplayFunc(renderScene);
    setupRc();
    glutMainLoop();
    return 0;
}

实现这个是真的很难0.0

posted @ 2026-08-23 20:53  爱情丶眨眼而去  阅读(8)  评论(0)    收藏  举报