Qt3D:如何将 Scene2D 缩放为与窗口相同的大小(逐像素)?

Posted

技术标签:

【中文标题】Qt3D:如何将 Scene2D 缩放为与窗口相同的大小(逐像素)?【英文标题】:Qt3D: How to scale a Scene2D to be the same size as the window (pixel-wise)? 【发布时间】:2020-11-19 00:29:33 【问题描述】:

我创建了一个带有 800x600 窗口的 C++ 应用程序,它使用 Qt Quick 2 元素以及 Qt 3D 对象成功地在 QML 中绘制了一些对象:

QML 代码使用 Qt Quick 2 Rectangle 元素在 Scene2D 内绘制几个绿色/黄色矩形。然后将 2D 场景传送到 3D 立方体的其中一个表面以进行渲染并在 3D 世界中显示。最后,来自 Qt 3D 的蓝色 SphereMesh 被渲染在中心,如上面的屏幕截图所示。

我一直在尝试调整 3D 立方体(2D UI 被渲染到的位置)的大小,使其与窗口的大小相同,但我找不到以编程方式执行此操作的方法:

所以问题是如何调整或缩放 3D 立方体,使其自动调整为与窗口大小相同?

我正在寻找一种解决方案,允许立方体具有与窗口相同数量的像素。例如,在一个 800x600 的窗口上,我希望看到一个 800x600 的绿色矩形。

这是我尝试过的:我可以手动调整camZ 的值,即Camera 与3D 世界中心的距离,有点眼花缭乱,但这不是一个精确的解决方案:如果窗口稍后更改为不同的尺寸,我需要再次进行大量测试以确定camZ 的新值必须是什么。

有什么想法吗?

ma​​in.cpp

#include <QGuiApplication>
#include <QQmlContext>

#include <Qt3DQuickExtras/qt3dquickwindow.h>
#include <Qt3DQuick/QQmlAspectEngine>


int main(int argc, char **argv)

    QGuiApplication app(argc, argv);

    Qt3DExtras::Quick::Qt3DQuickWindow view;
    view.setSource(QUrl("qrc:/main.qml"));
    auto rootContext = view.engine()->qmlEngine()->rootContext();
    rootContext->setContextProperty("_window", &view);
    view.resize(800, 600);
    view.show();

    return app.exec();

ma​​in.qml

import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12

import QtQuick 2.0
import QtQuick.Scene2D 2.9
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2

Entity

    id: sceneRoot
    property int w: _window.width
    property int h: _window.height
    property real camZ: 1000

    /* setup camera */

    Camera 
        id: mainCamera
        projectionType: CameraLens.PerspectiveProjection
        fieldOfView: 45
        aspectRatio: _window.width / _window.height
        nearPlane:   0.01
        farPlane: 1000000.0
        position:    Qt.vector3d( 0.0, 0.0, sceneRoot.camZ )
        viewCenter:  Qt.vector3d( 0.0, 0.0, 0.0 )
        upVector:    Qt.vector3d( 0.0, 1.0, 0.0 )
    

    components: [
        RenderSettings 
            activeFrameGraph: ForwardRenderer 
                camera: mainCamera
                clearColor: "white"
            
            pickingSettings.pickMethod: PickingSettings.TrianglePicking
        ,

        InputSettings 
    ]

    /* setup a 3D cube to be used as the 2D drawing surface for all Qt Quick 2 stuff */

    Entity 
        id: drawingSurface

        CuboidMesh 
            id: planeMesh
        

        Transform 
            id: planeTransform
            translation: Qt.vector3d(0, 0, 0)
            scale3D: Qt.vector3d(sceneRoot.w, sceneRoot.h, 1)
        

        TextureMaterial 
            id: planeMaterial
            texture: offscreenTexture  // created by qmlTexture below
        

        // picked up by Scene2D’s "entities" property and used as a source for events
        ObjectPicker 
            id: planePicker
            hoverEnabled: false
            dragEnabled: false
        

        components: [ planeMesh, planeMaterial, planeTransform, planePicker ]
    

    /* setup Scene2D offscreen texture to be used as canvas by Qt Quick 2 */

    Scene2D 
        id: qmlTexture
        output: RenderTargetOutput 
            attachmentPoint: RenderTargetOutput.Color0
            texture: Texture2D 
                id: offscreenTexture
                width: sceneRoot.w
                height: sceneRoot.h
                format: Texture.RGBA8_UNorm
                generateMipMaps: true
                magnificationFilter: Texture.Linear
                minificationFilter: Texture.LinearMipMapLinear
                wrapMode 
                    x: WrapMode.ClampToEdge
                    y: WrapMode.ClampToEdge
                
            
        

        mouseEnabled: false
        entities: [ drawingSurface ]

        /* Qt Quick 2 rendering */

        Rectangle 
            width: offscreenTexture.width
            height: offscreenTexture.height
            x: 0
            y: 0
            border.color: "red"
            color: "green"

            Component.onCompleted: 
                console.log("Outter rectangle size: " + width + "x" + height + " at " + x + "," + y);
            

            Rectangle 
                id: innerRect
                height: parent.height*0.6
                width: height
                x: (parent.width/2) - (width/2)
                y: (parent.height/2) - (height/2)
                border.color: "red"
                color: "yellow"
                transform: Rotation  origin.x: innerRect.width/2; origin.y: innerRect.height/2; angle: 45

                Component.onCompleted: 
                    console.log("Inner rectangle size: " + width + "x" + height + " at " + x + "," + y);
                
            
        

     // Scene2D

    /* add light source at the same place as the camera */

    Entity 
        PointLight 
            id: light
            color: "white"
            intensity: 1
            constantAttenuation: 1.0
            linearAttenuation: 0.0
        

        Transform 
            id: lightTransform
            translation: Qt.vector3d(0.0, 0.0, sceneRoot.camZ)
        

        components: [ light, lightTransform ]
    

    /* display 3D object */

    Entity 
        SphereMesh 
            id: mesh
            radius: 130
        

        PhongMaterial 
            id: material
            ambient: "blue"
        

        Transform 
           id: transform
           translation: Qt.vector3d(0, 0, 0)
       

       components: [ mesh, material, transform ]
   

 // sceneRoot

将这些模块添加到您的 .pro 文件中:

QT += qml quick 3dquick 3dquickextras

【问题讨论】:

【参考方案1】:

通常,当您希望纹理覆盖整个屏幕时,您使用orthographic projection。与透视投影相反,物体在屏幕上总是以相同的尺寸出现,无论它们与相机的距离如何。这种类型的投影通常用于可视化建筑物等的 3D 平面图或以 3D 呈现 UI 元素。

现在的想法是你必须为分支绘制框架:

    绘制背景图片 绘制所有对象
                     RenderSurfaceSelector
                                |
                             Viewport
                                |
          -------------------------------------------
          |             |             |             |
     ClearBuffers  LayerFilter   ClearBuffers  LayerFilter
          |             |             |             |
        NoDraw    CameraSelector    NoDraw    CameraSelector

第一个(从左到右)清除缓冲区会清除所有缓冲区。第一层过滤器过滤器用于背景层(您必须附加到背景实体)。第二个清除缓冲区仅清除深度(以便明确绘制对象)。第二层过滤器过滤主层(您必须将其附加到要绘制的所有对象)。

然后创建背景相机并将其投影类型设置为正交投影:

Camera 
        id: backgroundCamera
        projectionType: CameraLens.OrthographicProjection
        fieldOfView: 45
        aspectRatio: sceneRoot.w / sceneRoot.h
        left: - sceneRoot.w / 2
        right: sceneRoot.w / 2
        bottom: - sceneRoot.h / 2
        top: sceneRoot.h / 2
        nearPlane:   0.1
        farPlane:    1000.0
        position:    Qt.vector3d( 0.0, 0.0, 1.0 )
        viewCenter:  Qt.vector3d( 0.0, 0.0, 0.0 )
        upVector:    Qt.vector3d( 0.0, 1.0, 0.0 )

您还可以选择-11 作为左-右和下-上而不是sceneRoot.wsceneRoot.h。在这种情况下,您必须将纹理平面的大小调整为(2, 2)。我想绘制用户在纹理上的点击,这就是我选择屏幕尺寸的原因。

附注:不要对nearPlanefarPlane 使用非常高或非常低的值。它在 Qt3D 文档(某处,现在找不到)中说,当远平面设置为更大的 100.000 时,将发生不准确。此外,如果您将其设置得太小,也会发生同样的情况。你可以在网上看,这是 3D 计算机图形中的一个普遍问题。

好吧,这是完整的代码:

import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12

import QtQuick 2.0
import QtQuick.Scene2D 2.9
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.2

Entity

    id: sceneRoot
    property int w: _window.width
    property int h: _window.height
    property real camZ: 1000

    components: [
        RenderSettings 
            activeFrameGraph:  RenderSurfaceSelector 
                id: surfaceSelector

                Viewport 
                    id: mainViewport
                    normalizedRect: Qt.rect(0, 0, 1, 1)

                    ClearBuffers 
                        buffers: ClearBuffers.ColorDepthBuffer
                        clearColor: Qt.rgba(0.6, 0.6, 0.6, 1.0)

                        NoDraw 
                            // Prevent drawing here, we only want to clear the buffers
                        
                    

                    LayerFilter 
                        id: backgroundLayerFilter

                        layers: [backgroundLayer]

                        CameraSelector 
                            id: backgroundCameraSelector
                            camera: backgroundCamera
                        
                    

                    ClearBuffers 
                        buffers: ClearBuffers.DepthBuffer

                        NoDraw 
                            // Prevent drawing here, we only want to clear the buffers
                        
                    

                    LayerFilter 
                        id: mainLayerFilter

                        layers: [mainLayer]

                        CameraSelector 
                            id: mainCameraSelector
                            camera: mainCamera
                        
                    
                
            
            pickingSettings.pickMethod: PickingSettings.TrianglePicking
        ,

        InputSettings 
    ]

    Camera 
        id: mainCamera
        projectionType: CameraLens.PerspectiveProjection
        fieldOfView: 45
        aspectRatio: _window.width / _window.height
        nearPlane:   0.1
        farPlane:    1000.0
        position:    Qt.vector3d( 0.0, 0.0, camZ )
        viewCenter:  Qt.vector3d( 0.0, 0.0, 0.0 )
        upVector:    Qt.vector3d( 0.0, 1.0, 0.0 )
    

    /* setup camera */

    Camera 
        id: backgroundCamera
        projectionType: CameraLens.OrthographicProjection
        fieldOfView: 45
        aspectRatio: sceneRoot.w / sceneRoot.h
        left: - sceneRoot.w / 2
        right: sceneRoot.w / 2
        bottom: - sceneRoot.h / 2
        top: sceneRoot.h / 2
        nearPlane:   0.1
        farPlane:    1000.0
        position:    Qt.vector3d( 0.0, 0.0, 1.0 )
        viewCenter:  Qt.vector3d( 0.0, 0.0, 0.0 )
        upVector:    Qt.vector3d( 0.0, 1.0, 0.0 )
    

    /* setup a 3D cube to be used as the 2D drawing surface for all Qt Quick 2 stuff */

    Entity 
        id: drawingSurface

        PlaneMesh 
            id: planeMesh
            width: sceneRoot.w
            height: sceneRoot.h
        

        Transform 
            id: planeTransform
            translation: Qt.vector3d(0, 0, 0)
            rotationX: 90
        

        TextureMaterial 
            id: planeMaterial
            texture: offscreenTexture  // created by qmlTexture below
        

        Layer 
            id: backgroundLayer
        

        // picked up by Scene2D’s "entities" property and used as a source for events
        ObjectPicker 
            id: planePicker
            hoverEnabled: false
            dragEnabled: false
        

        components: [ planeMesh, planeMaterial, planeTransform, planePicker, backgroundLayer ]
    

    /* setup Scene2D offscreen texture to be used as canvas by Qt Quick 2 */

    Scene2D 
        id: qmlTexture
        output: RenderTargetOutput 
            attachmentPoint: RenderTargetOutput.Color0
            texture: Texture2D 
                id: offscreenTexture
                width: sceneRoot.w
                height: sceneRoot.h
                format: Texture.RGBA8_UNorm
                generateMipMaps: true
                magnificationFilter: Texture.Linear
                minificationFilter: Texture.LinearMipMapLinear
                wrapMode 
                    x: WrapMode.ClampToEdge
                    y: WrapMode.ClampToEdge
                
            
        

        mouseEnabled: false
        entities: [ drawingSurface ]

        /* Qt Quick 2 rendering */

        Rectangle 
            width: offscreenTexture.width
            height: offscreenTexture.height
            x: 0
            y: 0
            border.color: "red"
            color: "green"

            Component.onCompleted: 
                console.log("Outter rectangle size: " + width + "x" + height + " at " + x + "," + y);
            

            Rectangle 
                id: innerRect
                height: parent.height*0.6
                width: height
                x: (parent.width/2) - (width/2)
                y: (parent.height/2) - (height/2)
                border.color: "red"
                color: "yellow"
                transform: Rotation  origin.x: innerRect.width/2; origin.y: innerRect.height/2; angle: 45

                Component.onCompleted: 
                    console.log("Inner rectangle size: " + width + "x" + height + " at " + x + "," + y);
                
            
        

     // Scene2D

    /* add light source at the same place as the camera */

    Layer 
        id: mainLayer
    

    Entity 
        PointLight 
            id: light
            color: "white"
            intensity: 1
            constantAttenuation: 1.0
            linearAttenuation: 0.0
        

        Transform 
            id: lightTransform
            translation: Qt.vector3d(0.0, 0.0, sceneRoot.camZ)
        

        components: [ light, lightTransform, mainLayer ]
    

    /* display 3D object */

    Entity 
        SphereMesh 
            id: mesh
            radius: 130
        

        PhongMaterial 
            id: material
            ambient: "blue"
        

        Transform 
           id: transform
           translation: Qt.vector3d(0, 0, 0)
       

       components: [ mesh, material, transform, mainLayer ]
   

 // sceneRoot

结果截图:

顺便说一句:由于在屏幕外表面上绘图,您的代码会产生错误的结果。我建议你创建和实际的屏幕外渲染帧图并在那里绘制你的东西。结帐this very nice and informative GitHub repo 和my C++ Qt3D offscreen renderer implementation。

也许附带说明:您绝对可以通过使用透视投影来获得相同的结果。您可以阅读互联网上的透视投影,例如here。本质上,您有一个线性问题系统,您知道像素坐标(您希望平面出现在屏幕上的位置)并求解平面的 3D 点。但它可能会变得复杂,我确信我发布的解决方案更易于使用;)

【讨论】:

太棒了!我最初尝试使用 Ortho 投影,但无论出于何种原因,我都无法让它工作,并且仍然用透视渲染事物。当您添加答案时,我正要完成从 2D 屏幕坐标到 3D 世界的计算,thx vm!你给了我们足够的材料来学习。人,这个答案值得更多的支持! 很高兴能帮上忙 :)

以上是关于Qt3D:如何将 Scene2D 缩放为与窗口相同的大小(逐像素)?的主要内容,如果未能解决你的问题,请参考以下文章

Qt3D:根据实体和相机之间的距离缩放实体大小

如何将数据框拆分为与 R 大小相同的元素,由另一个大小重叠,保留每个元素?

qt3d窗口内的Qt小部件

如何在标准GUI应用程序中使用Qt3D进行渲染?

如何使用qml qt3d(qt)将对象旋转一个角度?

如何将元素定义为与随机访问迭代器指向的元素相同的类型?