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

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

本章是颜色混合相关的内容

球体世界圆环倒影

#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>

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

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 lightPosMirror[] = { -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旋转角度

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;
    }
}


void specialKeys(const int key, const int x, const int y){
    switch (key) {
        case GLUT_KEY_UP:
            break;
        case GLUT_KEY_DOWN:
            break;
        case GLUT_KEY_LEFT:
            break;
        case GLUT_KEY_RIGHT:
            break;
        default:
            return; // 不处理其他按键
    }
    glutPostRedisplay();
}
void drawGround(){
    constexpr GLfloat y = 0.0F;
    constexpr GLfloat edge = 20.0F;
    constexpr GLfloat step = 0.5F;
    GLint count = 0;
    glShadeModel(GL_FLAT);
    for (GLfloat i = -edge, color = 0.0F; 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) {
            color = (count % 2 == 0) ? 1.0F : 0.0F;
            glColor4f(color, color, color, 0.5F);
            glVertex3f(i, y, j);
            glVertex3f(i + step, y, j);
            count ++ ;
        }
        glEnd();
    }
    glShadeModel(GL_SMOOTH);
}

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 drawWorld(){
    glColor3f(1.0F, 0.0F, 0.0F);
    glPushMatrix();
        glTranslatef(0.0F, 0.5F, -3.5F);
        glPushMatrix();
            // glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
            glTranslatef(1.0F, 0.0F, 0.0F);
            glutSolidSphere(0.1F, 17, 9);
        glPopMatrix();
        drawTorus(0.35F, 0.15F, 40, 20);
    glPopMatrix();
}
void renderScene(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
        glLightfv(GL_LIGHT0, GL_POSITION, lightPosMirror);
        glTranslatef(0.0F, -0.4F, 0.0F);
        glPushMatrix();
            glFrontFace(GL_CW); // 绕 Y 轴镜像,顺时针为正面
            glScalef(1.0F, -1.0F, 1.0F); // XZ 平面 镜像
            drawWorld();
            glFrontFace(GL_CCW); // 恢复默认
        glPopMatrix();
        
        glDisable(GL_LIGHTING);
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        drawGround();
        glDisable(GL_BLEND);
        glEnable(GL_LIGHTING);
        glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
        drawWorld();

    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);
}

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;
}

混合抗锯齿

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

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

// 常量
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 GLint mainWindowId = 0; // 主窗口ID

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();
    // gluOrtho2D 属于 GLU 库,Windows/Linux 上需额外链接 glu32/libGLU;
    // 改用核心函数 glOrtho(gluOrtho2D(l,r,b,t) 等价于 glOrtho(l,r,b,t,-1.0,1.0)),
    // 即可消除 GLU 依赖,跨平台通用。
    glOrtho(0.0, static_cast<GLdouble>(width), 0.0, static_cast<GLdouble>(height), -1.0, 1.0);
    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':
            break;
        case 'd':
        case 'D':
            break;
        default:
            break;
    }
}


void specialKeys(const int key, const int x, const int y){
    switch (key) {
        case GLUT_KEY_UP:
            break;
        case GLUT_KEY_DOWN:
            break;
        case GLUT_KEY_LEFT:
            break;
        case GLUT_KEY_RIGHT:
            break;
        default:
            return; // 不处理其他按键
    }
}

void renderScene(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    // 绘制moon (月亮)
    glBegin(GL_TRIANGLE_FAN);
        glVertex2f(400.0F, 300.0F); // 月亮中心
        constexpr GLfloat radius = 50.0F;
        constexpr GLint numSegments = 100;
        for (GLint i = 0; i <= numSegments; ++i) {
            const GLfloat angle = 2.0F * PI * static_cast<GLfloat>(i) / static_cast<GLfloat>(numSegments);
            glVertex2f(400.0F + radius * cosf(angle), 300.0F + radius * sinf(angle));
        }
    glEnd();

    // 绘制star
    glPointSize(5.5F);
    glBegin(GL_POINTS);
        glVertex2f(100.0F, 500.0F);
        glVertex2f(150.0F, 550.0F);
        glVertex2f(200.0F, 400.0F);
        glVertex2f(250.0F, 500.0F);
        glVertex2f(300.0F, 450.0F);
        glVertex2f(350.0F, 500.0F);
        glVertex2f(500.0F, 550.0F);
        glVertex2f(550.0F, 500.0F);
        glVertex2f(400.0F, 500.0F);
        glVertex2f(450.0F, 500.0F);
        glVertex2f(600.0F, 350.0F);
        glVertex2f(650.0F, 400.0F);
        glVertex2f(700.0F, 500.0F);
        glVertex2f(750.0F, 450.0F);
        glVertex2f(800.0F, 400.0F);
    glEnd();

        // 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, 125.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 setupRc(){
    glClearColor(0.0F, 0.0F, 0.0F, 1.0F);
}
void processMenu(const int option) {
    switch (option) {
        case 1: // Antialiased Rendering
            glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
            glEnable(GL_BLEND);
            glEnable(GL_POINT_SMOOTH);
            glEnable(GL_LINE_SMOOTH);
            glEnable(GL_POLYGON_SMOOTH);
            glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
            glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
            glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
            break;
        case 2: // Normal Rendering
            glDisable(GL_BLEND);
            glDisable(GL_POINT_SMOOTH);
            glDisable(GL_LINE_SMOOTH);
            glDisable(GL_POLYGON_SMOOTH);
            break;
        default:
            break;
    }
    std::cout << std::format("Menu option selected: {}, Main window ID: {}", option, mainWindowId) << "\n";
    glutSetWindow(mainWindowId);
    glutPostRedisplay(); // 请求重新绘制窗口
}
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);
    mainWindowId = glutCreateWindow("OpenGL Window");
    std::cout << "Main window ID: " << mainWindowId << std::endl;
    glutReshapeFunc(changeSize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeys);
    glutDisplayFunc(renderScene);
    
    glutCreateMenu(processMenu);
	glutAddMenuEntry("Antialiased Rendering",1);
	glutAddMenuEntry("Normal Rendering",2);
	glutAttachMenu(GLUT_RIGHT_BUTTON);
    setupRc();
    glutMainLoop();
    return 0;
}

windows可以看到效果,mac看不到。

球体世界抗锯齿

#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, 48, 32);
        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 };
 
    glEnable(GL_MULTISAMPLE);
    // 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 | GLUT_MULTISAMPLE);
    glutInitWindowSize(800, 600);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    std::cout << "Main window ID: " << mainWindowId << std::endl;
    // 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
    GLint sampleBuffers = 0, samples = 0;
    glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
    glGetIntegerv(GL_SAMPLES, &samples);
    std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
        reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
    glutReshapeFunc(changeSize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeys);
    glutDisplayFunc(renderScene);
    setupRc();
    glutMainLoop();
    return 0;
}

球体世界雾化

#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, 48, 32);
        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 };
 
    glEnable(GL_MULTISAMPLE);
    // 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);

    // 开启雾化
    glEnable(GL_FOG);
    glFogfv(GL_FOG_COLOR, fLowLight);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogf(GL_FOG_START, 5.0F);
    glFogf(GL_FOG_END, 30.0F);
 
    // 阴影
    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 | GLUT_MULTISAMPLE);
    glutInitWindowSize(800, 600);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    std::cout << "Main window ID: " << mainWindowId << std::endl;
    // 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
    GLint sampleBuffers = 0, samples = 0;
    glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
    glGetIntegerv(GL_SAMPLES, &samples);
    std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
        reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
    glutReshapeFunc(changeSize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeys);
    glutDisplayFunc(renderScene);
    setupRc();
    glutMainLoop();
    return 0;
}

移动拖尾球体

#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:
            break;
        case GLUT_KEY_DOWN:
            break;
        case GLUT_KEY_LEFT:
            break;
        case GLUT_KEY_RIGHT:
            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];
}
 
/**
 * 物体→世界
 * 叉积有方向前面为右手法则,右向量 = 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 drawGround(){
    constexpr GLfloat y = 0.0F;
    constexpr GLfloat edge = 20.0F;
    constexpr GLfloat step = 0.5F;
    GLint count = 0;
    glShadeModel(GL_FLAT);
    for (GLfloat i = -edge, color = 0.0F; 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) {
            color = (count % 2 == 0) ? 1.0F : 0.0F;
            glColor4f(color, color, color, 0.5F);
            glVertex3f(i, y, j);
            glVertex3f(i + step, y, j);
            count ++ ;
        }
        glEnd();
    }
    glShadeModel(GL_SMOOTH);
}
void drawGeometry(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glPushMatrix();
        glTranslatef(0.0F, -0.4F, 0.0F);
        drawGround();
        glColor3f(1.0F, 0.0F, 0.0F);
        glTranslatef(0.0F, 0.5F, -3.5F);
        glRotatef(-(yRot * 2.0F), 0.0F, 1.0F, 0.0F);
        glTranslatef(1.0F, 0.0F, 0.0F);
        glutSolidSphere(0.1F, 17, 9);
    glPopMatrix();
}
void renderScene(){
    GLfloat fPass = 0.0F;
    constexpr GLfloat fPasses = 10.0F;
    yRot = 35.0F;
    for (fPass = 0.0F; fPass < fPasses; fPass += 1.0F) {
        yRot += 0.75F; // 1.0F / (fPass + 1.0F)
        drawGeometry();
        if(fPass == 0.0F){
            glAccum(GL_LOAD, 0.5F);
        } else {
            glAccum(GL_ACCUM, 0.5F * (1.0F / fPasses));
        }
    }
    glAccum(GL_RETURN, 1.0F);
    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);
    
    glCullFace(GL_BACK);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glEnable(GL_DEPTH_TEST);
 
    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);
}
 
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 | GLUT_ACCUM);
    glutInitWindowSize(800, 600);
    const int mainWindowId = glutCreateWindow("OpenGL Window");
    std::cout << "Main window ID: " << mainWindowId << std::endl;
    // 验证 MSAA 是否真正生效(macOS 的 GLUT 拿不到多重采样像素格式时会静默降级)
    GLint sampleBuffers = 0, samples = 0;
    glGetIntegerv(GL_SAMPLE_BUFFERS, &sampleBuffers);
    glGetIntegerv(GL_SAMPLES, &samples);
    std::cout << std::format("Renderer: {}, sample buffers: {}, samples: {}",
        reinterpret_cast<const char*>(glGetString(GL_RENDERER)), sampleBuffers, samples) << std::endl;
    glutReshapeFunc(changeSize);
    glutKeyboardFunc(keyboard);
    glutSpecialFunc(specialKeys);
    glutDisplayFunc(renderScene);
    setupRc();
    glutMainLoop();
    return 0;
}
posted @ 2026-09-05 16:43  爱情丶眨眼而去  阅读(13)  评论(0)    收藏  举报