QtGraphs 개선점 이해 및 활용하기 - 2편

안녕하세요, 다시 방문해주셔서 감사합니다!

본 2편에서는 지난 1편에서 더 나아가 QtGraphs의 구체적인 개선점을 설명하고, 3D 커스텀 아이템에 대해서도 추가적으로 설명합니다. 마지막으로, 파도 최고점 분석 앱의 실습을 마무리 합니다.

지난 편 읽어보기 > Qt Graphs 개선점 이해 및 활용하기 1편

 

 

개요

본 글에서는 QtGraphs의 구조적 개선 사항인 QtGraphs와 Qt Quick3D와의 관계를 설명합니다. Quick3D의 활용으로 인해 취할 수 있는 성능적 안정성, 그리고 QtGraphs에서 발생하는 새로운 기능적 이점에 대해 설명합니다. 또한 1편에서 진행했던 파도 최고점 분석 앱에 3D 커스텀 객체 및 3D 시각효과를 적용해볼 것 입니다.

 

QtGraphs 개선점

기존 Data Visualization 모듈의 렌더링 및 구조

과거 Qt5의 Data Visualization 모듈은 자신만의 독자적인 렌더링 클래스를 보유하고 있었고, 이 클래스는 OpenGL API를 활용하여 렌더링을 수행했었습니다. 때문에 자신의 렌더링 클래스의 기능을 별도로 관리해야 했으며, OpenGL을 활용할 수 없는 환경에서는 성능적인 제한이 있었습니다. 또한 3D 시각적 효과를 적용하는데 있어서도 별도의 수작업을 필요로 했었습니다. 물론 이런 특성이 단점만 있는 것은 아닙니다. 모듈의 요구사항에 맞춰 편리하게 자체 기능을 개발할 수 있다는 장점도 있기는 합니다.

아래 다이어그램은 Qt 5의 Data Visualization 모듈이 렌더링을 수행하는 방식을 나타내고 있습니다.

 

image-2025-1-10_17-48-3

 

요약하면, Data Visualization 모듈에서는 자체 독립적인 렌더링 코드의 존재로 인해 코드의 중복 및 유지보수의 문제, 그리고 OpenGL에 대한 렌더링 의존성으로 인해 야기되는 문제점들을 보유하고 있었습니다.

 

QtGraphs의 Qt Quick3D를 활용한 구조 개선

Qt 6의 QtGraphs 모듈부터는 자체 렌더링 클래스를 더 이상 사용하지 않고, 렌더링 백엔드로 Quick3D 모듈을 사용하도록 재설계 되었습니다. 즉, QtGraphs의 View는 Quick3D 모듈의 ViewPort를 상속하여 ViewPort가 보유한 기능과 특성을 상속받게 되었습니다. 이에 따라 아래와 같이 기능의 동작 방식이 변경되었습니다:

  • RHI 기반의 Scene Graph 렌더링 수행
  • SceneEnvironment, Light Control, Camera 등의 기존/신규 기능이 Quick3D 방식으로 처리됨

 

내부 변경점을 모른다면 Qt6의 QtGraph 모듈은 사용자 입장에서 단순한 기능 통합/추가 정도로 느껴질 수 있습니다. 하지만 내부 변경점을 이해한다면 기술적으로 큰 구조 개선이 이루어졌다는 것을 느낄 수 있을 것입니다. 아래는 Qt 6에서의 QtGraphs와 Quick3D의 관계를 다이어그램으로 나타낸 것입니다.

 

image-2025-1-13_10-3-0

 

한 가지 간단한 예제를 들어보겠습니다. 우리는 이제 QtGraphs가 Quick3D를 활용한다는 것을 알고 있습니다. 이로 인해 QtGraphs에서 가능한 것 중 하나는, 3DGraph에 Quick3D에서 적용 가능한 속성 중 하나인 SceneEnivronment를 적용할 수 있다는 것입니다. 즉, 아래처럼 우리는 Graph에 SceneEnvironment를 적용하여 Tonemap을 적용할 수 있습니다.

example.ui
Surface3D {
    id: surfaceGraph
 
    environment: ExtendedSceneEnvironment {
        tonemapMode: ExtendedSceneEnvironment.TonemapModeFilmic
    }
    ...
}

 

번외

QtGraph 3D에서 커스텀 아이템 추가하기

QtGraphs에서 제공하는 기본 요소들만으로 데이터를 시각하기에 부족한 상황이 올 수 있습니다. 예를 들어서 Graph에 자신만의 특별한 Marker를 추가하여 데이터의 시각화를 보완하거나 커스터마이징 하고 싶을 것 입니다. 이런 상황에서 우리는 Custom3DItem이라는 요소를 통해 자신만의 3D 객체를 Graph에 추가할 수 있습니다. 커스텀 3D 모델을 사용하려면 Qt Framework가 해석 가능한 3D object mesh file이 필요합니다. 보통 Maya, 3ds Max, 또는 Blender와 같은 툴을 통해 3D asset을 생성하지만, 이 툴들을 사용하여 생성한 결과물이 바로 Qt Framework에 사용되지 못할 수 있습니다. 이를 위해 Qt Balsam이라는 툴이 존재하며 Qt가 해석가능하도록 3D asset을 변환시켜줍니다.

위 과정을 통해 Custom3DItem에 mesh file을 적용하여 커스텀 3D 객체를 Graph에 추가할 수 있습니다. 이렇게 추가된 3D 커스텀 객체는 아래 다이어그램과 같이 SceneGraph로 해석된 후 렌더링됩니다. Custom3DItem 외에도, Qt에서는 이를 상속하여 아래와 같이 더 구체적인 타입들을 제공하고 있습니다:

  • Custom3DLabel: 커스텀 라벨 객체
  • Custom3DVolume: 3D 텍스쳐 적용 가능한 객체

image-2025-1-13_11-9-23

 

코딩 실습으로 개념 익히기

지난 실습에서의 나머지 단계를 진행하며 파도 최고점 앱을 완성해보겠습니다. 본 편에서는 3단계와 4단계를 진행합니다. 🌊

  1. 그래프 모델 생성하기 (1편)
  2. 그래프 뷰 생성하기 (1편)
  3. 커스텀 3D 객체 추가해보기 (2편)
  4. 3D 시각 효과 적용해보기 (2편)
 
커스텀 3D 객체 추가하기

이제 파도의 최고점을 나타내기 위해 3D 부표 객체를 추가하고, 또 파도의 높이를 표기할 라벨을 함께 추가해봅시다. 우리는 Custom3DItem과 Custom3DLabel을 사용하여 이를 나타낼 것입니다. Surface3D는 customItemList라는 속성을 가지고 있습니다. 이 속성에 커스텀 객체들을 추가하여 그래프에 나타낼 수 있습니다. Custom3DItem에 우리가 원하는 모양의 객체을 만들기 위해서, 우리는 메쉬 obj 파일을 생성해야 합니다. 이를 위해 Blender라는 툴을 사용하여 매우 간단한 도넛 모양의 메쉬 파일을 생성하고, 이 메쉬 파일을 Qt Balsam 툴을 통해 컨버팅하여 obj 파일을 만들었습니다. 마지막으로, 부표의 색상과 유사한 텍스쳐 파일을 Custom3DItem에 적용하여 부표와 같이 보이도록 하였습니다.

아래의 코드를 참조해주세요.

SeaSurfaceForm.ui
Surface3D {
    id: seaSurfaceGraph
 
    ...
 
    customItemList: [
        Custom3DItem {
            id: markerId
            meshFile: "SeaAnalysis/mesh/buoy.obj"
            textureFile: "SeaAnalysis/mesh/coral.jpg"
            scaling: Qt.vector3d(0.15, 0.15, 0.15)
            visible: false // Start hidden
        },
        Custom3DLabel {
            id: labelId
            backgroundColor: "black"
            textColor: "orange"
            font.bold: true
            visible: false
            scaling: Qt.vector3d(1.5, 1.5, 1.5)
        }
    ]
}
 

이제 부표와 라벨의 위치를 갱신하는 코드를 작성할 차례입니다. 이를 위해 우리의 WaveDataProxy에 highestPeakChanged라는 시그널을 새로 정의하고, 가장 높은 파도의 위치가 갱신될때 해당 위치로 부표와 라벨을 이동시켜주는 코드를 작성하였습니다. 부표와 라벨이 보다 자연스럽게 움직이도록, Vector3dAnimation을 적용하여 위치를 갱신하도록 하였습니다.

SeaSurface.qml
SeaSurfaceForm {
 
    ...
 
    Connections {
        target: waveDataProxyId
        function onHighestPeakChanged () {
            markerId.visible = true;
            labelId.visible = true;
 
            // Animate the marker's position
            markerPositionAnimation.from = markerId.position
            markerPositionAnimation.to = waveDataProxyId.highestPeakPos
            markerPositionAnimation.running = true
 
            // Animate the label's position
            labelPositionAnimation.from = labelId.position
            labelPositionAnimation.to = waveDataProxyId.highestPeakLabelPos
            labelPositionAnimation.running = true
 
            labelId.text = " Highest Peak: " + waveDataProxyId.highestPeakPos.y.toFixed(1) + " M ";
        }
    }
 
    Vector3dAnimation {
        id: markerPositionAnimation
        target: markerId
        property: "position"
        duration: 150
    }
 
    Vector3dAnimation {
        id: labelPositionAnimation
        target: labelId
        property: "position"
        duration: 150
    }
}

 

이렇게 Custom3DItem, Custom3DLabel, 이벤트 슬롯, 그리고 Vector3dAnimation을 적용하여 실행하면 아래와 같이 가장 높은 파도를 마킹하는 부표를 확인할 수 있습니다. ring buoy

image-2025-1-24_16-25-52

 
3D 시각 효과 적용하기

앞서 우리는 QtGraphs가 Quick3D를 활용하여 더 많은 시각 효과들을 적용할 수 있다는 것을 확인했었습니다. 또한 이러한 시각적 효과들이 RHI에 의해 성능적으로 더 안정적으로 수행될 수 있다는 것도 알 수 있었습니다. 우리는 theme, texture, 그리고 scene environment와 같은 시각 효과들을 적용하여 파도를 보다 더 현실감있게 만들 수 있습니다. 가장 먼저 텍스쳐를 Surface3D에 적용하면 아래와 같은 결과를 확인할 수 있습니다.

Surface3DSeries {
    id: surfaceSeriesId
 
    drawMode: Surface3DSeries.DrawSurface
    textureFile: "SeaAnalysis/image/ocean_texture.jpg"
}

 

래는 위 텍스쳐를 적용하여 실행한 결과입니다.

image-2025-1-24_16-29-40

 

또한 우리는 좌표를 지우고, Quick3D 활용을 통해 가능해진 SceneEnvironment의 glow 효과 등을 적용하여 밤바다와 같은 효과도 표현할 수 있을 것입니다.

아래는 이러한 시각 효과를 Surface3D에 적용하는 코드입니다.

Surface3D {
    id: seaSurfaceGraph
 
    anchors.fill: parent
 
    theme : GraphsTheme {
        id: surfaceTheme
        colorScheme: Qt.Dark
        plotAreaBackgroundVisible: false
        labelsVisible: false
        gridVisible: false
    }
 
    environment: ExtendedSceneEnvironment {
        backgroundMode: ExtendedSceneEnvironment.Color
        clearColor: "black"
        tonemapMode: ExtendedSceneEnvironment.TonemapModeNone
        adjustmentContrast: 4
        glowEnabled: true
        glowStrength: 1.2
        glowIntensity: 0.8
        glowBloom: 0.9
        glowUseBicubicUpscale: true
        glowLevel: ExtendedSceneEnvironment.GlowLevel.One
                   | ExtendedSceneEnvironment.GlowLevel.Two
                   | ExtendedSceneEnvironment.GlowLevel.Three
                   | ExtendedSceneEnvironment.GlowLevel.Four
    }
 
    ...
}

 

Surface3D의 그리드와 라벨이 이제 숨겨졌으며, SceneEnvironment의 glow 속성을 활용해 라이트 효과가 추가되었습니다. 우리는 QtGraphs를 사용하여 밤바다의 파도를 나타내고, 파도의 최고점을 시각화 해보았습니다! water wave

전체 연습 코드가 궁금하다면 여기를 참조하여 주세요.

app_sample (1)

 

정리하며

이번 블로그 시리즈에서는 Qt 6.8에 새롭게 도입된 QtGraphs 모듈의 기본 개념과 개선점 및 장점들을 살펴보았습니다. QtGraphs에 적용된 모델/뷰 프로그래밍 개념을 살펴보고, 과거의 구조와 개선된 현재 구조를 비교해 보았습니다. 또한, QtGraphs가 Quick3D를 활용하여 Rendering Hardware Interface(RHI) 등의 이점을 어떻게 활용 수 있는지 알아보았습니다. 더불어, 3D 데이터 작업이 2D 데이터와 비교하여 가지는 복잡성과 3D 그래프를 위한 모델을 설계할 때 Data Proxy와 같이 추가적으로 고려해야 할 사항들을 탐구했습니다. 마지막으로, 이 글에서 다룬 개념들을 적용하여 QtGraphs를 사용한 간단한 파도 최고점 분석 앱을 만들어 보았습니다.

QtGraphs는 배워야 할 내용과 연습할 것이 많은 방대한 주제입니다. 이 글이 QtGraphs를 적극적으로 사용하지 않는 상황이라도 QtGraphs와 그 장점에 대해 더 익숙해지는데 도움이 되었기를 바랍니다.

읽어주셔서 감사합니다. 다음에도 흥미로운 주제로 다시 찾아뵙겠습니다.

 

더 자세히 알고 싶으신가요?

무료 10일 평가판을 통해 Qt Software를 직접 경험해보세요!

Qt 6 무료 체험하기

Qt 전문가에게 문의하기

 


Blog Topics:

Comments