간단한 RHI 위젯 예제
Qt의 3D API와 셰이딩 언어 추상화 레이어인 QRhi 를 사용하여 삼각형을 렌더링하는 방법을 보여줍니다.
간단한 RHI 위젯 예제의 스크린샷
이 예제는 여러 가지 면에서 QWidget 의 RHI 창 예제에 대응하는 예제입니다. 이 애플리케이션의 QRhiWidget 서브클래스는 기본 버텍스 및 프래그먼트 셰이더가 포함된 간단한 그래픽 파이프라인을 사용하여 단일 삼각형을 렌더링합니다. 일반적인 QWindow 기반 애플리케이션과 달리 이 예제에서는 창과 QRhi 을 설정하거나 스왑체인 및 창 이벤트를 처리하는 것과 같은 하위 수준의 세부 사항은 QWidget 프레임워크에서 처리하므로 걱정할 필요가 없습니다. QRhiWidget 서브클래스의 인스턴스는 QVBoxLayout 에 추가됩니다. 예제를 최소화하고 간결하게 유지하기 위해 추가 위젯이나 3D 콘텐츠는 도입되지 않았습니다.
QRhiWidget 서브클래스인 ExampleRhiWidget
의 인스턴스가 최상위 위젯의 하위 계층 구조에 추가되면 해당 창은 자동으로 Direct 3D, Vulkan, Metal 또는 OpenGL 렌더링 창이 됩니다. QPainter -렌더링된 위젯 콘텐츠, 즉 QRhiWidget, QOpenGLWidget, QQuickWidget 이 아닌 모든 콘텐츠는 텍스처에 업로드되는 반면, 앞서 언급한 특수 위젯은 각각 텍스처에 렌더링됩니다. 결과물인 textures 세트는 최상위 위젯의 백킹스토어에 의해 함께 합성됩니다.
구조와 main()
main()
함수는 매우 간단합니다. 최상위 위젯의 기본 크기는 720p입니다(이 크기는 논리 단위로, 실제 픽셀 크기는 scale factor. 창 크기는 조정 가능합니다. QRhiWidget 을 사용하면 창 크기 또는 레이아웃 변경으로 인한 위젯의 크기 조정을 올바르게 처리하는 서브클래스를 간단하게 구현할 수 있습니다.
int main(int argc, char **argv) { QApplication app(argc, argv); ExampleRhiWidget *rhiWidget = new ExampleRhiWidget; QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(rhiWidget); QWidget w; w.setLayout(layout); w.resize(1280, 720); w.show(); return app.exec(); }
QRhiWidget 서브클래스는 initialize() 및 render()라는 두 개의 가상을 재구현합니다. 초기화()는 렌더() 전에 적어도 한 번 호출되지만 위젯 크기 변경으로 인해 위젯의 배경 텍스처가 다시 생성되거나 렌더 대상 파라미터가 변경되거나 새 최상위 창으로 이동하여 위젯이 새 QRhi 로 변경되는 등 여러 가지 중요한 변경 사항이 있을 때도 호출됩니다.
참고: QOpenGLWidget 의 기존 initializeGL
- resizeGL
- paintGL
모델과 달리 QRhiWidget 에는 두 개의 가상만 있습니다. 이는 단순히 크기를 조정하는 것보다 다른 최상위 창으로 리패런싱할 때와 같이 처리해야 할 특별한 이벤트가 더 많기 때문입니다. (강력한 QOpenGLWidget 구현은 관련 QOpenGLContext 수명을 추적하는 등 추가적인 장부 관리를 수행하여 이 문제를 처리해야 했기 때문에 3개의 가상으로는 실제로 충분하지 않았습니다.) 중요한 변경 시 initialize
이 다시 호출되는 더 간단한 initialize
- render
쌍이 이에 더 적합할 수 있습니다.
QRhi 인스턴스는 위젯이 소유하지 않습니다. initialize()
from the base class 에서 쿼리될 것입니다. 멤버로 저장하면 initialize()
이 다시 호출될 때 변경 사항을 인식할 수 있습니다. 그러나 버텍스 및 유니폼 버퍼 또는 그래픽 파이프라인과 같은 그래픽 리소스는 ExampleRhiWidget
의 제어를 받습니다.
#include <QRhiWidget> #include <rhi/qrhi.h> class ExampleRhiWidget : public QRhiWidget { public: ExampleRhiWidget(QWidget *parent = nullptr) : QRhiWidget(parent) { } void initialize(QRhiCommandBuffer *cb) override; void render(QRhiCommandBuffer *cb) override; private: QRhi *m_rhi = nullptr; std::unique_ptr<QRhiBuffer> m_vbuf; std::unique_ptr<QRhiBuffer> m_ubuf; std::unique_ptr<QRhiShaderResourceBindings> m_srb; std::unique_ptr<QRhiGraphicsPipeline> m_pipeline; QMatrix4x4 m_viewProjection; float m_rotation = 0.0f; };
#include <rhi/qrhi.h>
문이 작동하려면 애플리케이션이 GuiPrivate
(또는 qmake를 사용하는 경우 gui-private
)에 링크해야 합니다. QRhi API 제품군의 호환성 약속에 대한 자세한 내용은 QRhi 을 참조하세요.
CMakeLists.txt
target_link_libraries(simplerhiwidget PRIVATE Qt6::Core Qt6::Gui Qt6::GuiPrivate Qt6::Widgets )
렌더링 설정
examplewidget.cpp
에서 위젯 구현은 도우미 함수를 사용하여 .qsb
파일에서 QShader 객체를 로드합니다. 이 애플리케이션은 Qt 리소스 시스템을 통해 실행 파일에 미리 컨디셔닝된 .qsb
파일을 임베드하여 제공합니다. 모듈 종속성 때문에(그리고 여전히 qmake를 지원하기 때문에) 이 예제에서는 편리한 CMake 함수 qt_add_shaders()
를 사용하지 않고 소스 트리의 일부로 .qsb
파일을 제공합니다. 실제 애플리케이션에서는 이를 피하고 Qt Shader Tools 모듈의 CMake 통합 기능(qt_add_shaders
)을 사용하는 것이 좋습니다. 접근 방식에 관계없이 C++ 코드에서 번들/생성된 .qsb
파일의 로딩은 동일합니다.
static QShader getShader(const QString &name) { QFile f(name); return f.open(QIODevice::ReadOnly) ? QShader::fromSerialized(f.readAll()) : QShader(); }
초기화() 구현을 살펴봅시다. 먼저 QRhi 객체를 쿼리하여 나중에 사용할 수 있도록 저장하고 나중에 함수를 호출할 때 비교할 수 있도록 합니다. 불일치가 발생하면(예: 위젯이 창 사이를 이동할 때) 적절한 객체(이 경우 m_pipeline
)를 파괴하고 무효화하여 다시 생성해야 하는 그래픽 리소스를 트리거합니다. 이 예시에서는 창 간 리부모화를 적극적으로 보여주지는 않지만 이를 처리할 수 있도록 준비되어 있습니다. 또한 창 크기를 조정할 때 발생할 수 있는 위젯 크기 변경도 처리할 수 있도록 준비되어 있습니다. 이러한 일이 발생할 때마다 initialize()
가 호출되므로 renderTarget()->pixelSize()
또는 colorTexture()->pixelSize()
를 쿼리하면 항상 최신 픽셀 단위의 최신 크기가 제공되므로 특별한 처리가 필요하지 않습니다. 이 예제에서는 텍스처 형식 변경과 multisample settings 은 기본값(RGBA8 및 멀티샘플 안티앨리어싱 없음)만 사용하기 때문에 준비되지 않은 사항입니다.
void ExampleRhiWidget::initialize(QRhiCommandBuffer *cb) { if (m_rhi != rhi()) { m_pipeline.reset(); m_rhi = rhi(); }
그래픽 리소스를 (재)생성해야 하는 경우 initialize()
는 매우 일반적인 QRhi 기반 코드를 사용하여 이를 수행합니다. 인터리브 위치-컬러 버텍스 데이터가 포함된 단일 버텍스 버퍼로 충분하지만, 모델뷰-투영 행렬은 64바이트(16플로트)의 균일한 버퍼를 통해 노출됩니다. 균일 버퍼는 유일한 셰이더 가시 리소스이며 버텍스 셰이더에서만 사용됩니다. 그래픽 파이프라인은 많은 기본값(예: 깊이 테스트 꺼짐, 블렌딩 비활성화, 컬러 쓰기 활성화, 페이스 컬링 비활성화, 삼각형의 기본 토폴로지 등)에 의존합니다. 버텍스 데이터 레이아웃은 x
, y
, r
, g
, b
따라서 보폭은 5플로트이고 두 번째 버텍스 입력 속성(색상)의 오프셋은 2플로트( x
및 y
건너뛰기)입니다. 각 그래픽 파이프라인은 QRhiRenderPassDescriptor 에 연결되어야 합니다. 이는 기본 클래스에서 관리하는 QRhiRenderTarget 에서 검색할 수 있습니다.
QRhiWidget참고: 이 예제에서는 autoRenderTarget 의 기본값이 true
로 설정되어 있으므로 렌더링 대상을 관리할 필요 없이 renderTarget()를 호출하여 기존 대상을 쿼리하면 됩니다.
if (!m_pipeline) { m_vbuf.reset(m_rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData))); m_vbuf->create(); m_ubuf.reset(m_rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 64)); m_ubuf->create(); m_srb.reset(m_rhi->newShaderResourceBindings()); m_srb->setBindings({ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage, m_ubuf.get()), }); m_srb->create(); m_pipeline.reset(m_rhi->newGraphicsPipeline()); m_pipeline->setShaderStages({ { QRhiShaderStage::Vertex, getShader(QLatin1String(":/shader_assets/color.vert.qsb")) }, { QRhiShaderStage::Fragment, getShader(QLatin1String(":/shader_assets/color.frag.qsb")) } }); QRhiVertexInputLayout inputLayout; inputLayout.setBindings({ { 5 * sizeof(float) } }); inputLayout.setAttributes({ { 0, 0, QRhiVertexInputAttribute::Float2, 0 }, { 0, 1, QRhiVertexInputAttribute::Float3, 2 * sizeof(float) } }); m_pipeline->setVertexInputLayout(inputLayout); m_pipeline->setShaderResourceBindings(m_srb.get()); m_pipeline->setRenderPassDescriptor(renderTarget()->renderPassDescriptor()); m_pipeline->create(); QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); resourceUpdates->uploadStaticBuffer(m_vbuf.get(), vertexData); cb->resourceUpdate(resourceUpdates); }
마지막으로 투영 행렬이 계산됩니다. 이는 위젯 크기에 따라 달라지므로 함수를 호출할 때마다 무조건 수행됩니다.
참고: 모든 크기 및 뷰포트 계산은 컬러 버퍼 역할을 하는 리소스에서 쿼리된 픽셀 크기가 실제 렌더링 대상이기 때문에 항상 픽셀 크기에만 의존해야 합니다. QWidget -보고된 크기 또는 디바이스 픽셀 비율을 기준으로 크기, 뷰포트, 가위 등을 수동으로 계산하지 마세요.
참고: 투영 행렬에는 정규화된 디바이스 좌표의 3D API 차이에 대응하기 위해 QRhi 의 correction matrix 가 포함됩니다. (예: Y 아래 대 Y 위)
z
값이 0인 삼각형이 표시되도록 하기 위해 -4
의 번역이 적용됩니다.
const QSize outputSize = renderTarget()->pixelSize(); m_viewProjection = m_rhi->clipSpaceCorrMatrix(); m_viewProjection.perspective(45.0f, outputSize.width() / (float) outputSize.height(), 0.01f, 1000.0f); m_viewProjection.translate(0, 0, -4); }
렌더링
위젯은 단일 그리기 호출이 포함된 단일 렌더링 패스를 기록합니다.
초기화 단계에서 계산된 뷰-투영 행렬은 모델 행렬과 결합되며, 이 경우 단순 회전이 이루어집니다. 그런 다음 결과 행렬이 균일 버퍼에 기록됩니다. resourceUpdate ()를 수동으로 호출하지 않아도 되는 지름길인 beginPass()로 resourceUpdates
가 전달되는 방식에 주목하세요.
void ExampleRhiWidget::render(QRhiCommandBuffer *cb) { QRhiResourceUpdateBatch *resourceUpdates = m_rhi->nextResourceUpdateBatch(); m_rotation += 1.0f; QMatrix4x4 modelViewProjection = m_viewProjection; modelViewProjection.rotate(m_rotation, 0, 1, 0); resourceUpdates->updateDynamicBuffer(m_ubuf.get(), 0, 64, modelViewProjection.constData());
렌더 패스에서는 3개의 버텍스가 있는 단일 드로우 호출이 기록됩니다. 초기화 단계에서 생성된 그래픽 파이프라인은 명령 버퍼에 바인딩되고 뷰포트는 전체 위젯을 덮도록 설정됩니다. 균일 버퍼를 (버텍스) 셰이더에 표시하기 위해 인자 없이 setShaderResources()를 호출하는데, 이는 파이프라인 생성 시 파이프라인과 연결된 m_srb
을 사용하는 것을 의미합니다. 더 복잡한 렌더링에서는 다른 QRhiShaderResourceBindings 객체를 전달하는 것이 일반적이지만, 파이프라인 생성 시 지정된 것과 동일한 layout-compatible 객체만 있으면 됩니다. 인덱스 버퍼는 없으며 단일 버텍스 버퍼 바인딩이 있습니다( vbufBinding
의 단일 요소는 파이프라인 생성 시 지정한 QRhiVertexInputLayout 의 바인딩 목록에 있는 단일 항목을 참조합니다).
const QColor clearColor = QColor::fromRgbF(0.4f, 0.7f, 0.0f, 1.0f); cb->beginPass(renderTarget(), clearColor, { 1.0f, 0 }, resourceUpdates); cb->setGraphicsPipeline(m_pipeline.get()); const QSize outputSize = renderTarget()->pixelSize(); cb->setViewport(QRhiViewport(0, 0, outputSize.width(), outputSize.height())); cb->setShaderResources(); const QRhiCommandBuffer::VertexInput vbufBinding(m_vbuf.get(), 0); cb->setVertexInput(0, 1, &vbufBinding); cb->draw(3); cb->endPass();
렌더 패스가 기록되면 update()가 호출됩니다. 이는 새 프레임을 요청하며 위젯이 지속적으로 업데이트되고 삼각형이 회전하는 것처럼 보이도록 하는 데 사용됩니다. 렌더링 스레드(이 경우 메인 스레드)는 기본적으로 프레젠테이션 속도에 따라 스로틀됩니다. 이 예제에는 적절한 애니메이션 시스템이 없으므로 매 프레임마다 회전이 증가하므로 새로 고침 빈도가 다른 디스플레이에서 삼각형이 다른 속도로 회전합니다.
update(); }
QRhi, 큐브 RHI 위젯 예제 및 RHI 창 예제도참조하세요 .
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.