GLSL(OpenGL Shading Language)和GLM(OpenGL Mathematics Library)来实现OpenGL trackball功能的示例

以下是一个使用GLSL(OpenGL Shading Language)和GLM(OpenGL Mathematics Library)来实现OpenGL trackball功能的示例。这个示例将展示如何通过鼠标操作来旋转场景中的物体,就好像在操作一个虚拟的trackball一样。

1. 准备工作

  • 包含必要的头文件和库
    • 首先需要包含GLSL相关的头文件以及GLM库的头文件。GLM用于处理向量、矩阵等数学运算,使我们在OpenGL编程中更方便地进行3D变换操作。
    • 示例中还会用到GLUT(OpenGL Utility Toolkit)库来进行窗口管理和事件处理,所以也需要包含相关的GLUT头文件。
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>

// 窗口大小
const int WIDTH = 800;
const int HEIGHT = 600;

// GLSL顶点着色器代码
const char* vertexShaderSource =
    "#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\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"
    "}\n";

// GLSL片段着色器代码
const char* fragmentShaderSource =
    "#version 330 core\n"
    "out vec4 FragColor;\n"
    "void main()\n"
    "{\n"
    "    FragColor = vec4(1.0, 0.5, 0.2, 1.0);\n"
    "}\n";

在上述代码中:

  • 我们定义了顶点着色器和片段着色器的代码字符串。顶点着色器负责将顶点位置进行变换并传递给片段着色器,片段着色器则负责确定每个片段(像素)的颜色。
  • 这里的顶点着色器接受一个顶点位置输入 aPos,并通过与模型矩阵 model、视图矩阵 view 和投影矩阵 projection 相乘来计算最终的顶点位置 gl_Position
  • 片段着色器简单地将每个像素的颜色设置为 vec4(1.0, 0.5, 0.2, 1.0),即一个橙色。

2. 初始化OpenGL环境和着色器

  • 创建窗口并初始化OpenGL上下文
    • 使用GLFW库来创建窗口并初始化OpenGL上下文。这包括设置窗口的大小、标题等属性,以及初始化GLFW和GLEW(OpenGL Extension Wrangler Library)。
// 初始化GLFW
if (!glfwInit())
{
    std::cerr << "Failed to initialize GLFW" << std::endl;
    return -1;
}

// 设置GLFW窗口属性
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

// 创建窗口
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL Trackball with GLSL and GLM", nullptr, nullptr);
if (!window)
{
    std::cerr << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}

// 设置当前窗口上下文
glfwMakeCurrent(window);

// 初始化GLEW
if (glewInit()!= GLEW_OK)
{
    std::cerr << "Failed to initialize GLEW" << std::endl;
    glfwTerminate();
    return -1;
}

// 设置视口大小
glfwSetWindowSizeCallback(window, [](GLFWwindow* window, int width, int height) {
    glViewport(0, 0, width, height);
});
  • 编译和链接着色器程序
    • 使用 glCreateShaderglShaderSourceglCompileShader 等OpenGL函数来分别创建、设置源 代码并编译顶点着色器和片段着色器。然后使用 glCreateProgramglLinkProgram 来创建并链接着色器程序。
// 编译顶点着色器
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 << "Vertex shader compilation failed: " << infoLog << std::endl;
    return -1;
}

// 编译片段着色器
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 << "Fragment shader compilation failed: " << infoLog << std::endl;
    return -1;
}

// 创建并链接着色器程序
unsigned int 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 << "Shader program linking failed: " << infoLog << std::endl;
    return -1;
}

// 删除不再需要的单独的着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

3. 设置投影、视图和模型矩阵

  • 投影矩阵
    • 使用GLM库来创建投影矩阵,这里采用透视投影方式,设置视场角、宽高比、近裁剪面和远裁剪面等参数。
// 创建投影矩阵
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)WIDTH / (float)HEIGHT, 0.1f, 100.0f);
// 将投影矩阵传递给着色器
unsigned int projectionLoc = glGetUniformLocation(shaderProgram, "projection");
glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));
  • 视图矩阵
    • 视图矩阵用于确定观察点的位置和方向。这里简单地将观察点设置在原点正前方一定距离处,例如 (0, 0, 3),并朝向原点。
// 创建视图矩阵
glm::mat4 view = glm::lookAt(glm::vec3(0, 0, 3), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
// 将视图矩阵传递给着色器
unsigned int viewLoc = glGetUniformLocation(shaderProgram, "view");
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
  • 模型矩阵
    • 模型矩阵用于对物体进行变换,如旋转、平移、缩放等。最初将模型矩阵设置为单位矩阵,表示物体没有进行任何变换。
// 创建模型矩阵
glm::mat4 model = glm::mat4(1.0f);
// 将模型矩阵传递给着色器
unsigned int modelLoc = glGetUniformLocation(shaderProgram, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

4. 鼠标事件处理

  • 鼠标按下事件
    • 当鼠标按下时,记录鼠标的初始位置坐标,以便在鼠标移动时计算鼠标的移动量。
// 鼠标按下时的坐标
double mouseXInitial, mouseYInitial;

// 鼠标按下事件回调函数
void mousePressed(GLFWwindow* window, int button, int state, int x, int y) {
    if (button == GLFW_MOUSE_BUTTON_LEFT && state == GLFW_MOUSE_BUTTON_DOWN) {
        mouseXInitial = x;
        mouseYInitial = y;
    }
}

glfwSetMouseButtonCallback(window, mousePressed);
  • 鼠标移动事件
    • 根据鼠标的初始位置和当前位置计算出旋转轴和旋转角度,然后利用GLM库创建旋转矩阵,并将其与当前的模型矩阵相乘,更新模型矩阵以实现物体的旋转效果。
// 计算两点之间的距离
double distance(double x1, double y1, double x2, double y2) {
    return sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

// 鼠标移动事件回调函数
void mouseMoved(GLFWwindow* window, int x, int y) {
    double dx = x - mouseXInitial;
    double dy = y - mouseYInitial;

    // 计算旋转角度
    double angle = distance(mouseXInitial, mouseYInitial, x, y) * 0.01;

    // 计算旋转轴
    glm::vec3 axis = glm::vec3(dy, -dx, 0.0);

    // 规范化旋转轴向量
    axis = glm::normalize(axis);

    // 创建旋转矩阵
    glm::mat4 rotationMatrix = glm::rotate(glm::mat4(1.0f), (float)angle, axis);

    // 更新模型矩阵
    model = rotationMatrix * model;

    // 将更新后的模型矩阵传递给着色器
    unsigned int modelLoc = glGetUniformLocation(shaderProgram, "model");
    glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));

    // 重新渲染场景
    glfwPostRedraw(window);
}

glfwSetCursorPosCallback(window, mouseMoved);

5. 渲染场景

  • 清除颜色缓冲和深度缓冲,然后使用设置好的着色器程序绘制场景中的物体。这里以绘制一个简单的立方体为例。
// 绘制场景中的物体
void drawScene() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

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

    // 这里可以绘制具体的物体,比如一个立方体
    // 假设已经有定义好的立方体顶点数据等相关设置

    glfwSwapBuffers(window);
}

// 主函数
int main(int argc, char** argv) {
    // 初始化OpenGL环境、窗口等(前面已完成相关步骤)

    // 设置鼠标事件回调函数
    glfwSetMouseButtonCallback(window, mousePressed);
    glfwSetCursorPosCallback(window, mouseMoved);

    // 渲染循环
    while (!glfwTerminate()) {
        drawScene();
        glfwPollEvents();
    }

    return 0;
}

在上述示例中:

  • 通过GLSL编写了顶点着色器和片段着色器程序,用于对物体进行渲染和着色。
  • 利用GLM库方便地进行了矩阵运算,包括创建投影、视图和模型矩阵,以及根据鼠标移动计算旋转矩阵并更新模型矩阵。
  • 处理了鼠标按下和移动事件,实现了通过鼠标操作来旋转场景中的物体的trackball功能。
  • 最后通过渲染循环不断地更新和渲染场景,以展示物体的旋转效果。

请注意,上述示例中假设已经有定义好的立方体顶点数据等相关设置,实际应用中需要根据具体要绘制的物体进行相应的顶点数据准备和绘制操作。

posted @ 2024-11-09 18:33  MarsCactus  阅读(56)  评论(0)    收藏  举报