OpenGL使用C++实现相机模块功能

1. 功能介绍

1.1 相机的基础概念

概念名称 作用说明
相机位置 相机所在的位置,表示站在哪里进行观察
目标点 相机看向的目标位置,表示看向谁、朝向谁
上方向向量 相机的头部朝向,用于表示相机头顶朝向哪个方向,控制相机倾斜 / 翻转

目标点 为原点建立坐标系,其参照图如下:

1.2 球坐标与笛卡尔坐标的转换

向量的概念请在基础课程《线性代数》中进行基础的理解与学习

参数名称 作用说明
前方向向量 你眼睛看出去的 "视线方向"------ 比如你盯着墙上的钟,从你眼球(相机位置)到钟(目标点)的那根 "无形的线",就是前方向向量。
偏航角(Yaw) 你现在盯着墙上的钟(目标点),向右走动,这时候为了保证看向钟,你会摆头向左看,这时所产生的角度为偏航角
俯仰角(Pitch) 你还是盯着墙上的钟(目标点),向上飞起,这时候为了保证看向钟,你会低头向下看,这时所产生的角度为俯仰角

目标点 建立坐标系,其参照图如下

1.3 投影矩阵

投影矩阵(Projection Matrix)是 3D 渲染中将 3D 空间坐标 "挤压" 到 2D 屏幕上的关键矩阵,它解决了 "如何把立体的 3D 场景变成平面图像" 的问题。在glm库中的perspective 函数用于创建透视投影矩阵,模拟人眼观察世界的 "近大远小" 效果(比如远处的物体看起来更小)

  1. 垂直视野角度(Field of View, FOV)
    含义:相机 "视野的上下角度范围",类似人眼的 "视野开阔程度"。
    效果:角度越大(比如 90°),视野越宽,能看到的范围越多,但物体看起来更小;角度越小(比如 30°),视野越窄,物体看起来更大更 "近"。
  2. 宽高比(Aspect Ratio)
    含义:屏幕宽度与高度的比例(比如 800x600 的屏幕,宽高比 = 4:3)。
    作用:确保 3D 物体在屏幕上不会被 "拉伸变形"。如果宽高比设置错误,例如圆形会被拉成椭圆。
  3. 近平面(Near Plane)
    含义:相机能看到的 "最近距离"。距离小于 0.1 的物体(比如贴在镜头上的物体)会被裁剪掉,不显示。
    作用:避免近距离物体遮挡视线,同时提升渲染效率(不用渲染过近的物体)。
  4. 远平面(Far Plane)
    含义:相机能看到的 "最远距离"。距离大于 100 的物体(比如极远处的山)会被裁剪掉,不显示。
    作用:避免渲染过远的物体(人眼也看不清),节省计算资源。

2. 基础环境

  1. VSCode + XMake搭建OpenGL开发环境

  2. 下载 glm库

    GLM(Generalized Linear Models)库是一个用于广义线性模型的C++数学库,主要设计用于图形渲染和游戏开发中的向量、矩阵运算。它提供与GLSL(OpenGL Shading Language)相似的语法和功能,便于开发者实现高性能的数学计算。

3. 项目结构

前置项目:OpenGL使用C++ 绘制三角形,xmake配置不变

  1. 将glm库中的glm文件夹拷贝到include文件夹下
  2. 补充glcamera.h和glcamera.cpp文件,用以实现相机模块功能
bash 复制代码
opengl-xmake-demo/
├─ include/                # 头文件目录
│  ├─ ***
│  ├─ glm/                 # glm库
│  ├─ glcamera.h           # 相机模块
│  └─ glwindow.h           # OpenGL核心功能模块
├─ src/
│  ├─ ***
│  ├─ glcamera.cpp           # 相机模块
│  ├─ glwindow.cpp         # OpenGL核心功能模块
│  └─ main.cpp             # 主程序代码(测试代码见下文)
└─ xmake.lua               # XMake 配置文件(核心)

4. 源代码实现

4.1 相机模块

glcamera.h

cpp 复制代码
#ifndef GLCAMERA_H
#define GLCAMERA_H

#include <glm/glm.hpp>
#include <GLFW/glfw3.h>

// 相机类:处理视角旋转、缩放和矩阵计算
class GLCamera
{
public:
    // 构造函数:初始化相机参数(窗口宽高用于投影矩阵)
    GLCamera(int windowWidth, int windowHeight,
             const glm::vec3 &target = glm::vec3(0.0f, 0.0f, 1.0f),
             float initialDistance = 3.0f);
    // 析构函数
    ~GLCamera();

public:
    // 根据角度更新相机前方向向量
    void updateCameraVectors();

    // 更新视图矩阵(内部调用,也可外部强制更新)
    void updateViewMatrix();

    // 鼠标按键回调(处理右键按下/释放)
    void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods);

    // 鼠标移动回调(处理右键拖动旋转)
    void mouseMotionCallback(GLFWwindow *window, double xpos, double ypos);

    // 鼠标滚轮回调(处理缩放)
    void scrollCallback(double xoffset, double yoffset);

    // 窗口大小变化时更新投影矩阵
    void resize(int width, int height);

    // 获取视图矩阵
    const glm::mat4 &getViewMatrix() const { return view; }

    // 获取投影矩阵
    const glm::mat4 &getProjectionMatrix() const { return projection; }

private:
    // 相机属性
    glm::vec3 cameraPos;    // 相机位置
    glm::vec3 cameraTarget; // 目标点(看向的点)
    glm::vec3 cameraUp;     // 上方向向量
    glm::vec3 cameraFront;  // 前方向向量(指向目标点)

    // 视角控制参数
    float yaw;         // 偏航角(左右旋转,初始-90°指向Z轴负方向)
    float pitch;       // 俯仰角(上下旋转)
    float distance;    // 相机到目标点的距离
    float sensitivity; // 鼠标灵敏度
    float zoomSpeed;   // 缩放速度

    // 鼠标状态
    bool isRightMouseDown; // 右键是否按下
    double lastX, lastY;   // 上一帧鼠标位置

    // 矩阵
    glm::mat4 view;       // 视图矩阵
    glm::mat4 projection; // 投影矩阵
};

#endif // GLCAMERA_H

glcamera.cpp

cpp 复制代码
#include "glcamera.h"
#include <iostream>
#include <glm/gtc/matrix_transform.hpp>
GLCamera::GLCamera(int windowWidth, int windowHeight, const glm::vec3 &target, float initialDistance)
    : cameraTarget(target), distance(initialDistance),
      yaw(0.0f), pitch(0.0f), sensitivity(0.1f), zoomSpeed(0.2f),
      isRightMouseDown(false), lastX(windowWidth / 2.0f), lastY(windowHeight / 2.0f)
{
    // 初始化上方向为Y轴正方向
    cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);

    // 计算初始相机位置
    updateCameraVectors();

    // 初始化视图矩阵
    updateViewMatrix();

    // 初始化投影矩阵
    resize(windowWidth, windowHeight);
}

GLCamera::~GLCamera()
{
}

// 根据偏航角和俯仰角更新相机前方向向量
void GLCamera::updateCameraVectors()
{
    glm::vec3 front;
    front.x = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = -cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    // 浮点计算会存在误差,所以front的平方和结果不一定为1
    // 将前方向向量(front)"归一化",确保它的长度始终为 1(单位向量)
    cameraFront = glm::normalize(front);

    // 更新相机位置
    cameraPos = cameraTarget - cameraFront * distance;
}

// 更新视图矩阵(相机位置 = 目标点 - 前方向 * 距离)
void GLCamera::updateViewMatrix()
{
    cameraPos = cameraTarget - cameraFront * distance;
    view = glm::lookAt(cameraPos, cameraTarget, cameraUp);
}

// 鼠标按键事件:记录右键状态和初始位置
void GLCamera::mouseButtonCallback(GLFWwindow *window, int button, int action, int mods)
{
    if (button == GLFW_MOUSE_BUTTON_RIGHT)
    {
        if (action == GLFW_PRESS)
        {
            isRightMouseDown = true;
            glfwGetCursorPos(window, &lastX, &lastY); // 记录按下时的鼠标位置
        }
        else if (action == GLFW_RELEASE)
        {
            isRightMouseDown = false;
        }
    }
}

// 鼠标移动事件:右键拖动时旋转视角
void GLCamera::mouseMotionCallback(GLFWwindow *window, double xpos, double ypos)
{
    // 仅在右键按下时处理
    if (!isRightMouseDown)
        return;

    // 计算鼠标位移(当前位置 - 上一帧位置)
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // Y轴反向(上移为正)
    lastX = xpos;
    lastY = ypos;

    // 应用灵敏度
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    // 更新角度
    yaw += xoffset;   // 偏航角(左右旋转)
    pitch += yoffset; // 俯仰角(上下旋转)

    // 限制俯仰角(-89° ~ 89°,避免相机翻转)
    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;

    // 更新方向向量和视图矩阵
    updateCameraVectors();
    updateViewMatrix();
}

// 鼠标滚轮事件:缩放视角(调整相机到目标点的距离)
void GLCamera::scrollCallback(double xoffset, double yoffset)
{
    distance -= (float)yoffset * zoomSpeed; // 滚轮Y偏移控制距离
    if (distance < 0.5f)
        distance = 0.5f; // 限制最小距离(避免穿过模型)
    updateViewMatrix();  // 更新视图矩阵
}

// 窗口大小变化时更新投影矩阵(保持宽高比)
void GLCamera::resize(int width, int height)
{
    projection = glm::perspective(
        glm::radians(45.0f),   // 1. 垂直视野角度(FOV)
        (float)width / height, // 2. 宽高比(屏幕宽度/高度)
        0.1f,                  // 3. 近平面(最近可见距离)
        100.0f);               // 4. 远平面(最远可见距离)
}

4.2 坐标轴与三棱锥

glwindow.h

cpp 复制代码
#ifndef GLWINDOW_H
#define GLWINDOW_H

#include <iostream>
#include <string>
#include <GLFW/glfw3.h>

#include "glcamera.h"

class GLWindow
{
public:
    GLWindow(int w, int h, const std::string &title);
    ~GLWindow();

    void render();
    void update();

    bool shouldClose();
    void processInput();

private:
    bool initGLFW();
    bool initGLAD();
    void initShaders();
    void initBuffers();

    // 窗口大小和鼠标事件回调函数(静态成员函数)
    static void framebufferSizeCallback(GLFWwindow *window, int width, int height);
    static void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods);
    static void mouseMotionCallback(GLFWwindow *window, double xpos, double ypos);
    static void scrollCallback(GLFWwindow *window, double xoffset, double yoffset);

private:
    // 窗口相关信息
    GLFWwindow *window;
    int width;
    int height;
    std::string title;

    // 渲染相关
    unsigned int shaderProgram;
    
    // 坐标轴
    unsigned int coordVAO, coordVBO;
    // 三棱锥
    unsigned int pyramidVAO, pyramidVBO;

    // 相机对象
    GLCamera camera;
};

#endif // GLWINDOW_H

glwindow.cpp

cpp 复制代码
// glad必须在glwindow.h之前包含
#include <glad/glad.h>
#include "glwindow.h"

GLWindow::GLWindow(int w, int h, const std::string &title)
    : width(w), height(h), title(title), camera(w, h)
{
    if (!initGLFW())
    {
        throw std::runtime_error("GLFW初始化失败!");
    }
    if (!initGLAD())
    {
        throw std::runtime_error("GLAD初始化失败!");
    }
    initShaders();
    initBuffers();
}

GLWindow::~GLWindow()
{
    // 释放VAO、VBO和着色器程序
    glDeleteVertexArrays(1, &coordVAO);
    glDeleteBuffers(1, &coordVBO);

    glDeleteVertexArrays(1, &pyramidVAO);
    glDeleteBuffers(1, &pyramidVBO);

    glDeleteProgram(shaderProgram);
    // 销毁窗口并终止GLFW
    glfwDestroyWindow(window);
    glfwTerminate();
}

/* 渲染函数 */
void GLWindow::render()
{
    // 清空屏幕
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 使用着色器程序
    glUseProgram(shaderProgram);

    // 获取相机的视图矩阵和投影矩阵
    const glm::mat4 &view = camera.getViewMatrix();
    const glm::mat4 &projection = camera.getProjectionMatrix();
    glm::mat4 model = glm::mat4(1.0f); // 模型矩阵(单位矩阵)

    // 传递矩阵给着色器
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "model"), 1, GL_FALSE, &model[0][0]);
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "view"), 1, GL_FALSE, &view[0][0]);
    glUniformMatrix4fv(glGetUniformLocation(shaderProgram, "projection"), 1, GL_FALSE, &projection[0][0]);

    // -------------------------- 绘制XYZ坐标系 --------------------------
    glBindVertexArray(coordVAO);
    glDrawArrays(GL_LINES, 0, 6); // 6个顶点,绘制3条线段(GL_LINES模式:每2个顶点一条线)

    // -------------------------- 绘制三棱锥 --------------------------
    glBindVertexArray(pyramidVAO);
    glDrawArrays(GL_TRIANGLES, 0, 18); // 18个顶点,绘制6个三角形(每个面2个三角形)
}

/* 更新函数 */
void GLWindow::update()
{
    glfwSwapBuffers(window);
    glfwPollEvents();
}

/* 检查窗口是否应该关闭 */
bool GLWindow::shouldClose()
{
    return glfwWindowShouldClose(window);
}

/* 处理输入 */
void GLWindow::processInput()
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, true);
    }
}

/* 初始化GLFW */
bool GLWindow::initGLFW()
{
    // 初始化GLFW
    if (!glfwInit())
    {
        std::cerr << "GLFW初始化失败!" << std::endl;
        return false;
    }

    // 设置OpenGL版本和配置文件
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // 创建窗口
    window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
    if (!window)
    {
        std::cerr << "窗口创建失败!" << std::endl;
        glfwTerminate();
        return false;
    }
    // 绑定窗口上下文将当前GLWindow对象指针存入窗口用户数据(供回调使用)
    glfwMakeContextCurrent(window);
    glfwSetWindowUserPointer(window, this);

    // 设置事件回调函数
    glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);
    glfwSetMouseButtonCallback(window, mouseButtonCallback);
    glfwSetCursorPosCallback(window, mouseMotionCallback);
    glfwSetScrollCallback(window, scrollCallback);

    return true;
}

/* 初始化GLAD*/
bool GLWindow::initGLAD()
{
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cerr << "Glad加载失败!" << std::endl;
        return false;
    }
    // 设置视口大小(初始窗口大小)
    glViewport(0, 0, width, height);
    return true;
}

/* 初始化着色器程序 */
void GLWindow::initShaders()
{
    glEnable(GL_DEPTH_TEST);
    // 顶点着色器源码
    const char *vertexShaderSource = "#version 330 core\n"
                                     "layout (location = 0) in vec3 aPos;\n"
                                     "layout (location = 1) in vec3 aColor;\n"
                                     "out vec3 ourColor;\n"
                                     "uniform mat4 model;\n"
                                     "uniform mat4 view;\n"
                                     "uniform mat4 projection;\n"
                                     "void main()\n"
                                     "{\n"
                                     "   gl_Position = projection * view * model * vec4(aPos, 1.0);\n"
                                     "   ourColor = aColor;\n"
                                     "}\0";

    // 片段着色器源码
    const char *fragmentShaderSource = "#version 330 core\n"
                                       "in vec3 ourColor;\n"
                                       "out vec4 FragColor;\n"
                                       "void main()\n"
                                       "{\n"
                                       "   FragColor = vec4(ourColor, 1.0);\n"
                                       "}\0";
    // 编译顶点着色器
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
    glCompileShader(vertexShader);

    // 检查顶点着色器错误
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);
        std::cerr << "顶点着色器编译失败:\n"
                  << infoLog << std::endl;
    }

    // 编译片段着色器
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
    glCompileShader(fragmentShader);

    // 检查片段着色器错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);
        std::cerr << "片段着色器编译失败:\n"
                  << infoLog << std::endl;
    }

    // 链接着色器程序
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    // 检查链接错误
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);
        std::cerr << "着色器程序链接失败:\n"
                  << infoLog << std::endl;
    }

    // 删除临时着色器对象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

/* 初始化缓冲区对象 */
void GLWindow::initBuffers()
{
    // 格式:[位置(x,y,z), 颜色(r,g,b)],3条轴(X红、Y绿、Z蓝),每条轴2个顶点
    float coordinateVertices[] = {
        // X轴(红):原点(0,0,0) → (20,0,0)
        0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        20.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        // Y轴(绿):原点(0,0,0) → (0,20,0)
        0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
        0.0f, 20.0f, 0.0f, 0.0f, 1.0f, 0.0f,
        // Z轴(蓝):原点(0,0,0) → (0,0,20)
        0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 20.0f, 0.0f, 0.0f, 1.0f};

    // 创建VAO/VBO(坐标系)
    glGenVertexArrays(1, &coordVAO);
    glGenBuffers(1, &coordVBO);
    glBindVertexArray(coordVAO);
    glBindBuffer(GL_ARRAY_BUFFER, coordVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(coordinateVertices), coordinateVertices, GL_STATIC_DRAW);
    // 位置属性(location=0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);
    // 颜色属性(location=1)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 顶点数据(位置+颜色)
    float pyramidVertices[] = {
        // -------------------------- 面1:底面(z=0平面,三角形(0,0,0)-(1,0,0)-(0,1,0),黄色)
        0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // 顶点O(0,0,0)
        1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, // 顶点A(1,0,0)
        0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, // 顶点B(0,1,0)

        // -------------------------- 面2:侧面(三角形(0,0,0)-(1,0,0)-(0,0,1),红色)
        0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 顶点O(0,0,0)
        1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 顶点A(1,0,0)
        0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, // 顶点C(0,0,1)

        // -------------------------- 面3:侧面(三角形(0,0,0)-(0,1,0)-(0,0,1),绿色)
        0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 顶点O(0,0,0)
        0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // 顶点B(0,1,0)
        0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 顶点C(0,0,1)

        // -------------------------- 面4:侧面(三角形(1,0,0)-(0,1,0)-(0,0,1),蓝色)
        1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 顶点A(1,0,0)
        0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 顶点B(0,1,0)
        0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f  // 顶点C(0,0,1)
    };

    // 生成VAO和VBO
    glGenVertexArrays(1, &pyramidVAO);
    glGenBuffers(1, &pyramidVBO);

    // 绑定VAO(必须先绑定VAO才能配置顶点属性)
    glBindVertexArray(pyramidVAO);

    // 绑定VBO并复制数据到GPU
    glBindBuffer(GL_ARRAY_BUFFER, pyramidVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(pyramidVertices), pyramidVertices, GL_STATIC_DRAW);

    // 配置位置属性(location=0)
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)0);
    glEnableVertexAttribArray(0);

    // 配置颜色属性(location=1)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void *)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // 解绑缓冲(可选,避免误操作)
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

// 窗口大小回调:转发给相机更新投影矩阵
void GLWindow::framebufferSizeCallback(GLFWwindow *window, int width, int height)
{
    // 从窗口用户数据中获取GLWindow对象指针
    GLWindow *glWindow = static_cast<GLWindow *>(glfwGetWindowUserPointer(window));
    if (glWindow)
    {
        // 更新窗口大小并设置视口
        glWindow->width = width;
        glWindow->height = height;
        glViewport(0, 0, width, height);
        glWindow->camera.resize(width, height); // 更新相机投影矩阵
    }
}
// 鼠标按键回调:转发给相机
void GLWindow::mouseButtonCallback(GLFWwindow *window, int button, int action, int mods)
{
    GLWindow *glWindow = static_cast<GLWindow *>(glfwGetWindowUserPointer(window));
    if (glWindow)
    {
        glWindow->camera.mouseButtonCallback(window, button, action, mods);
    }
}

// 鼠标移动回调:转发给相机
void GLWindow::mouseMotionCallback(GLFWwindow *window, double xpos, double ypos)
{
    GLWindow *glWindow = static_cast<GLWindow *>(glfwGetWindowUserPointer(window));
    if (glWindow)
    {
        glWindow->camera.mouseMotionCallback(window, xpos, ypos);
    }
}

// 鼠标滚轮回调:转发给相机
void GLWindow::scrollCallback(GLFWwindow *window, double xoffset, double yoffset)
{
    GLWindow *glWindow = static_cast<GLWindow *>(glfwGetWindowUserPointer(window));
    if (glWindow)
    {
        glWindow->camera.scrollCallback(xoffset, yoffset);
    }
}

5. 运行效果图

bash 复制代码
xmake run
相关推荐
_dindong7 小时前
牛客101:二叉树
数据结构·c++·笔记·学习·算法
人邮异步社区8 小时前
推荐几本学习计算机语言的书
java·c语言·c++·python·学习·golang
ha204289419411 小时前
Linux操作系统学习之---线程池
linux·c++·学习
A-code11 小时前
C/C++ 中 void* 深度解析:从概念到实战
c语言·开发语言·c++·经验分享·嵌入式
玖笙&14 小时前
✨WPF编程进阶【6.1】:图形原则(附源码)
c++·c#·wpf·visual studio
s砚山s14 小时前
代码随想录刷题——二叉树篇(一)
c++·算法·leetcode
AC是你的谎言15 小时前
HTTP和HTTPS
linux·网络·c++·网络协议·学习·http·https
ZIM学编程16 小时前
「学长有话说」作为一个大三学长,我想对大一计算机专业学生说这些!
java·c语言·数据结构·c++·python·学习·php
代码AC不AC16 小时前
【C++】哈希表封装实现 unordered_map 和 unordered_set
c++·unordered_map·unordered_set·哈希表封装