viewCube.h

#pragma once
#include <QOpenGLFunctions_4_5_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QVector3D>
#include <QVector2D>
#include <QVector4D>
#include <vector>
#include <memory>
#include <QString>

// 顶点数据结构(位置+纹理坐标)
struct CubeVertex {
QVector3D pos; // 位置
QVector2D texCoord; // 纹理坐标
};

class ViewCube : protected QOpenGLFunctions_4_5_Core {
public:
// 构造函数:传入纹理路径
explicit ViewCube(QOpenGLFunctions_4_5_Core* gl, const QString& texPath, unsigned int windowWidth, unsigned int windowHeight)
: m_gl(gl), m_texPath(texPath), m_windowWidth(windowWidth), m_windowHeight(windowHeight) {
init();
}

// 扩展draw函数:接收view和projection矩阵并存储(关键修改)
void draw(QOpenGLShaderProgram& shader, const QMatrix4x4& model,
const QMatrix4x4& view, const QMatrix4x4& projection) {
// 存储当前绘制的矩阵(用于后续检测)
m_modelMatrix = model;
m_viewMatrix = view;
m_projectionMatrix = projection;

// 原有绘制逻辑
m_gl->glBindVertexArray(m_vao);
shader.setUniformValue("model", model);
m_texture->bind(0);
shader.setUniformValue("u_texture", 0);
m_gl->glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_INT, nullptr);
m_gl->glBindVertexArray(0);
m_texture->release();
}

enum class CubeFaceType {
None, // 无点击 0
Top, // 上面 1
Bottom, // 下面 2
Left, // 左面 3
Right, // 右面 4
Front, // 前面 5
Back // 后面 6
};

// 设置窗口宽高(外部窗口大小变化时调用)
void setWindowSize(int width, int height) {
m_windowWidth = width;
m_windowHeight = height;
}

// 核心函数:判断鼠标点击射线与ViewCube最先相交的面
CubeFaceType getFirstHitFace(
float mouseX, float mouseY,
int windowWidth, int windowHeight,
const QMatrix4x4& viewMatrix,
const QMatrix4x4& projectionMatrix)
{
// 1. 生成射线
Ray ray = createRayFromScreenCoord(mouseX, mouseY, windowWidth, windowHeight, viewMatrix, projectionMatrix);

// 2. 获取ViewCube的6个面
std::vector<Plane> cubePlanes = createViewCubePlanes();

// 3. 遍历所有面,找最小的有效t
float minT = std::numeric_limits<float>::max();
CubeFaceType hitFace = CubeFaceType::None; // 默认值

for (const auto& plane : cubePlanes) {
float t = getIntersectionT(ray, plane);
if (t > 0 && t < minT) { // 有效且更小的t
minT = t;
hitFace = plane.face;
}
}

return hitFace;
}

private:
// 射线结构体
struct Ray {
QVector3D origin; // 起点
QVector3D direction; // 方向向量(归一化)
};

// 平面方程(ax + by + cz + d = 0)
struct Plane {
float a, b, c, d;
CubeFaceType face; // 关联的面
Plane(float a, float b, float c, float d, CubeFaceType face)
: a(a), b(b), c(c), d(d), face(face) {
}
};

// 生成ViewCube的6个面(假设Cube中心在原点,边长为2,范围[-1,1])
std::vector<Plane> createViewCubePlanes() {
return {
// 前面(z=0.5):法向量+z(向外)
Plane(0, 0, 1, -0.5f, CubeFaceType::Front),
// 后面(z=-0.5):法向量-z(向外)
Plane(0, 0, -1, -0.5f, CubeFaceType::Back), // 修正:c=-1,d=-0.5
// 左面(x=-0.5):法向量-x(向外)
Plane(-1, 0, 0, -0.5f, CubeFaceType::Left), // 修正:a=-1,d=-0.5
// 右面(x=0.5):法向量+x(向外)
Plane(1, 0, 0, -0.5f, CubeFaceType::Right), // 修正:d=-0.5
// 顶面(y=0.5):法向量+y(向外)
Plane(0, 1, 0, -0.5f, CubeFaceType::Top), // 修正:b=1,d=-0.5
// 底面(y=-0.5):法向量-y(向外)
Plane(0, -1, 0, -0.5f, CubeFaceType::Bottom) // 修正:b=-1,d=-0.5
};
}//n*(P-p0)

// 计算射线与平面的交点参数t(返回-1表示不相交)
float getIntersectionT(const Ray& ray, const Plane& plane) {
const float eps = 1e-6;
float denom = plane.a * ray.direction.x() + plane.b * ray.direction.y() + plane.c * ray.direction.z();
if (fabs(denom) < eps) return -1; // 平行无交点

float numerator = -(plane.a * ray.origin.x() + plane.b * ray.origin.y() + plane.c * ray.origin.z() + plane.d);
float t = numerator / denom;
if (t < eps) return -1; // 交点在射线起点后方

// 计算交点坐标
QVector3D hitPoint = ray.origin + ray.direction * t;

// 根据平面所属面,检查交点是否在立方体的面上(范围[-0.5, 0.5])
switch (plane.face) {
case CubeFaceType::Front: // z=0.5,检查x和y
case CubeFaceType::Back: // z=-0.5,检查x和y
if (hitPoint.x() < -0.5f - eps || hitPoint.x() > 0.5f + eps) return -1;
if (hitPoint.y() < -0.5f - eps || hitPoint.y() > 0.5f + eps) return -1;
break;
case CubeFaceType::Left: // x=-0.5,检查y和z
case CubeFaceType::Right: // x=0.5,检查y和z
if (hitPoint.y() < -0.5f - eps || hitPoint.y() > 0.5f + eps) return -1;
if (hitPoint.z() < -0.5f - eps || hitPoint.z() > 0.5f + eps) return -1;
break;
case CubeFaceType::Top: // y=0.5,检查x和z
case CubeFaceType::Bottom: // y=-0.5,检查x和z
if (hitPoint.x() < -0.5f - eps || hitPoint.x() > 0.5f + eps) return -1;
if (hitPoint.z() < -0.5f - eps || hitPoint.z() > 0.5f + eps) return -1;
break;
default: return -1;
}

return t;
}

// 从屏幕坐标生成射线(复用之前的函数)
Ray createRayFromScreenCoord(
float screenX, float screenY,
int windowWidth, int windowHeight,
const QMatrix4x4& viewMatrix,
const QMatrix4x4& projectionMatrix)
{
// 1. 屏幕坐标→NDC坐标
float ndcX = (2.0f * screenX) / windowWidth - 1.0f;
float ndcY = 1.0f - (2.0f * screenY) / windowHeight; // 翻转Y轴
// 2. 逆矩阵转换
QMatrix4x4 invViewProj = (projectionMatrix * viewMatrix).inverted();
QVector4D nearPt(ndcX, ndcY, -1.0f, 1.0f);
QVector4D farPt(ndcX, ndcY, 1.0f, 1.0f);
QVector4D worldNearHomogeneous = invViewProj * nearPt;
QVector3D worldNear = worldNearHomogeneous.toVector3D() / worldNearHomogeneous.w();

QVector4D worldFarHomogeneous = invViewProj * farPt;
QVector3D worldFar = worldFarHomogeneous.toVector3D() / worldFarHomogeneous.w();
// 3. 生成射线
return { worldNear, (worldFar - worldNear).normalized() };
}

// 原有初始化逻辑(未修改)
void init() {
m_gl->initializeOpenGLFunctions();
generateVertices();
createTexture();
setupBuffers();
}

// 原有顶点生成逻辑(未修改)
void generateVertices() {
const QVector3D positions[8] = {
{ -0.5f, -0.5f, -0.5f },{ 0.5f, -0.5f, -0.5f },
{ 0.5f, 0.5f, -0.5f },{ -0.5f, 0.5f, -0.5f },
{ -0.5f, -0.5f, 0.5f },{ 0.5f, -0.5f, 0.5f },
{ 0.5f, 0.5f, 0.5f },{ -0.5f, 0.5f, 0.5f }
};

const QVector2D texCoords[6][4] = {
{ { 0.0f, 1.0f },{ 0.5f, 1.0f },{ 0.5f, 2.0f / 3.0f },{ 0.0f, 2.0f / 3.0f } },
{ { 1.0f, 2.0f / 3.0f },{ 0.5f, 2.0f / 3.0f },{ 0.5f, 1.0f },{ 1.0f, 1.0f } },
{ { 0.0f, 1.0f / 3.0f },{ 0.0f, 2.0f / 3.0f },{ 0.5f, 2.0f / 3.0f },{ 0.5f, 1.0f / 3.0f } },
{ { 1.0f, 2.0f / 3.0f },{ 1.0f, 1.0f / 3.0f },{ 0.5f, 1.0f / 3.0f },{ 0.5f, 2.0f / 3.0f } },
{ { 0.0f, 0.0f },{ 0.5f, 0.0f },{ 0.5f, 1.0f / 3.0f },{ 0.0f, 1.0f / 3.0f } },
{ { 1.0f, 0.0f } ,{ 0.5f, 0.0f },{ 0.5f, 1.0f / 3.0f } ,{ 1.0f, 1.0f / 3.0f } }
};

const unsigned int indices[6][6] = {
{ 3, 2, 6, 3, 7, 6 },{ 1, 0, 4, 4, 5, 1 },
{ 0, 3, 7, 7, 4, 0 },{ 2, 1, 5, 5, 6, 2 },
{ 4, 5, 6, 6, 7, 4 },{ 0, 1, 2, 2, 3, 0 }
};

m_vertices.clear();
m_indices.clear();

for (int face = 0; face < 6; ++face) {
for (int i = 0; i < 6; ++i) {
unsigned int globalIdx = indices[face][i];
int localIdx = -1;
for (int j = 0; j < 4; ++j) {
if (faceVertices[face][j] == globalIdx) {
localIdx = j;
break;
}
}
CubeVertex v;
v.pos = positions[globalIdx];
v.texCoord = texCoords[face][localIdx];
m_vertices.push_back(v);
}
}

for (unsigned int i = 0; i < m_vertices.size(); ++i) {
m_indices.push_back(i);
}
}

// 原有纹理创建逻辑(未修改)
void createTexture() {
QImage img(m_texPath);
if (img.isNull()) {
qWarning() << "纹理加载失败:" << m_texPath;
return;
}
m_texture = std::make_unique<QOpenGLTexture>(img.mirrored());
m_texture->setMinificationFilter(QOpenGLTexture::Linear);
m_texture->setMagnificationFilter(QOpenGLTexture::Linear);
}

// 原有缓冲区初始化逻辑(未修改)
void setupBuffers() {
m_gl->glGenVertexArrays(1, &m_vao);
m_gl->glGenBuffers(1, &m_vbo);
m_gl->glGenBuffers(1, &m_ebo);

m_gl->glBindVertexArray(m_vao);
m_gl->glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
m_gl->glBufferData(GL_ARRAY_BUFFER, m_vertices.size() * sizeof(CubeVertex), m_vertices.data(), GL_STATIC_DRAW);
m_gl->glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ebo);
m_gl->glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_indices.size() * sizeof(unsigned int), m_indices.data(), GL_STATIC_DRAW);

m_gl->glEnableVertexAttribArray(0);
m_gl->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(CubeVertex), (void*)offsetof(CubeVertex, pos));
m_gl->glEnableVertexAttribArray(1);
m_gl->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(CubeVertex), (void*)offsetof(CubeVertex, texCoord));

m_gl->glBindVertexArray(0);
}

private:
// 原有成员变量
QOpenGLFunctions_4_5_Core* m_gl;
QString m_texPath;
std::vector<CubeVertex> m_vertices;
std::vector<unsigned int> m_indices;
unsigned int m_vao, m_vbo, m_ebo;
std::unique_ptr<QOpenGLTexture> m_texture;
const unsigned int faceVertices[6][4] = {
{ 3, 2, 6, 7 },{ 1, 0, 4, 5 },{ 0, 3, 7, 4 },
{ 2, 1, 5, 6 },{ 4, 5, 6, 7 },{ 0, 1, 2, 3 }
};

// 新增成员变量(用于检测)
QMatrix4x4 m_modelMatrix; // 存储绘制时的模型矩阵
QMatrix4x4 m_viewMatrix; // 存储绘制时的视图矩阵
QMatrix4x4 m_projectionMatrix;// 存储绘制时的投影矩阵
int m_windowWidth; // 窗口宽度
int m_windowHeight; // 窗口高度
};

camera.h

 //.........................................


QMatrix4x4 GetViewMatrix4VieweCube()
{
QVector3D pos = GetCameraPos4ViewCube();

QMatrix4x4 result;
result.lookAt(pos, QVector3D(0.0, 0.0, 0.0), Up);
return result;
}

void FitView(int flag)
{
if (flag < 1 || flag > 6)
return;

float depth = 10.0f;
if (flag == 1)//上
{
Front = QVector3D(0.0, -1.0, 0.0);
Position = QVector3D(0.0, depth, 0.0);

Vector3f paraAxis = ViewerUtils::normalizeToAxis(Vector3f(Right.x(), Right.y(), Right.z()), Vector3f::BasicX, Vector3f::BasicY, Vector3f::BasicZ);
Right = QVector3D(paraAxis.X, paraAxis.Y, paraAxis.Z);
Up = QVector3D::crossProduct(Right, Front);

Yaw = Vector3f::BasicZ.AngleOnPlaneTo(Vector3f(Right.x(), Right.y(), Right.z()), Vector3f(Front.x(), Front.y(), Front.z())) / PI * 180.0 - 360.0;
//Yaw = -90.f;
Pitch = -90.f;
}
else if (flag == 2)//下
{
Front = QVector3D(0.0, 1.0, 0.0);
Position = QVector3D(0.0, -depth, 0.0);

Vector3f paraAxis = ViewerUtils::normalizeToAxis(Vector3f(Right.x(), Right.y(), Right.z()), Vector3f::BasicX, Vector3f::BasicY, Vector3f::BasicZ);
Right = QVector3D(paraAxis.X, paraAxis.Y, paraAxis.Z);
Up = QVector3D::crossProduct(Right, Front);

Yaw = Vector3f::BasicZ.AngleOnPlaneTo(Vector3f(Right.x(), Right.y(), Right.z()), Vector3f(Front.x(), Front.y(), Front.z())) / PI * 180.0 - 360.0;
//Yaw = -90.f;
Pitch = 90.f;
}
else if (flag == 3)//左
{
Front = QVector3D(1.0, 0.0, 0.0);
Right = QVector3D(0.0, 0.0, 1.0);
Up = QVector3D(0.0, 1.0, 0.0);
Position = QVector3D(-depth, 0.0, 0.0);
Yaw = 0.f;
Pitch = 0.f;
}
else if (flag == 4)//右
{
Front = QVector3D(-1.0, 0.0, 0.0);
Right = QVector3D(0.0, 0.0, -1.0);
Up = QVector3D(0.0, 1.0, 0.0);
Position = QVector3D(depth, 0.0, 0.0);
Yaw = -180.f;
Pitch = 0.f;
}
else if (flag == 5)//前
{
Front = QVector3D(0.0, 0.0, -1.0);
Right = QVector3D(1.0, 0.0, 0.0);
Up = QVector3D(0.0, 1.0, 0.0);
Position = QVector3D(0.0, 0.0, depth);
Yaw = -90.f;
Pitch = 0.f;
}
else if (flag == 6)//后
{
Front = QVector3D(0.0, 0.0, 1.0);
Right = QVector3D(-1.0, 0.0, 0.0);
Up = QVector3D(0.0, 1.0, 0.0);
Position = QVector3D(0.0, 0.0, -depth);
Yaw = -270.f;
Pitch = 0.f;
}
else
return;

firstMouse = true;
Zoom = 45.0f;
}

//.......................................................

glview.cpp

#include "glview.h"
#include <QMouseEvent>
#include <qdebug.h>

GLView::GLView(QWidget* parent)
: QOpenGLWidget{ parent }
{
}

GLView::~GLView()
{
makeCurrent();
delete m_model;

doneCurrent();
}

void GLView::initializeGL()
{
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
//glEnable(GL_CULL_FACE);
/*glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);*/

initShader(m_lightShader, "./shader/modelLoading.vert", "./shader/modelLoading.frag");
initShader(m_viewCubeShader, "./shader/viewCubeTexture.vert", "./shader/viewCubeTexture.frag");

m_model = new Model(this);
m_camera.lastFrame = QTime::currentTime().msecsSinceStartOfDay() / 1000.0;

initWorkPlane();
//m_viewMat.lookAt(QVector3D(0, 0, 5), QVector3D(0, 0, 0), QVector3D(0, 1, 0));

m_viewCube = new ViewCube(this, ":/MainWindow/resources/viewcube.png", width(), height());
}

void GLView::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);

m_camera.SCR_WIDTH = w;
m_camera.SCR_HEIGHT = h;

m_projectionMat.setToIdentity();
m_projectionMat.perspective(/*qDeg
reesToRadians*/(m_camera.Zoom), (float)m_camera.SCR_WIDTH / (float)m_camera.SCR_HEIGHT, 0.1f, 100.0f);
//m_projectionMat.perspective(45.0f, (float)w / h, 0.1f, 100.0f);
}

void GLView::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

float currentFrame = QTime::currentTime().msecsSinceStartOfDay() / 1000.0;
m_camera.deltaTime = currentFrame - m_camera.lastFrame;
m_camera.lastFrame = currentFrame;

m_lightShader.bind();

m_viewMat = m_camera.GetViewMatrix();

m_lightShader.setUniformValue("projection", m_projectionMat);
m_lightShader.setUniformValue("view", m_viewMat);
m_lightShader.setUniformValue("model", m_modelMat);

m_model->Draw(m_lightShader);
m_workPlane->drawPlane();

//-------------------未封装到Model类中的绘制版本-------------------------

// 2. 绘制ViewCube(使用纹理着色器)
if (m_viewCube)
{
m_viewCubeShader.bind(); // 绑定纹理着色器

//model矩阵: 缩放cubeScale
m_modelCube.setToIdentity();
float cubeScale = 0.5f;
m_modelCube.scale(cubeScale);

// View矩阵
m_viewMatNoTrans = m_camera.GetViewMatrix4VieweCube();

// Projection矩阵:透视投影 + 裁剪空间偏移(核心:固定到右上角)
// 基础透视投影
m_projection4ViewCube.setToIdentity();
float aspect = (float)width() / height(); // 窗口宽高比(替换为你的宽高获取方式)
m_projection4ViewCube.perspective(45.0f, aspect, 0.1f, 100.0f);

// 投影后偏移矩阵(在裁剪空间偏移,将viewCube推到右上角)
QMatrix4x4 offViewCube;
offViewCube.setToIdentity();
// 裁剪空间偏移:X正方向右移,Y正方向上移(范围[-1,1],0.85/0.7为右上角典型值)
QVector2D projOffset(0.90f, 0.80f);// 裁剪空间偏移(控制右上角位置,范围[-1,1])
offViewCube.translate(projOffset.x(), projOffset.y(), 0.0f);

// 最终投影矩阵 = 偏移矩阵 × 基础透视投影
m_finalProjection = offViewCube * m_projection4ViewCube;//将视图立方体固定在左上角的核心代码:施加投影矩阵后再施加一个偏移offViewCube,而不是先施加平移矩阵再投影

// 传递矩阵给着色器
m_viewCubeShader.setUniformValue("model", m_modelCube); // 原点位置的viewCube
m_viewCubeShader.setUniformValue("view", m_viewMatNoTrans); // 看向原点的view矩阵
m_viewCubeShader.setUniformValue("projection", m_finalProjection); // 带偏移的投影矩阵

// 绘制viewCube
m_viewCube->draw(m_viewCubeShader, m_modelCube, m_viewMatNoTrans, m_finalProjection);

m_viewCubeShader.release(); // 解绑着色器
}

m_lightShader.release();
}

void GLView::initShader(QOpenGLShaderProgram& shader, const QString& vertexFile, const QString& fragFile)
{
bool result = true;
result = shader.addShaderFromSourceFile(QOpenGLShader::Vertex, vertexFile);
if (!result) {
qDebug() << shader.log();
}

result = shader.addShaderFromSourceFile(QOpenGLShader::Fragment, fragFile);
if (!result) {
qDebug() << shader.log();
}
result = shader.link();
if (!result) {
qDebug() << shader.log();
}
}

bool GLView::event(QEvent* e)
{
makeCurrent();

if (e->type() == QEvent::MouseButtonPress)
{
auto mouseEvent = static_cast<QMouseEvent*>(e);
if (mouseEvent->button() == Qt::LeftButton)
{
ViewCube::CubeFaceType face = m_viewCube->getFirstHitFace(mouseEvent->position().x(), mouseEvent->position().y(),width(),height(), m_viewMatNoTrans, m_finalProjection);
if (face != ViewCube::CubeFaceType::None)
{
m_camera.FitView(static_cast<int>(face));
}
}
}

if (m_camera.handle(e))
update();

doneCurrent();
return QWidget::event(e);
}

void GLView::initWorkPlane()
{
PlanePara para;
para.origin = QVector3D(0.0f, -1.0f, 0.0f);//原点
para.normal = QVector3D(0.0f, 1.0f, 0.0f); //法向
para.halfLength = 15.0f;
para.gridCntPerEdge = 12;
para.offset = 0.0f;

m_workPlane = new WorkPlane();
m_workPlane->initPlane(para);
}

modeLoading.vert

#version 450 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fragColor;
out vec2 screenPos; // 输出屏幕坐标(用于渐变)

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform vec2 screenSize; // 屏幕宽高

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
fragColor = aColor;
// 计算屏幕坐标(标准化到0~1范围)
screenPos = (gl_Position.xy / gl_Position.w + 1.0) * 0.5;
}

modeLoading.frag

#version 450 core
in vec3 fragColor;
in vec2 screenPos; // 接收屏幕坐标
out vec4 FragColor;

// 渐变颜色参数(CAD风格深灰渐变)
vec3 topColor = vec3(0.25f, 0.25f, 0.25f); // 顶部稍亮
vec3 bottomColor = vec3(0.15f, 0.15f, 0.15f); // 底部稍暗

void main() {
// 绘制模型时用原有颜色,否则用渐变色背景
if (gl_FragCoord.z < 1.0) { // 若该像素是模型(z值小于1)
FragColor = vec4(fragColor, 1.0);
} else { // 否则是背景
// 按Y坐标插值计算渐变颜色
float t = screenPos.y; // t=0是底部,t=1是顶部
vec3 bgColor = mix(bottomColor, topColor, t);
FragColor = vec4(bgColor, 1.0);
}
}

viewCubeTexture.vert

#version 330 core
layout (location = 0) in vec3 aPos; // 顶点位置
layout (location = 1) in vec2 aTexCoord; // 纹理坐标

out vec2 TexCoord; // 传递给片段着色器

uniform mat4 model; // 模型矩阵
uniform mat4 view; // 视图矩阵
uniform mat4 projection; // 投影矩阵

void main() {
gl_Position = projection * view * model * vec4(aPos, 1.0);
TexCoord = aTexCoord;
}

viewCubeTexture.frag

#version 330 core
out vec4 FragColor;

in vec2 TexCoord; // 接收顶点着色器的纹理坐标

uniform sampler2D u_texture; // 纹理采样器

void main() {
FragColor = texture(u_texture, TexCoord); // 直接采样纹理
}

图-1

[源码与示例程序下载]