3d实现公告牌Billboard

公告牌Billboard

在3d中,相机是可以移动和旋转角度的,可以看到物体的不同面。但是有一种特殊的存在,就是想让物体一直对着相机,能够显示出来,受相机的位置变化所影响,3d中把这个技术叫做公告牌Billboard。

原理:

动态调整平面物体的朝向,使其始终面向摄像机,保持物体与视线方向垂直。

Billboard的核心原理是:通过构造一个特殊的旋转矩阵,使平面始终面向摄像机坐标系。

就是上面说的那个特点,物体平面都是对着相机的。假设物体的前方向都是对着相机,物体对着相机的正交坐标系也是能算的。只要算出来对着相机的旋转矩阵,让物体旋转就可以了,就是这么个原理。

还有一种是圆柱形公告板,就是只能绕着圆柱轴旋转,其他那个方向不旋转。

计算思路:

P = 平面中心点(世界坐标)

C = 摄像机位置(世界坐标)

V = 视图方向向量

Up = 世界向上向量(通常是(0,1,0))

我们要构建一个旋转矩阵 R,使得平面的法向量 N 与 V 方向相同(或相反)。

步骤1:计算视线方向

物体正面对着相机的向量。

复制代码
D = C - P  // 从平面指向摄像机的向量
Z_axis = normalize(D)  // 新的前向轴(或负方向,取决于坐标系)

步骤2:构建正交坐标系

有了物体对着相机的向量,我们是能构建一个标准的正交坐标系(两两轴都垂直)。

问题:仅知道Z轴(视线方向),需要找到合适的X轴(右向量)和Y轴(上向量)。

复制代码
X_axis = normalize(cross(WorldUp, Z_axis))
Y_axis = cross(Z_axis, X_axis)

步骤3:处理退化情况

当 Z_axis 与 WorldUp 几乎平行时,叉积结果为0或接近0。

复制代码
if (|dot(Z_axis, WorldUp)| > 0.9999) {
    // 接近平行,使用备用向量
    X_axis = normalize(cross(WorldLeft, Z_axis))
    Y_axis = cross(Z_axis, X_axis)
}

步骤4:构造旋转矩阵

旋转矩阵的列向量定义了局部坐标系在世界坐标系中的方向:

复制代码
R = [ X_axis.x  Y_axis.x  Z_axis.x  0 ]
    [ X_axis.y  Y_axis.y  Z_axis.y  0 ]
    [ X_axis.z  Y_axis.z  Z_axis.z  0 ]
    [    0        0         0       1 ]

有了旋转矩阵,只要把我们物体旋转到旋转矩阵位置就可以了。

或者是把3d中的顶点数据乘以旋转矩阵,就是我们要的新的顶点数据了。

这两个方法都可以实现,一个是cpu计算,控制物体旋转,一个是gpu计算顶点数据时候直接计算。

Qt3d具体实现公告牌案例

QText2DEntity + 调整模型旋转实现

功能:

利用qt的QText2DEntity文本+ QTransform 位置,根据相机的位置变化,调整模型的位置变化。

实现不管怎么旋转,文本都显示在相机的正面视角里面。

cpp 复制代码
class BillboardTextEntity : public Qt3DCore::QEntity
{
    Q_OBJECT
public:
    BillboardTextEntity(Qt3DCore::QNode *parent = nullptr)
        : Qt3DCore::QEntity(parent)
        , m_textEntity(new Qt3DExtras::QText2DEntity(this))
        , m_transform(new Qt3DCore::QTransform(this))
    {
        // 设置文本实体
        m_textEntity->setFont(QFont("Arial", 12));
        m_textEntity->setText("Qt mY Text");
        m_textEntity->setColor(Qt::red);
        m_textEntity->setWidth(100);
        m_textEntity->setHeight(100);

        // 添加变换组件
        addComponent(m_transform);

        // 初始位置
        m_transform->setTranslation(QVector3D(0, 0, 0));

        // 初始缩放
        m_transform->setScale(0.1f);
    }

    void setCamera(Qt3DRender::QCamera *camera) {
        m_camera = camera;
        if (m_camera) {
            // 连接相机变化信号
            QObject::connect(m_camera, &Qt3DRender::QCamera::viewMatrixChanged,
                             this, &BillboardTextEntity::updateBillboard);
        }
    }

    void setText(const QString &text) {
        m_textEntity->setText(text);
    }

    void setPosition(const QVector3D &position) {
        m_position = position;
        m_transform->setTranslation(position);
        updateBillboard();
    }

private slots:
    void updateBillboard() {
        if (!m_camera) return;

        // 获取相机视图矩阵
        QMatrix4x4 viewMatrix = m_camera->viewMatrix();

        // 提取相机的旋转部分(去除平移和缩放)
        // 方法1:直接从视图矩阵提取旋转矩阵
        QMatrix4x4 rotationMatrix = viewMatrix;
        rotationMatrix(0, 3) = 0;
        rotationMatrix(1, 3) = 0;
        rotationMatrix(2, 3) = 0;
        rotationMatrix(3, 0) = 0;
        rotationMatrix(3, 1) = 0;
        rotationMatrix(3, 2) = 0;
        rotationMatrix(3, 3) = 1;

        // 转置矩阵得到逆旋转(使文本面向相机)
        rotationMatrix = rotationMatrix.transposed();

        // 设置旋转(只保留y轴旋转,用于2D文本)
        QQuaternion rotation = QQuaternion::fromRotationMatrix(rotationMatrix.toGenericMatrix<3,3>());
        m_transform->setRotation(rotation);
    }

private:
    Qt3DExtras::QText2DEntity *m_textEntity;
    Qt3DCore::QTransform *m_transform;
    Qt3DRender::QCamera *m_camera = nullptr;
    QVector3D m_position;
};

QPlaneMesh + 调整模型旋转实现

注意:QPlaneMesh 是初始的时候是水平面的,所以要叠加一个90°的旋转角。

功能:

1.一直对着相机。

2.可以自己绘制自己想要的信息,不在局限于文本显示。

自定义着色器,GPU绘制

自己重新写着色器,在GPU端更新顶点数据达到效果。

功能:

1.一直面对相机。

2.可以自己绘制自己想要的信息,不在局限于文本显示。

3.自定义着色器,在GPU完成计算。

cpp 复制代码
class DBillboardMaterial : public Qt3DRender::QMaterial
{
    Q_OBJECT
public:
    explicit DBillboardMaterial(Qt3DCore::QNode *parent = nullptr)
        : Qt3DRender::QMaterial(parent)
        , mTexture(new Qt3DRender::QTexture2D(this))
        , mCameraPosition(new Qt3DRender::QParameter("cameraPosition", QVector3D(0, 10.0f, 20.0f), this))
        , mModelPosition(new Qt3DRender::QParameter("modelPosition", QVector3D(0.0f, 1.5f, 0.0f), this))
        , mCameraUp(new Qt3DRender::QParameter("cameraUp", QVector3D(0.0f, 1.5f, 0.0f), this))
    {
        setupTexture();
        setupShader();
        setupEffect();

        // 添加参数
        addParameter(mCameraPosition);
        addParameter(mModelPosition);
        addParameter(mCameraUp);
    }

    void setTextureImage(Qt3DRender::QPaintedTextureImage *image) {
        mTexture->addTextureImage(image);
    }

    void setCameraPosition(const QVector3D &position) {
        mCameraPosition->setValue(position);
    }

    void setCameraUp(const QVector3D &up)
    {
        mCameraUp->setValue(up);
    }

    void setModelPosition(const QVector3D &position) {
        mModelPosition->setValue(position);
    }

private:
    void setupTexture() {
        mTexture->setGenerateMipMaps(false);
        mTexture->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear);
        mTexture->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear);
        mTexture->setWrapMode(Qt3DRender::QTextureWrapMode(Qt3DRender::QTextureWrapMode::ClampToEdge));

        Qt3DRender::QParameter *textureParam = new Qt3DRender::QParameter("tex0", mTexture, this);
        addParameter(textureParam);
    }

    void setupShader() {
        Qt3DRender::QShaderProgram *shaderProgram = new Qt3DRender::QShaderProgram(this);

        shaderProgram->setVertexShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/billboards.vert" ) ) ) );
        shaderProgram->setFragmentShaderCode( Qt3DRender::QShaderProgram::loadSource( QUrl( QStringLiteral( "qrc:/shaders/billboards.frag" ) ) ) );

        // 创建渲染通道
        Qt3DRender::QRenderPass *renderPass = new Qt3DRender::QRenderPass(this);
        renderPass->setShaderProgram(shaderProgram);

        Qt3DRender::QFilterKey *filterKey = new Qt3DRender::QFilterKey;
        filterKey->setName( QStringLiteral( "renderingStyle" ) );
        filterKey->setValue( "forward" );

        // 设置渲染状态(支持透明)
        setupRenderStates(renderPass);

        // 创建技术
        Qt3DRender::QTechnique *technique = new Qt3DRender::QTechnique(this);
        // 设置图形 API
        //setupGraphicsApi(technique);
        technique->addRenderPass(renderPass);
        technique->addFilterKey(filterKey);
        technique->graphicsApiFilter()->setApi( Qt3DRender::QGraphicsApiFilter::OpenGL );
        technique->graphicsApiFilter()->setProfile( Qt3DRender::QGraphicsApiFilter::CoreProfile );
        technique->graphicsApiFilter()->setMajorVersion( 3 );
        technique->graphicsApiFilter()->setMinorVersion( 1 );

        // 创建效果
        mEffect = new Qt3DRender::QEffect(this);
        mEffect->addTechnique(technique);
        setEffect( mEffect );
    }

    void setupRenderStates(Qt3DRender::QRenderPass *renderPass) {
        // 深度测试
        Qt3DRender::QDepthTest *depthTest = new Qt3DRender::QDepthTest(renderPass);
        depthTest->setDepthFunction(Qt3DRender::QDepthTest::Less);
        renderPass->addRenderState(depthTest);

        // Alpha 混合(支持透明)
        Qt3DRender::QBlendEquationArguments *blend = new Qt3DRender::QBlendEquationArguments(renderPass);
        blend->setSourceRgb(Qt3DRender::QBlendEquationArguments::SourceAlpha);
        blend->setDestinationRgb(Qt3DRender::QBlendEquationArguments::OneMinusSourceAlpha);
        renderPass->addRenderState(blend);

        Qt3DRender::QBlendEquation *blendEquation = new Qt3DRender::QBlendEquation(renderPass);
        blendEquation->setBlendFunction(Qt3DRender::QBlendEquation::Add);
        renderPass->addRenderState(blendEquation);

        // 背面剔除(可选)
        Qt3DRender::QCullFace *cullFace = new Qt3DRender::QCullFace(renderPass);
        cullFace->setMode(Qt3DRender::QCullFace::Back);
        renderPass->addRenderState(cullFace);
    }

    void setupGraphicsApi(Qt3DRender::QTechnique *technique) {
        Qt3DRender::QGraphicsApiFilter *apiFilter = technique->graphicsApiFilter();
        apiFilter->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL);
        apiFilter->setProfile(Qt3DRender::QGraphicsApiFilter::NoProfile);
        apiFilter->setMajorVersion(3);
        apiFilter->setMinorVersion(1);
    }

    void setupEffect() {
        setEffect(mEffect);
    }

private:
    Qt3DRender::QTexture2D *mTexture;
    Qt3DRender::QParameter *mCameraPosition;
    Qt3DRender::QParameter *mModelPosition;
    Qt3DRender::QParameter *mCameraUp;
    Qt3DRender::QEffect *mEffect;
};
相关推荐
用户805533698032 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner2 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz7 天前
QML Hello World 入门示例
qt
xcyxiner10 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner11 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner11 天前
DicomViewer (添加模型类)3
qt
xcyxiner12 天前
DicomViewer (目录调整) 2
qt
xcyxiner12 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
桥田智能14 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构
森G14 天前
75、服务器源码解析---------云视频服务项目
linux·服务器·网络·c++·qt