OpenGl实战笔记(3)基于qt5.15.2+mingw64+opengl实现光照变化效果

发布于:2025-05-08 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、作用原理

1、作用
增强真实感:通过明暗变化模拟立体和材质。
体现空间感:让物体不再“平面”,更具三维效果。
表现材质特性:模拟金属、塑料等不同表面的反光。
实现动态光影:支持移动光源、阴影、高光等效果。
2、原理
OpenGL 光照效果基于 光与表面交互的数学模型,常见为 冯氏模型(Phong Model),包括:
环境光 Ambient 整体照亮,无方向,用于模拟环境反射光。
漫反射 Diffuse 与法线夹角有关,光越垂直表面越亮。
高光 Specular 反射光靠近视角方向时产生亮点,表现光滑表面反光。
3、常见光源类型
平行光:有方向,无位置(如太阳)。
点光源:有位置,向各方向发光(如灯泡)。
聚光灯:有方向和角度限制(如手电筒)。

二、实现效果
在这里插入图片描述
三、参考代码

#pragma once

// 渲染模块接口定义(用于模块化设计)
#include "RenderModuleInterface.h"

// Qt OpenGL 所需的类
#include <QOpenGLFunctions>              // 提供所有 OpenGL ES 2.0 函数
#include <QOpenGLShaderProgram>          // 管理顶点与片元着色器
#include <QOpenGLBuffer>                 // 管理 VBO(顶点缓冲对象)
#include <QOpenGLVertexArrayObject>      // 管理 VAO(顶点数组对象)
#include <QObject>                       // 支持 Qt 的信号槽机制

// 光照渲染类,继承自 QObject(用于信号槽)
// 同时实现渲染接口 RenderModuleInterface,并继承 OpenGL 函数支持
class LightingRenderer : public QObject,
                         public RenderModuleInterface,
                         protected QOpenGLFunctions
{
public:
    // 初始化 OpenGL 状态、着色器、顶点数据等
    void initialize() override;

    // 视口大小改变时调用,设置 OpenGL 视口
    void resize(int w, int h) override;

    // 渲染主函数,绘制一帧图像
    void render() override;

public slots:
    // 每帧更新函数,用于驱动动画等逻辑
    void onFrameUpdate();

private:
    QOpenGLShaderProgram shader;         // 着色器程序对象(包含顶点 + 片元着色器)
    QOpenGLBuffer vbo;                   // 顶点缓冲对象,用于存储顶点数据
    QOpenGLVertexArrayObject vao;        // 顶点数组对象,保存顶点属性配置
    int width = 0, height = 0;           // 当前窗口宽高,用于设置视口
    bool isInitialized = false;          // 标志位,确保只初始化一次
    float t = 0.0f;                      // 用于控制动画变化的时间参数(如光源移动等)
};
#include "LightingRenderer.h"
#include <QMatrix4x4>
#include <QVector3D>
#include <QtMath>

// 顶点着色器:传递位置、法线,并计算世界坐标和变换后的法线
static const char* lightingVert = R"(
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;

out vec3 FragPos;
out vec3 Normal;

uniform mat4 u_mvp;   // 模型-视图-投影矩阵
uniform mat4 u_model; // 模型矩阵,用于变换法线和世界坐标

void main()
{
    FragPos = vec3(u_model * vec4(position, 1.0));              // 计算世界坐标
    Normal = mat3(transpose(inverse(u_model))) * normal;        // 法线矩阵(保持正确方向)
    gl_Position = u_mvp * vec4(position, 1.0);                  // 最终顶点位置
})";

// 片元着色器:实现漫反射光照
static const char* lightingFrag = R"(
#version 330 core
in vec3 FragPos;
in vec3 Normal;
out vec4 FragColor;

uniform vec3 lightDir;     // 光照方向(单位向量)
uniform vec3 lightColor;   // 光颜色
uniform vec3 objectColor;  // 物体基础颜色

void main()
{
    // 漫反射分量(dot 表示光与法线夹角余弦)
    float diff = max(dot(normalize(Normal), -lightDir), 0.0);
    vec3 result = diff * lightColor * objectColor;
    FragColor = vec4(result, 1.0);  // 输出最终颜色
})";
void LightingRenderer::initialize()
{
    if (isInitialized) return;          // 防止重复初始化
    initializeOpenGLFunctions();        // 初始化 OpenGL 函数表

    if (shader.isLinked()) return;

    // 三角形顶点(每个顶点包含位置 + 法线,共 6 个 float)
    GLfloat vertices[] = {
        // position         // normal
        -0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,
         0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,
         0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f
    };

    // 编译并链接着色器程序
    shader.addShaderFromSourceCode(QOpenGLShader::Vertex, lightingVert);
    shader.addShaderFromSourceCode(QOpenGLShader::Fragment, lightingFrag);
    shader.link();

    shader.bind(); // 绑定以设置属性

    // 创建 VAO(保存所有绑定状态)
    vao.create();
    vao.bind();

    // 创建并填充 VBO
    vbo.create();
    vbo.bind();
    vbo.allocate(vertices, sizeof(vertices));

    // 设置属性 location 0(位置)和 location 1(法线)
    shader.enableAttributeArray(0);
    shader.setAttributeBuffer(0, GL_FLOAT, 0, 3, 6 * sizeof(float));

    shader.enableAttributeArray(1);
    shader.setAttributeBuffer(1, GL_FLOAT, 3 * sizeof(float), 3, 6 * sizeof(float));

    // 解绑
    vao.release();
    vbo.release();
    shader.release();

    isInitialized = true;
}
void LightingRenderer::resize(int w, int h)
{
    glViewport(0, 0, w, h);  // 设置 OpenGL 渲染区域
    width = w;
    height = h;
}
void LightingRenderer::render()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);  // 启用深度测试

    shader.bind();
    vao.bind();

    // 构造变换矩阵
    QMatrix4x4 model, view, proj;
    model.setToIdentity(); // 没有旋转缩放
    view.lookAt(QVector3D(0, 0, 2), QVector3D(0, 0, 0), QVector3D(0, 1, 0));
    proj.perspective(45.0f, float(width) / height, 0.1f, 10.0f);

    // 设置 uniform 变量
    QMatrix4x4 mvp = proj * view * model;
    shader.setUniformValue("u_mvp", mvp);
    shader.setUniformValue("u_model", model);

    // 动态光源方向(左右摆动)
    QVector3D lightDir = QVector3D(qSin(t), 0, -1).normalized();
    shader.setUniformValue("lightDir", lightDir);
    shader.setUniformValue("lightColor", QVector3D(1, 1, 1)); // 白光
    shader.setUniformValue("objectColor", QVector3D(1.0f, 0.5f, 0.3f)); // 橘色

    // 绘制三角形
    glDrawArrays(GL_TRIANGLES, 0, 3);

    vao.release();
    shader.release();
}
void LightingRenderer::onFrameUpdate()
{
    t += 0.03f;                  // 控制光源角度变化
    if (t > 2 * M_PI) t -= 2 * M_PI;
}
//使用
modules["光照效果"] = new LightingRenderer();
modules["光照效果"]->initialize();
if (auto* light = dynamic_cast<LightingRenderer*>(modules["光照效果"]))
        connect(this, &OpenGLSceneWidget::frameSwapped, light, &LightingRenderer::onFrameUpdate);

欢迎关注我,一起交流!


网站公告

今日签到

点亮在社区的每一天
去签到