一、引言
在计算机图形学中,使用 OpenGL 和 QT 进行图形渲染是一项常见的任务。本博客将演示如何使用 QT 和 OpenGL 创建一个带有颜色贴图、高光贴图和自发光贴图的立体 Box。
二、着色器文件
- 创建顶点着色器文件
.vert
。传递顶点属性和纹理坐标。 - 创建片段着色器文件
.frag
。实现颜色贴图、高光贴图和自发光贴图的效果。
vertex_shader.vert:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
layout (location = 2) in vec3 aNormal;
out vec2 TexCoord;
out vec3 FragPos;
out vec3 Normal;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
FragPos = vec3(model * vec4(aPos, 1.0));
Normal = mat3(transpose(inverse(model))) * aNormal;
TexCoord = aTexCoord;
}
fragment_shader.frag:
#version 330 core
out vec4 FragColor;
in vec2 TexCoord;
in vec3 FragPos;
in vec3 Normal;
uniform sampler2D texture1;
uniform vec3 objectColor;
uniform vec3 lightColor;
uniform vec3 lightPos;
uniform vec3 viewPos;
void main() {
// ambient
float ambientStrength = 0.1;
vec3 ambient = ambientStrength * lightColor;
// diffuse
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
// specular
float specularStrength = 0.5;
vec3 viewDir = normalize(viewPos - FragPos);
vec3 reflectDir = reflect(-lightDir, norm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = specularStrength * spec * lightColor;
vec3 result = (ambient + diffuse + specular) * objectColor;
FragColor = texture(texture1, TexCoord) * vec4(result, 1.0);
}
三、绘制立体 Box
- 在主渲染循环中绘制立体 Box。
- 使用顶点数组定义立体 Box 的几何形状。
- 创建 Vertex Buffer Objects (VBOs) 和 Vertex Array Objects (VAOs)。
- 使用 QT 的
QImage
加载颜色、高光和自发光贴图。 - 设置光照、投影等参数。
myopenglwidget.h:
// myopenglwidget.h
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLTexture>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions>
#include <QTime>
class MyOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
MyOpenGLWidget(QWidget *parent = nullptr);
protected:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
private slots:
void on_timeout();
private:
QTime m_time;
QTimer m_timer;
QOpenGLTexture *texture;
QOpenGLShaderProgram shaderProgram;
GLuint VAO, VBO;
// Projection matrix
QMatrix4x4 projection;
// Model matrix
QMatrix4x4 model;
// View matrix
QMatrix4x4 view;
QVector3D viewInitPos = QVector3D(0.0f, 0.0f, 3.0f);
struct Vertex {
QVector3D position;
QVector2D textureCoords;
QVector3D normal;
};
QVector<Vertex> vertices;
void setupVertices();
};
#endif // MYOPENGLWIDGET_H
myopenglwidget.cpp:
// myopenglwidget.cpp
#include "myopenglwidget.h"
#include <QImage>
#include <QFile>
#include <QTextStream>
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
connect(&m_timer, SIGNAL(timeout()), this, SLOT(on_timeout()));
m_timer.start(16); // 60 FPS
m_time.start();
m_camera.Position = viewInitPos;
setFocusPolicy(Qt::StrongFocus);
setupVertices();
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
// Position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(0);
// Texture attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, textureCoords));
glEnableVertexAttribArray(1);
// Normal attribute
glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glEnableVertexAttribArray(2);
// Load and create a texture
texture = new QOpenGLTexture(QImage(":/path/to/your/texture/image.jpg").mirrored());
// Load shaders from file
QFile vertexShaderFile(":/shaders/vertex_shader.vert");
QFile fragmentShaderFile(":/shaders/fragment_shader.frag");
if (!vertexShaderFile.open(QIODevice::ReadOnly | QIODevice::Text) ||
!fragmentShaderFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Failed to open shader files.";
return;
}
QTextStream vertexShaderStream(&vertexShaderFile);
QTextStream fragmentShaderStream(&fragmentShaderFile);
QString vertexShaderSource = vertexShaderStream.readAll();
QString fragmentShaderSource = fragmentShaderStream.readAll();
const char *vertexShaderCode = vertexShaderSource.toLocal8Bit().constData();
const char *fragmentShaderCode = fragmentShaderSource.toLocal8Bit().constData();
// Compile shaders
GLuint vertexShader, fragmentShader;
GLint success;
GLchar infoLog[512];
// Vertex Shader
vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderCode, nullptr);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(vertexShader, sizeof(infoLog), nullptr, infoLog);
qWarning() << "Vertex shader compilation failed\n" << infoLog;
}
// Fragment Shader
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderCode, nullptr);
glCompileShader(fragmentShader);
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success) {
glGetShaderInfoLog(fragmentShader, sizeof(infoLog), nullptr, infoLog);
qWarning() << "Fragment shader compilation failed\n" << infoLog;
}
// Shader Program
shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderCode);
shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderCode);
shaderProgram.link();
// Delete the shaders as they're linked into our program now and no longer necessary
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
}
void MyOpenGLWidget::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
projection.setToIdentity();
projection.perspective(45.0f, static_cast<float>(w) / static_cast<float>(h), 0.1f, 100.0f);
}
void MyOpenGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shaderProgram.bind();
shaderProgram.setUniformValue("model", model);
shaderProgram.setUniformValue("view", view);
shaderProgram.setUniformValue("projection", projection);
// Bind texture
glActiveTexture(GL_TEXTURE0);
texture->bind();
shaderProgram.setUniformValue("texture1", 0);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, vertices.size()); // Assuming vertices.size() vertices
// Draw more objects...
}
void MyOpenGLWidget::setupVertices()
{
// Set up vertex data and buffers
vertices.clear();
// Define vertices with positions, texture coordinates, and normals
// ...
// Add vertices to the vertices vector
// Example:
vertices.push_back({QVector3D(-0.5f, -0.5f, -0.5f), QVector2D(0.0f, 0.0f), QVector3D(0.0f, 0.0f, -1.0f)});
vertices.push_back({QVector3D(0.5f, -0.5f, -0.5f), QVector2D(1.0f, 0.0f), QVector3D(0.0f, 0.0f, -1.0f)});
vertices.push_back({QVector3D(0.5f, 0.5f, -0.5f), QVector2D(1.0f, 1.0f), QVector3D(0.0f, 0.0f, -1.0f)});
// ... Add more vertices for other objects
// ...
}
四、总结
在本教程中,我们将学习如何使用Qt和OpenGL创建一个具有颜色贴图、高光贴图和自发光贴图效果的立体盒子。以下是整个制作流程的详细总结:
首先,我们需要创建一个Qt Widgets应用程序项目,并添加OpenGL模块。接着,定义一个自定义的OpenGL小部件类,继承自QOpenGLWidget
,例如MyOpenGLWidget
,该类将用于渲染OpenGL场景。
为了在立体盒子上应用纹理,我们需要加载颜色、高光、自发光贴图的图片文件。Qt的QOpenGLTexture
类可用于方便地加载和管理这些纹理。
接下来,准备顶点着色器和片段着色器的外部文件(.vert和.frag文件),并使用QFile
和QTextStream
加载着色器源代码。
在着色器文件中,我们编写顶点和片段着色器代码,实现光照、纹理贴图等效果。这些着色器将负责处理立体盒子的渲染过程。
在OpenGL初始化代码中,我们使用initializeGL
函数进行OpenGL的初始化工作,包括生成顶点数组对象(VAO)和顶点缓冲对象(VBO)等。
在OpenGL渲染代码中,我们使用paintGL
函数编写渲染代码,设置模型矩阵、视图矩阵、投影矩阵等,绑定纹理,绘制立体盒子。
为了确保在窗口大小调整时保持正确的比例,我们在resizeGL
函数中设置视口大小和投影矩阵。通过QOpenGLShaderProgram
类,我们连接顶点着色器和片段着色器,创建一个OpenGL着色器程序。
最后,在主窗口或主窗体中创建MyOpenGLWidget
实例,将其设置为中心窗口,运行程序。通过正确设置文件路径和名称,你将能够在Qt环境下运行这个简单但功能强大的OpenGL程序,呈现一个具有颜色贴图、高光贴图和自发光贴图效果的立体盒子。这个技术博客提供了一个完整的制作流程,帮助你构建更复杂的OpenGL场景。