Qt OpenGL(05)标准化设备坐标(NDC)

OpenGL中的坐标简介

OpenGL 基于绘制流水线模型,而且绘制流水线的第一个步骤是对顶点进行一 系列的操作, 其中大部分属于几何操作。这些操作可以用一系列坐标变换来表示。

对于基于固定功能绘制流水线和立即绘制模式的 OpenGL 版本,绘制流水线中存在 6 个标架。 其中有些会用于我们的 OpenGL 应用程序代码中,有些则会用于我们编写的着色器中。并不是所有的这 6 个标架对应用程序都是可见的。同一个顶点在不同的标架下会有不同的表示。

这 6 个坐标在绘制流水线中通常按照下面的先后顺序出现:

  1. 对象坐标系或者建模坐标系
  2. 世界坐标系
  3. 眼坐标系或者照相机坐标系
  4. 裁减坐标系
  5. 标准化的设备坐标系
  6. 窗口坐标系或者屏幕坐标系

标准化设备坐标

先了解一下 标准化设备坐标 :( Normalized Device Coordinates, NDC )

标准化设备坐标是一个 x、y 和 z 值在 -1.0 到 1.0 的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形(忽略z轴):

在这里插入图片描述

最后通过使用由 glViewport 函数提供的数据,进行视口变换(Viewport Transform),标准化设备坐标会变换为屏幕空间坐标(Screen-space Coordinates)。

引用:[你好,三角形 - LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/01 Getting started/04 Hello Triangle/)

标准化设备坐标绘制 x y z 三个轴线

在这里插入图片描述

上面的讲述忽略了 z 轴,下面绘制出 z 轴出来,对 NDC 坐标有个几何上的直观印象。

  1. 准备三个轴线的端点的坐标

    float lines[] = {
        // x
        -1.0f, 0.0f, 0.0f,
         1.0f, 0.0f, 0.0f,
        // y
         0.0f, -1.0f, 0.0f,
         0.0f,  1.0f, 0.0f,
        // z
         0.0f,  0.0f, -1.0f,
         0.0f,  0.0f,  1.0f,
    };
    

    利用 glDrawArrays (GL_LINES,0,2) 绘制

  2. 三个轴的正方形的端点的索引

    因为数据已经存在线段的坐标中,所以可以使用 索引对象 EBO ,保存 x, y, z 轴的正方向端点的索引

    unsigned int end[] = {
        1,3,5
    };
    

    利用 glDrawArrays (GL_LINES,0,2) 绘制

  3. 绑定显卡缓存数据VBO

        glGenBuffers (1,&VBO);
        glBindBuffer (GL_ARRAY_BUFFER,VBO);
    
        glBufferData (GL_ARRAY_BUFFER,
                      sizeof(lines) + sizeof(end) ,
                      NULL,
                      GL_STATIC_DRAW);
    
        glBufferSubData (GL_ARRAY_BUFFER,
                         0,
                         sizeof(lines),
                         lines );
    
  4. 指定顶点数组对象:Vertex Array Object,VAO

    这个 A A A 我更愿意理解成 a t t r i b u t e attribute attribute 或者 a s s i s t a n t assistant assistant,为 GPU 缓存中的数据分类并记录哪些是位置数据,哪些是颜色数据等。

        glGenVertexArrays (1,&VAO);
        glBindVertexArray(VAO);
        glVertexAttribPointer(0,    // vao 索引,对应顶点着色器中的变量的位置 : “layout (location = 0) in vec3 aPos;”
                              3,    // 变量中元素的个数, 比如 aPos变量 有 3 个数据【x,y,z】组成
                              GL_FLOAT, // 类型
                              GL_FALSE, // 标准化,是否在 [-1,1] 之间
                              3 * sizeof(float),  // 步长,表示下个元组的首元素 和 该元组首元素之间的大小,因为顶点数据中 可能还夹杂颜色、法向量等数据
                              (void*)0 );   // 变量的偏移量,在多个变量混合时指定变量的偏移
        glEnableVertexAttribArray(0); // 使能 location = 0 位置
    
  5. 指定元素缓冲对象( EBO 或者叫 IBO )

    EBO 理解成 VAO 的成员,让VAO中已经记录成功的变量从新组合或者复用

        glGenBuffers(1, &EBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(end), end, GL_STATIC_DRAW);
    
  6. 设置着色器程序

        QString filename = ":/shader";
        shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex,   filename+".vert");
        shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment, filename+".frag");
        shaderProgram.link ();
    
  7. 渲染过程

        // 绘制 x y z
        glLineWidth (20.0f);    // 线宽
        shaderProgram.setUniformValue ("u_color",1.0f, 0.0f, 0.0f, 1.0f); // 红色
        glDrawArrays (GL_LINES,0,2); // 画 x 轴
        shaderProgram.setUniformValue ("u_color",0.0f, 1.0f, 0.0f, 1.0f); // 绿色
        glDrawArrays (GL_LINES,2,2); // 画 y 轴
        shaderProgram.setUniformValue ("u_color",0.0f, 0.0f, 1.0f, 1.0f); // 蓝色
        glDrawArrays (GL_LINES,4,2); // 画 z 轴
    
        // 绘制 end
        glPointSize (30.0f);    // 点大小
        shaderProgram.setUniformValue ("u_color",1.0f, 0.0f, 0.0f, 1.0f);   // 红色
        glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT,0); // x 端点
    
        shaderProgram.setUniformValue ("u_color",0.0f, 1.0f, 0.0f, 1.0f);   // 绿色
        glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*1)); // y 端点
    
        shaderProgram.setUniformValue ("u_color",0.0f, 0.0f, 1.0f, 1.0f);   // 蓝色
        glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*2)); // z 端点
    

完整代码

顶点着色器

因为 z 轴正对屏幕,如果不做旋转,显示不出 z 轴,所以需要一个模型矩阵 model 变量

#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;

void main()
{
   gl_Position = model * vec4(aPos.x, aPos.y, aPos.z, 1.0);
};

片段着色器

通过 u_color 设置不同的颜色

#version 330 core
uniform vec4 u_color;
void main()
{
    gl_FragColor = vec4( u_color );
}

Widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>


class Widget : public QOpenGLWidget,QOpenGLFunctions_3_3_Core
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    // QOpenGLWidget interface
protected:
    virtual void initializeGL() override;
    virtual void paintGL() override;
    virtual void resizeGL(int w, int h) override;

private:
    unsigned int VAO, VBO, EBO;
    QOpenGLShaderProgram shaderProgram;

    int min,max;

    double ratio;
};
#endif // WIDGET_H

Widget.cpp

#include "Widget.h"
#include <QApplication>
#include <QEvent>
#include <QThread>

#define qRandom   QRandomGenerator::global ()
#define qout if( 1 ) qDebug() << __FILE__ << __LINE__ << ": "

float lines[] = {
    // x
    -1.0f, 0.0f, 0.0f,
     1.0f, 0.0f, 0.0f,
    // y
     0.0f, -1.0f, 0.0f,
     0.0f,  1.0f, 0.0f,
    // z
     0.0f,  0.0f, -1.0f,
     0.0f,  0.0f,  1.0f,
};

unsigned int end[] = {
    1,3,5
};
//float end[] = {
//    // x
//    1.0f,  0.0f, 0.0f,
//    // y
//    0.0f,  1.0f, 0.0f,
//    // z
//    0.0f,  0.0f, 1.0f,
//};

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    setWindowTitle ("07_xyz");
    resize (200,200);
    ratio = qApp->devicePixelRatio ();

}

Widget::~Widget()
{
    makeCurrent ();
    glDeleteBuffers (1,&VBO);
    glDeleteVertexArrays (1,&VAO);
    doneCurrent ();
}

void Widget::initializeGL()
{
    initializeOpenGLFunctions ();


    const char *version =(const char *) glGetString (GL_VERSION);
    qout << QString(version);

    // ---------------------------------

    glGenBuffers (1,&VBO);
    glBindBuffer (GL_ARRAY_BUFFER,VBO);

    glBufferData (GL_ARRAY_BUFFER,
                  sizeof(lines) ,
                  NULL,
                  GL_STATIC_DRAW);


    glBufferSubData (GL_ARRAY_BUFFER,
                     0,
                     sizeof(lines),
                     lines );


    QString filename = ":/shader";
    shaderProgram.addShaderFromSourceFile (QOpenGLShader::Vertex,   filename+".vert");
    shaderProgram.addShaderFromSourceFile (QOpenGLShader::Fragment, filename+".frag");
    shaderProgram.link ();

    glGenVertexArrays (1,&VAO);
    glBindVertexArray(VAO);
    glVertexAttribPointer(0,    // vao 索引,对应顶点着色器中的变量的位置 : “layout (location = 0) in vec3 aPos;”
                          3,    // 变量中元素的个数, 比如 aPos变量 有 3 个数据【x,y,z】组成
                          GL_FLOAT, // 类型
                          GL_FALSE, // 标准化,是否在 [-1,1] 之间
                          3 * sizeof(float),  // 步长,表示下个元组的首元素 和 该元组首元素之间的大小,因为顶点数据中 可能还夹杂颜色、法向量等数据
                          (void*)0 );   // 变量的偏移量,在多个变量混合时指定变量的偏移
    glEnableVertexAttribArray(0); // 使用 location = 0 的索引

    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(end), end, GL_STATIC_DRAW);



    glBindBuffer (GL_ARRAY_BUFFER,0);

//    glPolygonMode (GL_FRONT_AND_BACK,GL_LINE);

    // 深度测试
    glEnable(GL_DEPTH_TEST);
}

int rotateAngle = 0;
void Widget::paintGL()
{
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);   // 设置背景色
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glViewport ((max-min)/2 ,0,min,min);

    shaderProgram.bind ();
    glBindVertexArray(VAO);
    shaderProgram.setUniformValue ("u_color",1.0f, 0.5f, 0.2f, 1.0f);

    QMatrix4x4 model;
    model.rotate ( -15, 1.0f, 0.0f, 0.0f);
    model.rotate ( rotateAngle+15, 0.0f, 1.0f, 0.0f);
    shaderProgram.setUniformValue ("model",model);

    // 绘制 x y z
    glLineWidth (20.0f);    // 线宽
    glPointSize (30.0f);    // 点大小
    shaderProgram.setUniformValue ("u_color",1.0f, 0.0f, 0.0f, 1.0f); // 红色
    glDrawArrays (GL_LINES,0,2); // 画 x 轴
    glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT,0); // x 端点

    shaderProgram.setUniformValue ("u_color",0.0f, 1.0f, 0.0f, 1.0f); // 绿色
    glDrawArrays (GL_LINES,2,2); // 画 y 轴
    glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*1)); // y 端点

    shaderProgram.setUniformValue ("u_color",0.0f, 0.0f, 1.0f, 1.0f); // 蓝色
    glDrawArrays (GL_LINES,4,2); // 画 z 轴
    glDrawElements (GL_POINTS,1,GL_UNSIGNED_INT, (void *)(sizeof(unsigned int)*2)); // z 端点


//    qout << this->windowState ();
//    if( this->windowState () != Qt::WindowMinimized && 1 ){
    if( 1 ){
        QThread::currentThread ()->msleep (50);
        rotateAngle += 1 ;
        update ();
    }
}

void Widget::resizeGL(int w, int h)
{
    qout << "resizeGL";
    min = std::min (w,h) * ratio;
    max = std::max (w,h) * ratio;
}

总结

在这里插入图片描述

如图所示,在 NDC 坐标中 Z Z Z 的正向是朝屏幕内部的,这结果还真是让人意外。

<think>好的,我现在需要帮助用户了解如何使用QtOpenGL来实现TrackballManipulator。首先,用户之前已经询问过TrackballManipulator的视觉矩阵更新,现在他们希望具体在QtOpenGL环境中实现这个功能。我需要结合之前的回答,但这次要具体到Qt框架和OpenGL的集成。 首先,我应该回顾用户之前的对话,确保这次回答的一致性。之前的回答解释了TrackballManipulator的核心原理、代码结构示例、关键实现细节以及应用建议。现在需要将这些内容转化为QtOpenGL的具体实现步骤。 接下来,我需要考虑QtOpenGL的常用方法。Qt提供了QOpenGLWidget类,用于嵌入OpenGL渲染,还有QMatrix4x4来处理矩阵运算,这可能比GLM更方便。此外,Qt的事件处理机制,如鼠标事件和滚轮事件,需要被重写来捕捉用户的输入。 用户可能需要一个完整的示例框架,包括类的结构、事件处理、矩阵更新和渲染部分。需要分步骤解释如何在Qt中设置OpenGL环境,处理鼠标事件来更新视图矩阵,并将矩阵传递给着色器。 需要注意Qt坐标系与OpenGL的可能差异。例如,Qt的屏幕坐标原点在左上角,而OpenGLNDC原点在中心,需要转换处理。此外,QMatrix4x4的矩阵乘法顺序可能与之前提到的不同,需要确认正确的应用顺序,比如平移、旋转、缩放的组合顺序是否正确。 还要考虑如何将之前的伪代码转换为实际的Qt代码。例如,使用QQuaternion来处理旋转,QVector3D和QMatrix4x4来进行矩阵操作。在calculateRotation函数中,如何将屏幕坐标映射到虚拟轨迹球,并计算旋转四元数。 需要提醒用户注意一些常见问题,比如矩阵更新的顺序、坐标转换的正确性、事件处理的效率以及缩放限制等。此外,建议用户参考Qt的官方文档和示例,确保代码的兼容性和性能。 最后,确保回答结构清晰,分步骤说明,并提供一个完整的代码示例框架,帮助用户快速上手。可能还需要指出需要进一步实现的细节,比如mapToSphere的具体实现,或者如何将视图矩阵传递给着色器程序。</think>以下是基于QtOpenGL实现TrackballManipulator的完整技术方案: --- ### 一、环境准备 1. **Qt工程配置**: ```cpp // pro文件中添加OpenGL模块 QT += core gui opengl ``` 2. **创建OpenGL窗口**: ```cpp class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: explicit GLWidget(QWidget* parent = nullptr); // 重写OpenGL生命周期函数 void initializeGL() override; void paintGL() override; void resizeGL(int w, int h) override; }; ``` --- ### 二、Trackball核心实现 #### 1. 数据结构定义 ```cpp class TrackballManipulator { private: QMatrix4x4 m_viewMatrix; QVector2D m_lastMousePos; QQuaternion m_rotation; QVector3D m_translation = QVector3D(0, 0, -5); // 初始视点位置 float m_zoom = 1.0f; float m_viewportWidth = 800.0f; float m_viewportHeight = 600.0f; // 约束参数 const float MIN_ZOOM = 0.1f; const float MAX_ZOOM = 10.0f; }; ``` #### 2. 事件处理绑定 ```cpp // 在GLWidget类中添加事件处理 void GLWidget::mousePressEvent(QMouseEvent* event) { m_trackball.handleMousePress(event->pos()); } void GLWidget::mouseMoveEvent(QMouseEvent* event) { m_trackball.handleMouseMove(event->pos()); update(); // 触发重绘 } void GLWidget::wheelEvent(QWheelEvent* event) { m_trackball.handleWheel(event->angleDelta().y()); update(); } ``` --- ### 三、核心算法实现 #### 1. 旋转计算(核心) ```cpp QQuaternion TrackballManipulator::calculateRotation(const QVector2D& newPos) { // 坐标归一化到[-1,1] QVector3D v0 = mapToSphere(m_lastMousePos); QVector3D v1 = mapToSphere(newPos); // 计算旋转轴和角度 QVector3D axis = QVector3D::crossProduct(v0, v1); float angle = qAcos(qMin(1.0f, QVector3D::dotProduct(v0, v1))); return QQuaternion::fromAxisAndAngle(axis.normalized(), qRadiansToDegrees(angle)); } QVector3D TrackballManipulator::mapToSphere(const QVector2D& screenPos) { // Qt坐标系转换(原点在左上角) QVector2D normalized( 2.0f * screenPos.x() / m_viewportWidth - 1.0f, 1.0f - 2.0f * screenPos.y() / m_viewportHeight ); // 单位球面投影 float length = normalized.lengthSquared(); if(length > 1.0f) { return QVector3D(normalized.x(), normalized.y(), 0).normalized(); } else { return QVector3D(normalized.x(), normalized.y(), qSqrt(1.0f - length)); } } ``` #### 2. 矩阵合成 ```cpp QMatrix4x4 TrackballManipulator::getViewMatrix() { QMatrix4x4 matrix; matrix.translate(m_translation); // 先平移 matrix.rotate(m_rotation); // 后旋转 matrix.scale(m_zoom); // 最后缩放 return matrix; } ``` --- ### 四、渲染管线集成 #### 1. 在着色器中使用矩阵 ```glsl // Vertex Shader uniform mat4 viewMatrix; uniform mat4 projectionMatrix; void main() { gl_Position = projectionMatrix * viewMatrix * vec4(a_position, 1.0); } ``` #### 2. Qt中的矩阵传递 ```cpp void GLWidget::paintGL() { QOpenGLShaderProgram* program = // 获取着色器程序 program->bind(); program->setUniformValue("viewMatrix", m_trackball.getViewMatrix()); program->setUniformValue("projectionMatrix", m_projectionMatrix); // 绘制场景对象... program->release(); } ``` --- ### 五、高级功能扩展 #### 1. 惯性旋转(物理模拟) ```cpp void TrackballManipulator::updateInertia() { if(m_hasInertia) { m_rotation = QQuaternion::slerp(m_rotation, m_targetRotation, 0.1f); if(qAbs(m_rotation.angleTo(m_targetRotation)) < 0.1f) { m_hasInertia = false; } } } ``` #### 2. 多指触控支持 ```cpp void GLWidget::touchEvent(QTouchEvent* event) { if(event->touchPoints().count() == 2) { // 计算两指间距实现缩放 const QTouchEvent::TouchPoint& p1 = event->touchPoints().first(); const QTouchEvent::TouchPoint& p2 = event->touchPoints().last(); float distance = QVector2D(p1.pos() - p2.pos()).length(); // 计算缩放比例... } } ``` --- ### 六、关键注意事项 1. **坐标系差异处理**: - Qt鼠标坐标原点在窗口左上角 - OpenGL NDC坐标系原点在中心 - 使用`QMatrix4x4::perspective()`时的near/far值建议设为0.1-1000 2. **性能优化**: ```cpp // 使用QElapsedTimer实现帧率控制 void GLWidget::paintGL() { static QElapsedTimer timer; if(timer.elapsed() < 16) return; // 限制60FPS timer.restart(); // ...渲染代码 } ``` 3. **调试技巧**: ```cpp // 打印当前视图矩阵参数 qDebug() << "Rotation:" << m_rotation.toEulerAngles(); qDebug() << "Translation:" << m_translation; qDebug() << "Zoom:" << m_zoom; ``` 完整实现代码约需300-500行,建议参考Qt官方示例`Qt OpenGL Window Example`进行工程化扩展。实际开发中可结合QPropertyAnimation实现更流畅的过渡动画效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值