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
的新值必须是什么。
有什么想法吗?
main.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();
main.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 )
您还可以选择-1
和1
作为左-右和下-上而不是sceneRoot.w
和sceneRoot.h
。在这种情况下,您必须将纹理平面的大小调整为(2, 2)
。我想绘制用户在纹理上的点击,这就是我选择屏幕尺寸的原因。
附注:不要对nearPlane
和farPlane
使用非常高或非常低的值。它在 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 缩放为与窗口相同的大小(逐像素)?的主要内容,如果未能解决你的问题,请参考以下文章