프로그래밍 가능한 머티리얼, 이펙트, 지오메트리 및 텍스처 데이터
Qt Quick 3D, DefaultMaterial 및 PrincipledMaterial 의 기본 제공 머티리얼은 속성을 통해 다양한 수준의 사용자 정의가 가능하지만 버텍스 및 조각 셰이더 수준에서는 프로그래밍 기능을 제공하지 않습니다. 이를 허용하기 위해 CustomMaterial 유형이 제공됩니다.
다음을 포함하는 모델 PrincipledMaterial | 버텍스를 변형하는 CustomMaterial |
---|---|
View3D 의 출력이 Qt Quick 으로 전달되기 전에 선택적으로 깊이 버퍼를 고려하여 컬러 버퍼에서 하나 이상의 처리 패스가 수행되는 포스트 프로세싱 효과도 두 가지 종류가 있습니다:
- 글로우/블룸, 피사계 심도, 비네트, 렌즈 플레어와 같은 ExtendedSceneEnvironment 를 통해 구성할 수 있는 내장 후처리 단계,
custom
조각 셰이더 코드의 형태로 애플리케이션에서 구현되는 효과와 Effect 객체의 처리 패스 사양입니다.
실제로는 3D 렌더러의 개입 없이 View3D 항목의 출력에서 작동하는 Qt Quick 을 통해 구현된 2D 효과라는 세 번째 포스트 프로세싱 효과 범주가 있습니다. 예를 들어 View3D 항목에 블러를 적용하려면 Qt Quick 의 기존 기능(예: MultiEffect)을 사용하는 것이 가장 간단합니다. 3D 포스트 프로세싱 시스템은 뎁스 버퍼나 화면 텍스처와 같은 3D 씬 개념이 포함되거나 HDR 톤 매핑을 처리해야 하거나 중간 버퍼를 사용한 여러 패스가 필요한 복잡한 효과에 유용합니다. 3D 씬과 렌더러에 대한 인사이트가 필요하지 않은 간단한 2D 효과는 항상 ShaderEffect 또는 MultiEffect 을 사용하여 구현할 수 있습니다.
효과가 없는 씬 | 커스텀 포스트 프로세싱 효과가 적용된 동일한 씬 |
---|---|
프로그래밍 가능한 머티리얼과 포스트 프로세싱 외에도 일반적으로 파일 형태로 제공되는 데이터에는 두 가지 유형이 있습니다(.mesh
파일 또는 .png
과 같은 이미지):
- 렌더링할 메시의 지오메트리, 텍스처 좌표, 노멀, 색상 및 기타 데이터를 포함한 버텍스 데이터,
- 렌더링된 오브젝트의 텍스처 맵으로 사용되거나 스카이박스 또는 이미지 기반 조명과 함께 사용되는 텍스처의 콘텐츠.
원하는 경우 애플리케이션은 이러한 데이터를 C++에서 QByteArray. 이러한 데이터는 시간이 지남에 따라 변경될 수 있으므로 절차적으로 생성하고 나중에 Model 또는 Texture.
C++에서 버텍스 데이터를 동적으로 지정하여 렌더링한 그리드입니다. | C++에서 생성된 이미지 데이터로 텍스처링된 큐브 |
---|---|
머티리얼, 효과, 지오메트리 및 텍스처를 사용자 지정하고 동적으로 만드는 이 네 가지 접근 방식은 셰이더가 입력으로 받는 데이터의 셰이딩 및 절차적 생성을 프로그래밍할 수 있게 해줍니다. 다음 섹션에서는 이러한 기능에 대한 개요를 제공합니다. 전체 참조는 각 유형에 대한 문서 페이지에서 확인할 수 있습니다:
기능 | 참조 문서 | 관련 예제 |
---|---|---|
커스텀 머티리얼 | CustomMaterial | Qt Quick 3D -커스텀 셰이더 예제, Qt Quick 3D - 커스텀 머티리얼 예제 |
커스텀 포스트 프로세싱 효과 | Effect | Qt Quick 3D - 커스텀 이펙트 예제 |
커스텀 지오메트리 | QQuick3DGeometry, Model::geometry | Qt Quick 3D - 커스텀 지오메트리 예제 |
커스텀 텍스처 데이터 | QQuick3DTextureData, Texture::textureData | Qt Quick 3D - 프로시저럴 텍스처 예제 |
머티리얼 프로그래밍 기능
큐브가 있는 씬이 있고 기본값 PrincipledMaterial 과 CustomMaterial 으로 시작하겠습니다:
PrincipledMaterial | CustomMaterial |
---|---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { } Model { source: "#Cube" scale: Qt.vector3d(2, 2, 2) eulerRotation.x: 30 materials: PrincipledMaterial { } } } } | import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { } Model { source: "#Cube" scale: Qt.vector3d(2, 2, 2) eulerRotation.x: 30 materials: CustomMaterial { } } } } |
버텍스나 프래그먼트 셰이더 코드가 추가되지 않은 경우 CustomMaterial 는 사실상 PrincipledMaterial 이므로 둘 다 똑같은 결과를 가져옵니다.
참고: baseColor, metalness, baseColorMap 등과 같은 프로퍼티에는 CustomMaterial QML 유형에 해당하는 프로퍼티가 없습니다. 머티리얼 커스터마이징은 단순히 몇 가지 고정 값을 제공하는 것이 아니라 셰이더 코드를 통해 이루어지도록 설계되었습니다.
첫 번째 버텍스 셰이더
커스텀 버텍스 셰이더 스니펫을 추가해 보겠습니다. 이는 vertexShader 속성에서 파일을 참조하여 수행합니다. 접근 방식은 프래그먼트 셰이더에서도 동일합니다. 이러한 참조는 Image.source 또는 ShaderEffect.vertexShader 과 같이 로컬 또는 qrc
URL이며, 상대 경로는 .qml
파일 위치를 기준으로 처리됩니다. 따라서 일반적인 접근 방식은 .vert
및 .frag
파일을 Qt 리소스 시스템(CMake를 사용하는 경우qt_add_resources
)에 배치하고 상대 경로를 사용하여 참조하는 것입니다.
Qt 6.0에서 인라인 셰이더 문자열은 Qt Quick 나 Qt Quick 3D 에서 더 이상 지원되지 않습니다. (이러한 속성은 문자열이 아니라 URL이라는 사실에 유의하십시오.) 그러나 본질적으로 동적인 특성으로 인해 Qt Quick 3D 의 커스텀 머티리얼 및 포스트 프로세싱 효과는 참조된 파일에서 소스 형태의 셰이더 스니펫을 계속 제공합니다. 이는 셰이더가 엔진에 의해 추가 수정 없이 자체적으로 완성되므로 사전 컨디셔닝된 .qsb
셰이더 팩으로 제공되는 ShaderEffect 과의 차이점입니다.
참고: Qt Quick 3D URL은 로컬 리소스만 참조할 수 있습니다. 원격 콘텐츠에 대한 스키마는 지원되지 않습니다.
참고: 사용되는 셰이딩 언어는 Vulkan 호환 GLSL입니다. .vert
및 .frag
파일은 그 자체로는 완전한 셰이더가 아니므로 종종 snippets
이라고 불립니다. 따라서 이러한 스니펫에서 직접 제공하는 균일한 블록, 입력 및 출력 변수 또는 샘플러 균일성이 없습니다. 대신 Qt Quick 3D 엔진이 적절하게 수정합니다.
main.qml, material.vert의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { vertexShader: "material.vert" } void MAIN() { } |
커스텀 버텍스 또는 프래그먼트 셰이더 스니펫은 MAIN
, DIRECTIONAL_LIGHT
, POINT_LIGHT
, SPOT_LIGHT
, AMBIENT_LIGHT
, SPECULAR_LIGHT
와 같이 미리 정의된 이름을 가진 하나 이상의 함수를 제공할 것으로 예상됩니다. 지금은 MAIN
에 집중해 보겠습니다.
여기에 표시된 것처럼 비어 있는 MAIN()을 사용한 최종 결과는 이전과 완전히 동일합니다.
더 흥미롭게 만들기 전에 커스텀 버텍스 셰이더 스니펫에서 가장 일반적으로 사용되는 특수 키워드에 대한 개요를 살펴 보겠습니다. 전체 목록은 아닙니다. 전체 목록은 CustomMaterial 페이지를 참조하세요.
키워드 | 유형 | 설명 |
---|---|---|
MAIN | void MAIN()이 진입점입니다. 이 함수는 커스텀 버텍스 셰이더 스니펫에 항상 존재해야 하며, 그렇지 않은 경우 제공하지 않아도 아무런 의미가 없습니다. | |
VERTEX | vec3 | 셰이더가 입력으로 받는 버텍스 위치입니다. 커스텀 머티리얼에서 버텍스 셰이더의 일반적인 사용 사례는 전체 벡터 또는 일부 구성 요소에 값을 할당하여 이 벡터의 x, y 또는 z 값을 변경(대체)하는 것입니다. |
NORMAL | vec3 | 입력 메시 데이터의 버텍스 노멀, 또는 노멀이 제공되지 않은 경우 모두 0입니다. 버텍스와 마찬가지로 셰이더는 적절하다고 판단되는 대로 값을 자유롭게 변경할 수 있습니다. 그런 다음 변경된 값은 조각 단계의 조명 계산을 포함한 나머지 파이프라인에서 사용됩니다. |
UV0 | vec2 | 입력 메시 데이터의 첫 번째 텍스처 좌표 세트, 또는 제공된 UV 값이 없는 경우 모두 0입니다. 버텍스 및 노멀과 마찬가지로 값을 변경할 수 있습니다. |
모델뷰 투영_매트릭스 | mat4 | 모델 뷰 투영 행렬입니다. 어떤 그래픽 API 렌더링을 사용하든 동작을 통일하기 위해 모든 버텍스 데이터와 변환 행렬은 이 수준에서 OpenGL 규칙을 따릅니다. (Y축이 위를 가리키는, OpenGL 호환 투영 행렬) 읽기 전용입니다. |
MODEL_MATRIX | mat4 | 모델(월드) 행렬입니다. 읽기 전용입니다. |
NORMAL_MATRIX | mat3 | 모델 행렬의 왼쪽 위 3x3 슬라이스의 역변환된 역행렬입니다. 읽기 전용입니다. |
camera_position | vec3 | 월드 스페이스에서의 카메라 위치. 이 페이지의 예제에서는 (0, 0, 600) 입니다. 읽기 전용입니다. |
camera_direction | vec3 | 카메라 방향 벡터. 이 페이지의 예제에서는 (0, 0, -1) 입니다. 읽기 전용입니다. |
카메라_프로퍼티 | vec2 | 카메라의 근거리 및 원거리 클립 값입니다. 이 페이지의 예제에서는 (10, 10000) 입니다. 읽기 전용입니다. |
POINT_SIZE | float | 예를 들어 custom geometry 에서 메시의 지오메트리를 제공하는 것과 같이 포인트 토폴로지로 렌더링할 때만 관련이 있습니다. 이 값에 쓰는 것은 pointSize on a PrincipledMaterial 을 설정하는 것과 같습니다. |
POSITION | vec4 | 처럼 gl_Position . 없는 경우 기본 할당 문은 MODELVIEWPROJECTION_MATRIX 및 VERTEX 을 사용하여 자동으로 생성됩니다. 그렇기 때문에 빈 MAIN()이 작동하며, 대부분의 경우 사용자 정의 값을 할당할 필요가 없습니다. |
어떤 패턴에 따라 정점을 배치하는 커스텀 머티리얼을 만들어 봅시다. 좀 더 재미있게 만들려면 셰이더 코드에서 값이 유니폼으로 노출되는 애니메이션 QML 프로퍼티를 만들어 보겠습니다. (정확히 말하면, 대부분의 프로퍼티는 런타임에 균일한 버퍼로 뒷받침되는 균일한 블록의 멤버에 매핑되지만 Qt Quick 3D 을 사용하면 사용자 지정 머티리얼 작성자에게 이러한 세부 정보를 편리하게 투명하게 표시할 수 있습니다.)
main.qml, material.vert의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { vertexShader: "material.vert" property real uAmplitude: 0 NumberAnimation on uAmplitude { from: 0; to: 100; duration: 5000; loops: -1 } property real uTime: 0 NumberAnimation on uTime { from: 0; to: 100; duration: 10000; loops: -1 } } void MAIN() { VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude; } |
QML 프로퍼티의 유니폼
CustomMaterial 오브젝트의 커스텀 프로퍼티는 유니폼에 매핑됩니다. 위 예시에서는 uAmplitude
및 uTime
이 포함됩니다. 값이 변경될 때마다 업데이트된 값이 셰이더에 표시됩니다. 이 개념은 ShaderEffect 에서 이미 익숙할 수 있습니다.
QML 프로퍼티의 이름과 GLSL 변수는 반드시 일치해야 합니다. 셰이더 코드에는 개별 유니폼에 대한 별도의 선언이 없습니다. 오히려 QML 프로퍼티 이름을 그대로 사용할 수 있습니다. 따라서 위의 예제에서는 버텍스 셰이더 스니펫에서 uTime
및 uAmplitude
을 별도의 선언 없이 참조할 수 있습니다.
다음 표에는 유형이 매핑되는 방식이 나와 있습니다:
QML 유형 | 셰이더 유형 | 참고 |
---|---|---|
실수, 정수, 부울 | float, int, bool | |
color | vec4 | sRGB에서 선형 변환이 암시적으로 수행됩니다. |
vector2d | vec2 | |
벡터3d | vec3 | |
vector4d | vec4 | |
matrix4x4 | mat4 | |
쿼터니언 | vec4 | 스칼라 값은 w |
rect | vec4 | |
점, 크기 | vec2 | |
TextureInput | 샘플러2D |
예제 개선하기
더 진행하기 전에 예제를 좀 더 보기 좋게 만들어 보겠습니다. 회전된 직사각형 메시를 추가하고 DirectionalLight 그림자를 드리우도록 하면 큐브의 정점에 대한 변경 사항이 섀도 맵을 포함한 모든 렌더링 패스에 올바르게 반영되는지 확인할 수 있습니다. 이제 눈에 보이는 그림자를 얻기 위해 빛을 Y축에서 조금 더 높게 배치하고 회전이 적용되어 부분적으로 아래쪽을 가리키도록 합니다. ( directional
라이트이므로 회전이 중요합니다.)
main.qml, material.vert | 결과 |
---|---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { y: 200 eulerRotation.x: -45 castsShadow: true } Model { source: "#Rectangle" y: -250 scale: Qt.vector3d(5, 5, 5) eulerRotation.x: -45 materials: PrincipledMaterial { baseColor: "lightBlue" } } Model { source: "#Cube" scale: Qt.vector3d(2, 2, 2) eulerRotation.x: 30 materials: CustomMaterial { vertexShader: "material.vert" property real uAmplitude: 0 NumberAnimation on uAmplitude { from: 0; to: 100; duration: 5000; loops: -1 } property real uTime: 0 NumberAnimation on uTime { from: 0; to: 100; duration: 10000; loops: -1 } } } } } void MAIN() { VERTEX.x += sin(uTime + VERTEX.y) * uAmplitude; } |
조각 셰이더 추가하기
많은 커스텀 머티리얼에는 조각 셰이더가 필요할 것입니다. 실제로 많은 머티리얼은 조각 셰이더만 원할 것입니다. 버텍스에서 조각 단계로 전달할 추가 데이터가 없고 기본 버텍스 변환으로 충분하다면 CustomMaterial 에서 vertexShader
프로퍼티 설정을 생략할 수 있습니다.
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" } void MAIN() { } |
첫 번째 프래그먼트 셰이더에는 빈 MAIN() 함수가 포함되어 있습니다. 이는 조각 셰이더 스니펫을 전혀 지정하지 않은 것과 다르지 않습니다. 기본값 PrincipledMaterial 을 사용하면 얻을 수 있는 것과 같습니다.
조각 셰이더에서 일반적으로 사용되는 몇 가지 키워드를 살펴봅시다. 전체 목록은 아니므로 전체 참조는 CustomMaterial 문서를 참조하세요. 이들 중 다수는 읽기-쓰기이므로 기본값이 있지만 셰이더에서 다른 값을 할당할 수 있으며, 종종 다른 값을 할당하고 싶을 것입니다.
이름에서 알 수 있듯이 이들 중 다수는 금속성 거칠기 머티리얼 모델을 따라 동일한 의미와 의미를 가진 비슷한 이름의 PrincipledMaterial 프로퍼티에 매핑됩니다. 예를 들어 BASE_COLOR 값은 셰이더에서 하드 코딩하거나 텍스처 샘플링을 기반으로 하거나 유니폼으로 노출된 QML 프로퍼티 또는 버텍스 셰이더에서 전달된 보간 데이터를 기반으로 계산할 수 있는 등 이러한 값의 계산 방법을 결정하는 것은 사용자 지정 머티리얼 구현에 달려 있습니다.
키워드 | 유형 | 설명 |
---|---|---|
BASE_COLOR | vec4 | 기본 색상 및 알파 값입니다. PrincipledMaterial::baseColor 에 해당합니다. 조각의 최종 알파 값은 모델 불투명도에 기본 색상 알파를 곱한 값입니다. 기본값은 (1.0, 1.0, 1.0, 1.0) 입니다. |
EMISSIVE_COLOR | vec3 | 자체 조명의 색상입니다. PrincipledMaterial::emissiveFactor 에 해당하며 기본값은 (0.0, 0.0, 0.0) 입니다. |
METALNESS | float | Metalness 0-1 범위의 값입니다. 기본값은 0으로, 재료가 유전체(비금속)임을 의미합니다. |
거칠기 | float | Roughness 0-1 범위의 값입니다. 기본값은 0입니다. 값이 클수록 스페큘러 하이라이트가 부드러워지고 반사가 흐려집니다. |
SPECULAR_AMOUNT | float | The strength of specularity 0-1 범위의 값입니다. 기본값은 0.5 입니다. metalness 이 1 으로 설정된 금속 개체의 경우 이 값은 아무런 영향을 미치지 않습니다. SPECULAR_AMOUNT 및 METALNESS 값이 모두 0보다 크지만 1보다 작은 경우 결과는 두 머티리얼 모델 간의 블렌드입니다. |
NORMAL | vec3 | 월드 스페이스에서 보간된 노멀로, 페이스 컬링이 비활성화되었을 때 양면성을 위해 조정됩니다. 읽기 전용입니다. |
UV0 | vec2 | 보간된 텍스처 좌표입니다. 읽기 전용입니다. |
var_world_position | vec3 | 월드 스페이스에서 보간된 버텍스 위치. 읽기 전용입니다. |
큐브의 기본 색을 빨간색으로 만들어 봅시다:
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" } void MAIN() { BASE_COLOR = vec4(1.0, 0.0, 0.0, 1.0); } |
이제 자체 조명 수준을 조금 더 강화합니다:
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" } void MAIN() { BASE_COLOR = vec4(1.0, 0.0, 0.0, 1.0); EMISSIVE_COLOR = vec3(0.4); } |
셰이더에 값을 하드코딩하는 대신 유니폼으로 노출된 QML 프로퍼티, 심지어 애니메이션 프로퍼티도 사용할 수 있습니다:
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" property color baseColor: "black" ColorAnimation on baseColor { from: "black"; to: "purple"; duration: 5000; loops: -1 } } void MAIN() { BASE_COLOR = vec4(baseColor.rgb, 1.0); EMISSIVE_COLOR = vec3(0.4); } |
PrincipledMaterial 및 표준 내장 프로퍼티로는 구현할 수 없는 덜 사소한 작업을 해보겠습니다. 다음 머티리얼은 큐브 메시의 텍스처 UV 좌표를 시각화합니다. U는 0에서 1까지, 즉 검은색에서 빨간색으로, V도 0에서 1까지, 검은색에서 녹색으로 표시됩니다.
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" } void MAIN() { BASE_COLOR = vec4(UV0, 0.0, 1.0); } |
이왕 하는 김에 노멀도 구체에 시각화해 보겠습니다. UV와 마찬가지로 커스텀 버텍스 셰이더 스니펫이 NORMAL 값을 변경하면 조각 셰이더의 보간된 조각별 값도 NORMAL이라는 이름으로 노출되어 이러한 조정이 반영됩니다.
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
Model { source: "#Sphere" scale: Qt.vector3d(2, 2, 2) materials: CustomMaterial { fragmentShader: "material.frag" } } void MAIN() { BASE_COLOR = vec4(NORMAL, 1.0); } |
색
잠시 찻주전자 모델로 전환하여 머티리얼을 금속과 유전체가 혼합된 것으로 만들고 녹색을 기본 색상으로 설정해 보겠습니다. green
QColor 값은 (0, 128, 0)
에 매핑되며, 이를 기반으로 첫 번째 시도가 될 수 있습니다:
main.qml, material.frag |
---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { } Model { source: "teapot.mesh" scale: Qt.vector3d(60, 60, 60) eulerRotation.x: 30 materials: CustomMaterial { fragmentShader: "material.frag" } } } } void MAIN() { BASE_COLOR = vec4(0.0, 0.5, 0.0, 1.0); METALNESS = 0.6; SPECULAR_AMOUNT = 0.4; ROUGHNESS = 0.4; } |
이것은 완전히 올바르게 보이지 않습니다. 두 번째 접근 방식과 비교해 보세요:
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" property color uColor: "green" } void MAIN() { BASE_COLOR = vec4(uColor.rgb, 1.0); METALNESS = 0.6; SPECULAR_AMOUNT = 0.4; ROUGHNESS = 0.4; } |
PrincipledMaterial 로 전환하여 PrincipledMaterial::baseColor 을 "녹색"으로 설정하고 금속성 및 기타 속성을 따르면 두 번째 접근 방식과 결과가 동일하다는 것을 확인할 수 있습니다:
main.qml 변경 | 결과 |
---|---|
materials: PrincipledMaterial { baseColor: "green" metalness: 0.6 specularAmount: 0.4 roughness: 0.4 } |
uColor
속성의 유형을 vector4d
으로 변경하거나 color
이외의 다른 유형으로 변경하면 결과가 갑자기 변경되어 첫 번째 접근 방식과 동일하게 됩니다.
왜 그럴까요?
그 답은 DefaultMaterial, PrincipledMaterial 의 색상 프로퍼티와 CustomMaterial 의 color
유형이 있는 사용자 정의 프로퍼티에 대해 암시적으로 수행되는 선형 변환에 있습니다. 이러한 변환은 다른 값에는 수행되지 않으므로 셰이더가 색상 값을 하드코딩하거나 color
와 다른 유형의 QML 속성을 기반으로 하는 경우 소스 값이 sRGB 색 공간인 경우 선형화를 수행하는 것은 셰이더에 달려 있습니다. 선형으로 변환하는 것이 중요한 이유는 Qt Quick 3D 이 조각 셰이딩의 결과에 대해 tonemapping 을 수행하며, 이 프로세스는 sRGB 공간의 값을 입력으로 가정하기 때문입니다.
"green"
과 같은 기본 제공 QColor 상수는 모두 sRGB 공간에서 제공됩니다. 따라서 첫 번째 시도에서 vec4(0.0, 0.5, 0.0, 1.0)
을 BASE_COLOR 에 할당하는 것만으로는 sRGB 공간의 RGB 값 (0, 128, 0)
과 일치하는 결과를 얻기에 충분하지 않습니다. 이러한 색상 값을 선형화하는 공식은 CustomMaterial 의 BASE_COLOR
문서를 참조하세요. 텍스처를 샘플링하여 검색한 색상 값에도 동일하게 적용됩니다. 소스 이미지 데이터가 sRGB 색상 공간이 아닌 경우 변환이 필요합니다( tonemapping 비활성화하지 않은 경우).
블렌딩
알파 블렌딩을 기대하는 경우 1.0
보다 작은 값을 BASE_COLOR.a
에 쓰는 것만으로는 충분하지 않습니다. 이러한 자료는 원하는 결과를 얻기 위해 sourceBlend 및 destinationBlend 속성의 값을 변경하는 경우가 많습니다.
또한 결합된 알파 값은 Node opacity 에 머티리얼 알파를 곱한 값이라는 점을 기억하세요.
시각화를 위해 BASE_COLOR
에 알파가 0.5
인 빨간색을 할당하는 셰이더를 사용해 보겠습니다:
main.qml, material.frag | 결과 |
---|---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "white" } PerspectiveCamera { id: camera z: 600 } DirectionalLight { } Model { source: "#Cube" x: -150 eulerRotation.x: 60 eulerRotation.y: 20 materials: CustomMaterial { fragmentShader: "material.frag" } } Model { source: "#Cube" eulerRotation.x: 60 eulerRotation.y: 20 materials: CustomMaterial { sourceBlend: CustomMaterial.SrcAlpha destinationBlend: CustomMaterial.OneMinusSrcAlpha fragmentShader: "material.frag" } } Model { source: "#Cube" x: 150 eulerRotation.x: 60 eulerRotation.y: 20 materials: CustomMaterial { sourceBlend: CustomMaterial.SrcAlpha destinationBlend: CustomMaterial.OneMinusSrcAlpha fragmentShader: "material.frag" } opacity: 0.5 } } } void MAIN() { BASE_COLOR = vec4(1.0, 0.0, 0.0, 0.5); } |
첫 번째 큐브는 색상의 알파 값에 0.5를 쓰고 있지만 알파 블렌딩이 활성화되어 있지 않으므로 눈에 보이는 결과를 가져오지 않습니다. 두 번째 큐브는 CustomMaterial 속성을 통해 간단한 알파 블렌딩을 활성화합니다. 세 번째 큐브는 모델에 0.5의 불투명도를 할당하므로 유효 불투명도는 0.25입니다.
버텍스와 조각 셰이더 간에 데이터 전달하기
버텍스당 값을 계산한 다음(예를 들어 단일 삼각형이라고 가정하고 삼각형의 세 모서리에 대해) 이를 조각 단계로 전달하면 각 조각(예: 래스터화된 삼각형에 포함된 모든 조각)에 대해 보간된 값에 액세스할 수 있게 됩니다. 커스텀 머티리얼 셰이더 스니펫에서는 VARYING
키워드로 이 작업을 수행할 수 있습니다. 이는 GLSL 120 및 GLSL ES 100과 유사한 구문을 제공하지만 런타임에 사용되는 그래픽 API에 관계없이 작동합니다. 엔진은 다양한 선언을 적절하게 다시 작성합니다.
UV 좌표를 사용한 고전적인 텍스처 샘플링이 어떻게 보이는지 살펴봅시다. 텍스처는 다음 섹션에서 다룰 예정이므로 지금은 셰이더의 texture()
함수에 전달할 수 있는 UV 좌표를 얻는 방법에 집중해 보겠습니다.
main.qml, material.vert, material.frag |
---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { } Model { source: "#Sphere" scale: Qt.vector3d(4, 4, 4) eulerRotation.x: 30 materials: CustomMaterial { vertexShader: "material.vert" fragmentShader: "material.frag" property TextureInput someTextureMap: TextureInput { texture: Texture { source: "qt_logo_rect.png" } } } } } } VARYING vec2 uv; void MAIN() { uv = UV0; } VARYING vec2 uv; void MAIN() { BASE_COLOR = texture(someTextureMap, uv); } |
qt_logo_rect.png | 결과 |
---|---|
VARYING
선언에 유의하세요. 이름과 유형이 일치해야 하며, 조각 셰이더의 uv
은 현재 조각의 보간된 UV 좌표를 노출합니다.
다른 유형의 데이터도 비슷한 방식으로 조각 단계에 전달할 수 있습니다. 대부분의 경우 일반적인 요구 사항을 충족하는 내장 기능이 제공되므로 머티리얼의 자체 변형을 설정할 필요가 없다는 점에 유의할 필요가 있습니다. 여기에는 (보간된) 노멀, UV, 월드 위치(VAR_WORLD_POSITION
) 또는 카메라를 향하는 벡터(VIEW_VECTOR
)를 만드는 것이 포함됩니다.
위의 예는 실제로 조각 단계에서도 UV0
을 자동으로 사용할 수 있으므로 다음과 같이 단순화할 수 있습니다:
main.qml, material.frag의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { fragmentShader: "material.frag" property TextureInput someTextureMap: TextureInput { texture: Texture { source: "qt_logo_rect.png" } } void MAIN() { BASE_COLOR = texture(someTextureMap, UV0); } |
변수에 대한 보간을 비활성화하려면 버텍스 및 조각 셰이더 스니펫 모두에 flat
키워드를 사용합니다. 예를 들어
VARYING flat vec2 v;
텍스처
CustomMaterial 에는 텍스처 맵이 내장되어 있지 않으므로 예를 들어 PrincipledMaterial::baseColorMap 과 동등한 것이 없습니다. 이는 동일한 텍스처 맵을 구현하는 것이 종종 사소한 반면, DefaultMaterial 및 PrincipledMaterial 에 내장된 것보다 훨씬 더 많은 유연성을 제공하기 때문입니다. 커스텀 조각 셰이더 스니펫은 단순히 텍스처를 샘플링하는 것 외에도 BASE_COLOR
, EMISSIVE_COLOR
, ROUGHNESS
등에 할당하는 값을 계산할 때 다양한 소스에서 데이터를 자유롭게 결합하고 블렌딩할 수 있습니다. 이러한 계산은 QML 프로퍼티를 통해 제공되는 데이터, 버텍스 단계에서 전송된 보간 데이터, 샘플링 텍스처에서 검색된 값, 하드코딩된 값을 기반으로 할 수 있습니다.
이전 예제에서 볼 수 있듯이 텍스처를 버텍스, 조각 또는 두 셰이더에 노출하는 것은 스칼라 및 벡터 균일 값과 매우 유사합니다. 유형이 TextureInput 인 QML 속성은 셰이더 코드에서 자동으로 sampler2D
에 연결됩니다. 항상 그렇듯이 셰이더 코드에서 이 샘플러를 선언할 필요가 없습니다.
TextureInput 은 Texture 을 참조하고 enabled 프로퍼티를 추가합니다. Texture 는 세 가지 방법으로 데이터를 소싱할 수 있습니다: from an image file, from a texture with live Qt Quick content또는 QQuick3DTextureData 을 통한 can be provided from C++ 입니다.
참고: Texture 프로퍼티의 경우 소스, 타일링 및 필터링 관련 프로퍼티만 커스텀 머티리얼에 암시적으로 고려되며, 나머지(예: UV 변환)는 커스텀 셰이더가 적절하다고 판단되는 대로 구현할 수 있습니다.
라이브 Qt Quick 콘텐츠를 사용하여 모델(이 경우 구체)에 텍스처를 입힌 예시를 살펴보겠습니다:
main.qml, material.frag |
---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color; clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { } Model { source: "#Sphere" scale: Qt.vector3d(4, 4, 4) eulerRotation.x: 30 materials: CustomMaterial { fragmentShader: "material.frag" property TextureInput someTextureMap: TextureInput { texture: Texture { sourceItem: Rectangle { width: 512; height: 512 color: "red" Rectangle { width: 32; height: 32 anchors.horizontalCenter: parent.horizontalCenter y: 150 color: "gray"; NumberAnimation on rotation { from: 0; to: 360; duration: 3000; loops: -1 } } Text { anchors.centerIn: parent text: "Texture Map" font.pointSize: 16 } } } } } } } } void MAIN() { vec2 uv = vec2(UV0.x, 1.0 - UV0.y); vec4 c = texture(someTextureMap, uv); BASE_COLOR = c; } |
여기서 2D 서브트리(두 개의 자식이 있는 직사각형: 다른 직사각형과 텍스트)는 이 미니 씬이 변경될 때마다 512x512 2D 텍스처로 렌더링됩니다. 그런 다음 텍스처는 someTextureMap
라는 이름으로 커스텀 머티리얼에 노출됩니다.
셰이더에서 V 좌표가 뒤집힌 것을 주목하세요. 위에서 언급했듯이 셰이더 레벨에서 완전한 프로그래밍이 가능한 커스텀 머티리얼은 Texture 및 PrincipledMaterial 의 "고정" 기능을 제공하지 않습니다. 즉, UV 좌표에 대한 모든 변환은 셰이더에서 적용해야 합니다. 여기서는 텍스처가 Texture::sourceItem 를 통해 생성되었으므로 사용 중인 메시의 UV 세트와 일치하는 것을 얻으려면 V를 뒤집어야 합니다.
이 예제에서 보여주는 것은 PrincipledMaterial 에서도 가능합니다. 간단한 엠보스 효과를 추가하여 더 재미있게 만들어 보겠습니다:
material.frag | 결과 |
---|---|
void MAIN() { vec2 uv = vec2(UV0.x, 1.0 - UV0.y); vec2 size = vec2(textureSize(someTextureMap, 0)); vec2 d = vec2(1.0 / size.x, 1.0 / size.y); vec4 diff = texture(someTextureMap, uv + d) - texture(someTextureMap, uv - d); float c = (diff.x + diff.y + diff.z) + 0.5; BASE_COLOR = vec4(c, c, c, 1.0); } |
지금까지 살펴본 기능을 사용하면 시각적으로 인상적인 방식으로 메시를 음영 처리하는 머티리얼을 만들 수 있는 다양한 가능성이 열려 있습니다. 기본 둘러보기를 마무리하기 위해 평면 메시에 높이와 노멀 맵을 적용하는 예제를 살펴보겠습니다. (기본 제공 #Rectangle
에는 세분화가 충분하지 않기 때문에 전용 .mesh
파일이 사용됩니다.) 더 나은 조명 결과를 위해 360도 HDR 이미지가 포함된 이미지 기반 조명을 사용하겠습니다. 또한 이미지를 스카이박스로 설정하여 무슨 일이 일어나고 있는지 더 명확하게 보여줍니다.
먼저 빈 CustomMaterial:
main.qml | 결과 |
---|---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.SkyBox lightProbe: Texture { source: "00489_OpenfootageNET_snowfield_low.hdr" } } PerspectiveCamera { z: 600 } Model { source: "plane.mesh" scale: Qt.vector3d(400, 400, 400) z: 400 y: -50 eulerRotation.x: -90 materials: CustomMaterial { } } } } |
이제 메시에 높이와 노멀 맵을 적용하는 셰이더를 만들어 보겠습니다:
높이 맵 | 노멀맵 맵 |
---|---|
material.vert, material.frag |
---|
float getHeight(vec2 pos) { return texture(heightMap, pos).r; } void MAIN() { const float offset = 0.004; VERTEX.y += getHeight(UV0); TANGENT = normalize(vec3(0.0, getHeight(UV0 + vec2(0.0, offset)) - getHeight(UV0 + vec2(0.0, -offset)), offset * 2.0)); BINORMAL = normalize(vec3(offset * 2.0, getHeight(UV0 + vec2(offset, 0.0)) - getHeight(UV0 + vec2(-offset, 0.0)), 0.0)); NORMAL = cross(TANGENT, BINORMAL); } void MAIN() { vec3 normalValue = texture(normalMap, UV0).rgb; normalValue.xy = normalValue.xy * 2.0 - 1.0; normalValue.z = sqrt(max(0.0, 1.0 - dot(normalValue.xy, normalValue.xy))); NORMAL = normalize(mix(NORMAL, TANGENT * normalValue.x + BINORMAL * normalValue.y + NORMAL * normalValue.z, 1.0)); } |
main.qml의 변경 사항 | 결과 |
---|---|
materials: CustomMaterial { vertexShader: "material.vert" fragmentShader: "material.frag" property TextureInput normalMap: TextureInput { texture: Texture { source: "normalmap.jpg" } } property TextureInput heightMap: TextureInput { texture: Texture { source: "heightmap.png" } } } |
참고: WasdController 객체는 키보드와 마우스로 익숙한 방식으로 씬을 탐색하고 둘러볼 수 있으므로 개발 및 문제 해결 시 큰 도움이 될 수 있습니다. WasdController 으로 카메라를 제어하는 것은 간단합니다:
import QtQuick3D.Helpers View3D { PerspectiveCamera { id: camera } // ... } WasdController { controlledObject: camera }
뎁스 및 화면 텍스처
커스텀 셰이더 스니펫에서 DEPTH_TEXTURE
또는 SCREEN_TEXTURE
키워드를 사용하면 별도의 렌더 패스에서 해당 텍스처를 생성하도록 선택하는데, 이는 비용이 많이 드는 작업은 아니지만 유리와 같은 재질의 굴절과 같은 다양한 기법을 구현할 수 있습니다.
DEPTH_TEXTURE
는 sampler2D
으로, 씬에 렌더링된 모든 opaque
오브젝트가 있는 뎁스 버퍼의 내용으로 텍스처를 샘플링할 수 있습니다. 마찬가지로 SCREEN_TEXTURE
은 sampler2D
으로, 투명 머티리얼이나 SCREEN_TEXTURE를 사용하는 머티리얼을 제외한 씬의 내용을 포함하는 텍스처를 샘플링할 수 있습니다. 이 텍스처는 렌더링되는 프레임버퍼의 콘텐츠가 필요한 머티리얼에 사용할 수 있습니다. SCREEN_TEXTURE 텍스처는 View3D 와 동일한 클리어 모드를 사용합니다. 이 텍스처의 크기는 View3D 의 픽셀 단위 크기와 일치합니다.
DEPTH_TEXTURE
을 통해 뎁스 버퍼 내용을 시각화하여 간단한 데모를 해보겠습니다. 여기서 카메라의 far clip value 는 기본 10000에서 2000으로 축소되어 범위가 더 작아지고 시각화된 깊이 값의 차이가 더 분명해집니다. 그 결과 표면 위에 씬의 뎁스 버퍼를 시각화하는 직사각형이 나타납니다.
main.qml, material.frag | 결과 |
---|---|
import QtQuick import QtQuick3D import QtQuick3D.Helpers Rectangle { width: 400 height: 400 color: "black" View3D { anchors.fill: parent PerspectiveCamera { id: camera z: 600 clipNear: 1 clipFar: 2000 } DirectionalLight { } Model { source: "#Cube" scale: Qt.vector3d(2, 2, 2) position: Qt.vector3d(150, 200, -1000) eulerRotation.x: 60 eulerRotation.y: 20 materials: PrincipledMaterial { } } Model { source: "#Cylinder" scale: Qt.vector3d(2, 2, 2) position: Qt.vector3d(400, 200, -1000) materials: PrincipledMaterial { } opacity: 0.3 } Model { source: "#Sphere" scale: Qt.vector3d(2, 2, 2) position: Qt.vector3d(-150, 200, -600) materials: PrincipledMaterial { } } Model { source: "#Cone" scale: Qt.vector3d(2, 2, 2) position: Qt.vector3d(0, 400, -1200) materials: PrincipledMaterial { } } Model { source: "#Rectangle" scale: Qt.vector3d(3, 3, 3) y: -150 materials: CustomMaterial { fragmentShader: "material.frag" } } } WasdController { controlledObject: camera } } void MAIN() { float zNear = CAMERA_PROPERTIES.x; float zFar = CAMERA_PROPERTIES.y; float zRange = zFar - zNear; vec4 depthSample = texture(DEPTH_TEXTURE, vec2(UV0.x, 1.0 - UV0.y)); float zn = 2.0 * depthSample.r - 1.0; float d = 2.0 * zNear * zFar / (zFar + zNear - zn * zRange); d /= zFar; BASE_COLOR = vec4(d, d, d, 1.0); } |
원통은 반투명도에 의존하기 때문에 DEPTH_TEXTURE
에 존재하지 않으므로 모두 불투명한 다른 오브젝트와는 다른 범주에 속합니다. 이러한 객체는 깊이 버퍼에 쓰지는 않지만 불투명 객체가 쓴 깊이 값과 비교하여 테스트하고 뒤에서 앞 순서로 렌더링되는 것에 의존합니다. 따라서 DEPTH_TEXTURE
에도 존재하지 않습니다.
대신 셰이더를 샘플 SCREEN_TEXTURE
로 전환하면 어떻게 될까요?
material.frag | 결과 |
---|---|
void MAIN() { vec4 c = texture(SCREEN_TEXTURE, vec2(UV0.x, 1.0 - UV0.y)); if (c.a == 0.0) c.rgb = vec3(0.2, 0.1, 0.3); BASE_COLOR = c; } |
여기서 직사각형은 SCREEN_TEXTURE
로 텍스처링되고 투명한 픽셀은 보라색으로 대체됩니다.
라이트 프로세서 기능
CustomMaterial 의 고급 기능은 조각 셰이더에서 조각 색상을 계산하는 데 사용되는 조명 방정식을 다시 구현하는 함수를 정의할 수 있는 기능입니다. 조명 프로세서 함수가 있는 경우 장면의 각 조명에 대해 각 조각마다 한 번씩 호출됩니다. 다양한 조명 유형과 앰비언트 및 스페큘러 기여도에 대한 전용 함수가 있습니다. 해당 광원 처리기 함수가 없는 경우 PrincipledMaterial 에서와 마찬가지로 표준 계산이 사용됩니다. 조명 프로세서가 있지만 함수 본문이 비어 있으면 씬에 지정된 조명 유형의 기여도가 없다는 뜻입니다.
DIRECTIONAL_LIGHT
, POINT_LIGHT
, SPOT_LIGHT
, AMBIENT_LIGHT
, SPECULAR_LIGHT
과 같은 함수에 대한 자세한 내용은 CustomMaterial 문서를 참조하십시오.
음영 처리되지 않은 커스텀 머티리얼
CustomMaterial: unshaded
커스텀 머티리얼의 또 다른 유형이 있습니다. 지금까지의 모든 예제에서는 shadingMode 속성을 기본값인 CustomMaterial.Shaded 값으로 둔 shaded
사용자 정의 머티리얼을 사용했습니다.
이 속성을 CustomMaterial.Unshaded로 전환하면 어떻게 될까요?
우선 BASE_COLOR
, EMISSIVE_COLOR
, METALNESS
등과 같은 키워드는 더 이상 원하는 효과를 얻지 못합니다. 그 이유는 이름에서 알 수 있듯이 음영 처리되지 않은 머티리얼은 표준 셰이딩 코드의 대부분을 자동으로 수정하지 않으므로 씬의 조명, 이미지 기반 조명, 그림자 및 앰비언트 오클루전을 무시하기 때문입니다. 대신 셰이딩되지 않은 머티리얼은 FRAGCOLOR
키워드를 통해 셰이더를 완전히 제어할 수 있습니다. FRAGCOLOR
에 할당된 색상이 Qt Quick 3D 에 의한 추가 조정 없이 조각의 결과 및 최종 색상이 되는 것은 gl_FragColor와 유사합니다.
main.qml, material.frag, material2.frag | 결과 |
---|---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" } PerspectiveCamera { z: 600 } DirectionalLight { } Model { source: "#Cylinder" x: -100 eulerRotation.x: 30 materials: CustomMaterial { fragmentShader: "material.frag" } } Model { source: "#Cylinder" x: 100 eulerRotation.x: 30 materials: CustomMaterial { shadingMode: CustomMaterial.Unshaded fragmentShader: "material2.frag" } } } } void MAIN() { BASE_COLOR = vec4(1.0); } void MAIN() { FRAGCOLOR = vec4(1.0); } |
오른쪽 원통이 씬에서 DirectionalLight 을 무시하는 것을 볼 수 있습니다. 이 셰이딩은 씬 조명에 대해 아무것도 알지 못하며 최종 조각 색상은 모두 흰색입니다.
셰이딩되지 않은 머티리얼의 버텍스 셰이더에는 여전히 일반적인 입력을 사용할 수 있습니다: VERTEX
, NORMAL
, MODELVIEWPROJECTION_MATRIX
, 등을 사용할 수 있으며 POSITION
에 쓸 수 있습니다. 그러나 셰이딩되지 않은 머티리얼의 조각 셰이더에서는 NORMAL
, UV0
, VAR_WORLD_POSITION
와 같은 유사한 편의 기능을 더 이상 사용할 수 없습니다. 대신 최종 조각 색상을 결정하는 데 필요한 모든 것을 VARYING
을 사용하여 계산하고 전달하는 것은 이제 셰이더 코드에 달려 있습니다.
버텍스 셰이더와 프래그먼트 셰이더가 모두 있는 예제를 살펴보겠습니다. 변경된 버텍스 위치는 모든 조각에 보간된 값과 함께 조각 셰이더로 전달됩니다.
main.qml, material.vert, material.frag |
---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" } PerspectiveCamera { z: 600 } Model { source: "#Sphere" scale: Qt.vector3d(3, 3, 3) materials: CustomMaterial { property real time: 0.0 NumberAnimation on time { from: 0; to: 100; duration: 20000; loops: -1 } property real amplitude: 10.0 shadingMode: CustomMaterial.Unshaded vertexShader: "material.vert" fragmentShader: "material.frag" } } } } VARYING vec3 pos; void MAIN() { pos = VERTEX; pos.x += sin(time * 4.0 + pos.y) * amplitude; POSITION = MODELVIEWPROJECTION_MATRIX * vec4(pos, 1.0); } VARYING vec3 pos; void MAIN() { FRAGCOLOR = vec4(vec3(pos.x * 0.02, pos.y * 0.02, pos.z * 0.02), 1.0); } |
음영 처리되지 않은 머티리얼은 씬 조명과의 상호 작용이 필요하지 않거나 원하지 않을 때 유용하며, 최종 조각 색상을 완전히 제어해야 할 때 유용합니다. 위의 예시에는 DirectionalLight 또는 다른 조명이 없지만 사용자 지정 머티리얼이 있는 구가 예상대로 표시되는 것을 볼 수 있습니다.
참고: 버텍스 셰이더 스니펫만 있고 fragmentShader 프로퍼티를 지정하지 않은 셰이딩되지 않은 머티리얼은 여전히 작동하지만 결과는 shadingMode가 Shaded로 설정된 것과 같습니다. 따라서 버텍스 셰이더만 있는 머티리얼의 경우 shadingMode를 전환하는 것은 의미가 없습니다.
이펙트의 프로그래밍 가능성
포스트 프로세싱 효과는 하나 이상의 조각 셰이더를 View3D 의 결과에 적용합니다. 그러면 이러한 조각 셰이더의 출력이 원래 렌더링 결과 대신 표시됩니다. 이는 개념적으로 Qt Quick 의 ShaderEffect 및 ShaderEffectSource 과 매우 유사합니다.
참고: 포스트 프로세싱 효과는 View3D 의 renderMode 가 View3D.오프스크린으로 설정된 경우에만 사용할 수 있습니다.
커스텀 버텍스 셰이더 스니펫도 효과에 지정할 수 있지만 유용성이 제한되어 있으므로 비교적 드물게 사용될 것으로 예상됩니다. 포스트 프로세싱 효과에 대한 버텍스 입력은 쿼드(두 개의 삼각형 또는 삼각형 스트립)이며, 그 버텍스를 변형하거나 배치하는 것은 종종 도움이 되지 않습니다. 그러나 VARYING
키워드를 사용하여 데이터를 계산하고 조각 셰이더에 전달하기 위해 버텍스 셰이더를 사용하는 것이 합리적일 수 있습니다. 그러면 평소와 같이 조각 셰이더는 현재 조각 좌표를 기준으로 보간된 값을 받습니다.
Effect 와 관련된 셰이더 스니펫의 구문은 셰이딩되지 않은 CustomMaterial 에 대한 셰이더와 동일합니다. 내장 특수 키워드의 경우 VARYING
, MAIN
, FRAGCOLOR
(프래그먼트 셰이더 전용), POSITION
(버텍스 셰이더 전용), VERTEX
(버텍스 셰이더 전용) 및 MODELVIEWPROJECTION_MATRIX
는 CustomMaterial 과 동일하게 작동합니다.
Effect 프래그먼트 셰이더에 가장 중요한 특수 키워드는 다음과 같습니다:
이름 | Type | 설명 |
---|---|---|
INPUT | 샘플러2D 또는 샘플러2디레이 | 입력 텍스처의 샘플러입니다. 이펙트는 일반적으로 INPUT_UV 를 사용하여 샘플링합니다. |
INPUT_UV | vec2 | 샘플링할 UV 좌표 INPUT . |
INPUT_SIZE | vec2 | INPUT 텍스처의 크기(픽셀 단위)입니다. textureSize()를 호출하는 대신 편리하게 사용할 수 있습니다. |
OUTPUT_SIZE | vec2 | 출력 텍스처의 크기(픽셀 단위)입니다. 대부분의 경우 INPUT_SIZE 와 같지만, 멀티 패스 이펙트는 크기가 다른 중간 텍스처로 출력하는 패스를 가질 수 있습니다. |
DEPTH_TEXTURE | 샘플러2D | 씬의 불투명 오브젝트가 있는 뎁스 버퍼 콘텐츠가 있는 뎁스 텍스처입니다. CustomMaterial 와 마찬가지로 셰이더에 이 키워드가 있으면 뎁스 텍스처가 자동으로 생성됩니다. |
참고: 멀티뷰 렌더링이 활성화된 경우 입력 텍스처는 2D 텍스처 배열입니다. texture() 및 textureSize()와 같은 GLSL 함수는 각각 vec3/ivec3를 취하고 반환합니다. 레이어에는 VIEW_INDEX
을 사용합니다. 멀티뷰 렌더링을 사용하거나 사용하지 않고 작동하려는 VR/AR 애플리케이션에서는 다음과 같이 셰이더 코드를 작성하는 것이 이식 가능한 접근 방식입니다:
#if QSHADER_VIEW_COUNT >= 2 vec4 c = texture(INPUT, vec3(INPUT_UV, VIEW_INDEX)); #else vec4 c = texture(INPUT, INPUT_UV); #endif
포스트 프로세싱 효과
이번에는 기본 컬러 맵으로 바둑판 텍스처를 사용하는 텍스처 사각형 등 몇 가지 오브젝트를 더 사용하여 간단한 장면으로 시작해 보겠습니다.
main.qml | 결과 |
---|---|
import QtQuick import QtQuick3D Item { View3D { anchors.fill: parent environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" } PerspectiveCamera { z: 400 } DirectionalLight { } Texture { id: checkerboard source: "checkerboard.png" scaleU: 20 scaleV: 20 tilingModeHorizontal: Texture.Repeat tilingModeVertical: Texture.Repeat } Model { source: "#Rectangle" scale: Qt.vector3d(10, 10, 1) eulerRotation.x: -45 materials: PrincipledMaterial { baseColorMap: checkerboard } } Model { source: "#Cone" position: Qt.vector3d(100, -50, 100) materials: PrincipledMaterial { } } Model { source: "#Cube" position.y: 100 eulerRotation.y: 20 materials: PrincipledMaterial { } } Model { source: "#Sphere" position: Qt.vector3d(-150, 200, -100) materials: PrincipledMaterial { } } } } |
이제 전체 장면에 효과를 적용해 보겠습니다. 더 정확하게는 View3D. 씬에 여러 개의 View3D 항목이 있는 경우 각 항목에는 자체 SceneEnvironment 이 있으므로 자체 포스트 프로세싱 효과 체인이 있습니다. 이 예시에서는 전체 창을 덮는 단일 View3D 이 있습니다.
main.qml의 변경 사항 | effect.frag |
---|---|
environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" effects: redEffect } Effect { id: redEffect property real uRed: 1.0 NumberAnimation on uRed { from: 1; to: 0; duration: 5000; loops: -1 } passes: Pass { shaders: Shader { stage: Shader.Fragment shader: "effect.frag" } } } | void MAIN() { vec4 c = texture(INPUT, INPUT_UV); c.r = uRed; FRAGCOLOR = c; } |
이 간단한 효과는 빨간색 채널 값을 변경합니다. QML 프로퍼티를 유니폼으로 노출하는 것은 사용자 정의 머티리얼과 동일한 방식으로 효과와 함께 작동합니다. 셰이더는 이펙트에 대한 프래그먼트 셰이더를 작성할 때 매우 일반적으로 사용되는 라인으로 시작합니다: UV 좌표 INPUT_UV
에서 INPUT
을 샘플링합니다. 그런 다음 원하는 계산을 수행하고 최종 조각 색상을 FRAGCOLOR
에 할당합니다.
예제에서 설정된 많은 프로퍼티는 복수형(효과, 패스, 셰이더)입니다. 단일 요소만 있는 경우 목록 [ ]
구문을 생략할 수 있지만, 이러한 모든 속성은 목록이며 둘 이상의 요소를 포함할 수 있습니다. 왜 그럴까요?
- effects 는 목록이기 때문입니다. View3D 는 여러 효과를 함께 연결할 수 있기 때문입니다. 효과는 목록에 추가되는 순서대로 적용됩니다. 이를 통해 View3D 에 두 개 이상의 효과를 쉽게 적용할 수 있으며, ShaderEffect 항목을 중첩하여 Qt Quick 에서 얻을 수 있는 것과 유사합니다. 다음 효과의
INPUT
텍스처는 항상 이전 효과의 출력을 포함하는 텍스처입니다. 마지막 효과의 출력은 View3D 의 최종 출력으로 사용됩니다. - passes 는 ShaderEffect 와 달리 다중 패스를 기본적으로 지원하기 때문에 목록입니다. 다중 패스 효과는 effects 에서 여러 개의 독립적인 효과를 연결하는 것보다 더 강력합니다. 패스는 임시 중간 텍스처로 출력할 수 있으며, 이 텍스처는 효과의 원래 입력 텍스처 외에 후속 패스의 입력으로 사용될 수 있습니다. 이를 통해 최종 조각 색상에 도달하기 위해 여러 텍스처를 계산, 렌더링 및 블렌딩하는 복잡한 효과를 만들 수 있습니다. 이 고급 사용 사례는 여기서 다루지 않습니다. 자세한 내용은 Effect 문서 페이지를 참조하세요.
- shaders 는 하나의 효과에 버텍스와 조각 셰이더가 모두 연결될 수 있으므로 목록입니다.
여러 효과 연결하기
이전 예제의 효과가 기본 제공 DistortionSpiral 효과와 유사한 다른 효과로 보완되는 예제를 살펴보겠습니다.
main.qml의 변경 사항 | effect2.frag |
---|---|
environment: SceneEnvironment { backgroundMode: SceneEnvironment.Color clearColor: "black" effects: [redEffect, distortEffect] } Effect { id: redEffect property real uRed: 1.0 NumberAnimation on uRed { from: 1; to: 0; duration: 5000; loops: -1 } passes: Pass { shaders: Shader { stage: Shader.Fragment shader: "effect.frag" } } } Effect { id: distortEffect property real uRadius: 0.1 NumberAnimation on uRadius { from: 0.1; to: 1.0; duration: 5000; loops: -1 } passes: Pass { shaders: Shader { stage: Shader.Fragment shader: "effect2.frag" } } } | void MAIN() { vec2 center_vec = INPUT_UV - vec2(0.5, 0.5); center_vec.y *= INPUT_SIZE.y / INPUT_SIZE.x; float dist_to_center = length(center_vec) / uRadius; vec2 texcoord = INPUT_UV; if (dist_to_center <= 1.0) { float rotation_amount = (1.0 - dist_to_center) * (1.0 - dist_to_center); float r = radians(360.0) * rotation_amount / 4.0; float cos_r = cos(r); float sin_r = sin(r); mat2 rotation = mat2(cos_r, sin_r, -sin_r, cos_r); texcoord = vec2(0.5, 0.5) + rotation * (INPUT_UV - vec2(0.5, 0.5)); } vec4 c = texture(INPUT, texcoord); FRAGCOLOR = c; } |
이제 의외의 질문이 나올 수도 있습니다. 왜 이것이 나쁜 예시일까요?
더 정확히 말하자면, 나쁘다기보다는 피하는 것이 유익할 수 있는 패턴을 보여주기 때문입니다.
이러한 방식으로 효과를 연결하는 것은 유용할 수 있지만 성능에 미치는 영향을 염두에 두는 것이 중요합니다. 한 번이면 충분한데 두 번의 렌더링 패스(하나는 조정된 빨간색 채널로 텍스처를 생성하고 다른 하나는 왜곡을 계산하는 것)를 수행하는 것은 상당히 낭비적인 일입니다. 조각 셰이더 스니펫을 결합했다면 하나의 효과로 동일한 결과를 얻을 수 있었을 것입니다.
C++에서 메시 및 텍스처 데이터 정의하기
절차적으로 메시와 텍스처 이미지 데이터를 생성하는 방법은 둘 다 비슷한 단계를 따릅니다:
- 서브클래스 QQuick3DGeometry 또는 QQuick3DTextureData
- 베이스 클래스에서 보호된 멤버 함수를 호출하여 생성 시 원하는 버텍스 또는 이미지 데이터를 설정합니다.
- 이후 어느 시점에 동적 변경이 필요한 경우 새 데이터를 설정하고 update() 함수를 호출합니다.
- 구현이 완료되면 클래스를 등록하여 QML에서 볼 수 있도록 해야 합니다.
- Model 및 Texture 객체는 이제 Model::geometry 또는 Texture::textureData 속성을 설정하여 커스텀 버텍스 또는 이미지 데이터 프로바이더를 사용할 수 있습니다.
커스텀 버텍스 데이터
버텍스 데이터는 메시를 구성하는 (일반적으로 float
) 값의 시퀀스를 의미합니다. .mesh
파일을 로드하는 대신 사용자 지정 지오메트리 공급자가 동일한 데이터를 제공합니다. 버텍스 데이터는 위치, 텍스처(UV) 좌표 또는 노멀과 같은 attributes
으로 구성됩니다. 속성 사양은 어떤 종류의 속성이 있는지, 구성 요소 유형(예: x, y, z 값으로 구성된 버텍스 위치의 3성분 실수 벡터), 제공된 데이터에서 어느 오프셋에서 시작하는지, 보폭(동일한 속성에 대해 다음 요소를 가리키기 위해 오프셋에 추가해야 하는 증분)이 무엇인지 등을 설명합니다.
이러한 API에서 버텍스 입력이 지정되는 방식은 .mesh
파일이나 QQuick3DGeometry 인스턴스가 정의하는 것과 느슨하게 매핑되기 때문에 OpenGL이나 Vulkan과 같은 그래픽 API로 직접 작업해 본 사람이라면 익숙하게 느껴질 수 있습니다.
또한 메시 토폴로지(프리미티브 유형)도 지정해야 합니다. 색인화된 드로잉의 경우 색인 버퍼에 대한 데이터도 제공해야 합니다.
QtQuick3D.헬퍼 모듈에는 GridGeometry 유형이 포함된 사용자 지정 지오메트리 구현이 내장되어 있습니다. 이를 통해 사용자 지정 QQuick3DGeometry 서브클래스를 구현하지 않고도 선 프리미티브를 사용하여 씬에서 그리드를 렌더링할 수 있습니다.
또 다른 일반적인 사용 사례는 포인트 렌더링입니다. 이는 어트리뷰트 사양이 최소화될 것이므로 매우 간단합니다. 각 버텍스에 대해 세 개의 플로트(x, y, z)만 제공하고 그 외에는 아무것도 제공하지 않습니다. QQuick3DGeometry 서브클래스는 다음과 유사하게 2000개의 점으로 구성된 지오메트리를 구현할 수 있습니다:
clear(); const int N = 2000; const int stride = 3 * sizeof(float); QByteArray v; v.resize(N * stride); float *p = reinterpret_cast<float *>(v.data()); QRandomGenerator *rg = QRandomGenerator::global(); for (int i = 0; i < N; ++i) { const float x = float(rg->bounded(200.0f) - 100.0f) / 20.0f; const float y = float(rg->bounded(200.0f) - 100.0f) / 20.0f; *p++ = x; *p++ = y; *p++ = 0.0f; } setVertexData(v); setStride(stride); setPrimitiveType(QQuick3DGeometry::PrimitiveType::Points); addAttribute(QQuick3DGeometry::Attribute::PositionSemantic, 0, QQuick3DGeometry::Attribute::F32Type);
의 재질과 결합하면
DefaultMaterial { lighting: DefaultMaterial.NoLighting cullMode: DefaultMaterial.NoCulling diffuseColor: "yellow" pointSize: 4 }
의 머티리얼과 결합하면 최종 결과는 다음과 유사합니다(여기서는 WasdController 의 도움을 받아 변경된 카메라 각도에서 본 모습):
참고: 기본 그래픽 API에 따라 1 이외의 포인트 크기와 선 너비는 런타임에 지원되지 않을 수 있다는 점에 유의하세요. 이는 Qt가 제어할 수 있는 부분이 아닙니다. 따라서 점과 선 그리기에 의존하는 대신 다른 기술을 구현해야 할 수 있습니다.
커스텀 텍스처 데이터
텍스처의 경우 제공해야 하는 데이터는 구조적으로 훨씬 더 단순합니다. 텍스처 형식에 따라 픽셀당 바이트 수가 다른 원시 픽셀 데이터입니다. 예를 들어 RGBA
텍스처는 픽셀당 4바이트가 필요하지만 RGBA16F
은 픽셀당 하프플로트 4개가 필요합니다. 이는 QImage 텍스처가 내부적으로 저장하는 것과 유사합니다. 그러나 Qt Quick 3D 텍스처는 QImage 으로는 표현할 수 없는 데이터 형식을 가질 수 있습니다. 예를 들어 부동 소수점 HDR 텍스처 또는 압축 텍스처가 이에 해당합니다. 따라서 QQuick3DTextureData 의 데이터는 항상 원시 바이트 시퀀스로 제공됩니다. OpenGL이나 Vulkan과 같은 그래픽 API로 직접 작업해 본 적이 있다면 익숙하게 느껴질 수 있습니다.
자세한 내용은 QQuick3DGeometry 및 QQuick3DTextureData 문서 페이지를 참조하세요.
CustomMaterial, Effect, QQuick3DGeometry, QQuick3DTextureData, Qt Quick 3D - 커스텀 이펙트 예제, Qt Quick 3D - 커스텀 셰이더 예제, Qt Quick 3D - 커스텀 머티리얼 예제, Qt Quick 3D - 커스텀 지오메트리 예제, Qt Quick 3D - 프로시저럴 텍스처 예제도참조하세요 .
© 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.