このページでは

プログラム可能なマテリアル、エフェクト、ジオメトリ、およびテクスチャデータ

Qt Quick 3D、DefaultMaterial 、およびPrincipledMaterial に組み込まれているマテリアルは、そのプロパティを通じて幅広いカスタマイズが可能ですが、頂点シェーダーやフラグメントシェーダーレベルでのプログラム機能は提供されていません。これを可能にするために、CustomMaterial 型が提供されています。

次のようなモデルではPrincipledMaterialCustomMaterial による頂点の変換

標準マテリアルでレンダリングされたティーポット

カスタムマテリアルによって頂点変換されたティーポット

ポストプロセッシング効果には、View3D の出力がQt Quick に渡される前に、カラーバッファに対して1回以上の処理パスが実行され、必要に応じて深度バッファも考慮されるものがあり、これには2つの種類があります:

  • ExtendedSceneEnvironment を介して設定可能な組み込みのポストプロセッシングステップ(グロー/ブルーム、被写界深度、ヴィネット、レンズフレアなど)と、
  • custom アプリケーション側で、フラグメントシェーダーコードとEffect オブジェクト内の処理パスの仕様を用いて実装されるエフェクト。

実際には、3つ目のポストプロセッシングエフェクトのカテゴリが存在します。それは、Qt Quick を通じて実装され、3Dレンダラーを一切介さずにView3D 項目の出力に対して動作する2Dエフェクトです。 たとえば、View3D アイテムにぼかしを適用する場合、最も簡単なアプローチは、MultiEffect など、Qt Quick の既存の機能を使用することです。3Dポストプロセスシステムは、深度バッファやスクリーンテクスチャといった3Dシーンの概念が関わる複雑なエフェクトや、HDRトーンマッピングを扱う必要がある場合、あるいは中間バッファを用いた複数のパスが必要な場合などに有効です。 3Dシーンやレンダラーに関する知識を一切必要としない単純な2Dエフェクトについては、代わりにShaderEffect やMultiEffect を使用して実装することが常に可能です。

エフェクトを適用していないシーンカスタムポストプロセッシングエフェクトを適用した同じシーン

後処理効果を適用していない、球、円錐、立方体が描かれたシーン

カスタム後処理エフェクトによる、ジオメトリが歪んだシーン

プログラム可能なマテリアルやポストプロセッシングに加え、通常はファイル形式(.mesh ファイルや.png などの画像)で提供されるデータには、以下の2種類があります:

  • 頂点データ(レンダリング対象のメッシュのジオメトリ、テクスチャ座標、法線、色、その他のデータを含む)と、
  • レンダリングされたオブジェクトのテクスチャマップとして、あるいはスカイボックスや画像ベースのライティングで使用されるテクスチャのコンテンツ。

アプリケーションは、必要に応じて、C++からQByteArray の形式でそのようなデータを提供できます。また、このデータは時間の経過とともに変更することが可能であり、Model やTexture のデータをプロシージャルに生成し、後で変更することも可能です。

C++から動的に頂点データを指定してレンダリングされたグリッドC++から生成された画像データでテクスチャ化された立方体

カスタム形状データから生成されたグリッド

プロシージャルに生成されたグラデーションテクスチャを施した立方体

マテリアル、エフェクト、ジオメトリ、テクスチャをカスタマイズし、動的に生成するためのこれら4つのアプローチにより、シェーディングのプログラム化や、シェーダーが入力として受け取るデータのプロシージャル生成が可能になります。以下のセクションでは、これらの機能の概要を説明します。詳細なリファレンスは、各タイプのドキュメントページでご覧いただけます:

注: 複数のレンダーパスやカスタム・レンダーターゲットを含む、レンダリング・パイプライン全体の完全な制御については 、『Qt Quick 3D』の「ユーザー定義レンダーパス」を参照してください。ユーザー定義レンダーパスは、レンダリング・パイプラインのアーキテクチャ全体を定義できるようにすることで、このページで説明されている機能を補完するものです。一方、「CustomMaterial 」および「Effect」は、パイプライン内の個々のマテリアルやポストプロセッシング・ステップのカスタマイズに重点を置いています。

機能リファレンスドキュメント関連するサンプル
カスタムマテリアルCustomMaterialQt Quick 3D - カスタムシェーダーの例、Qt Quick 3D - カスタムマテリアルの例
カスタム・ポストプロセッシング・エフェクトEffectQt Quick 3D - カスタムエフェクトの例
カスタムジオメトリQQuick3DGeometry,Model::geometryQt Quick 3D - カスタムジオメトリの例
カスタムテクスチャデータQQuick3DTextureData,Texture::textureDataQt Quick 3D - プロシージャルテクスチャの例

マテリアルのプログラム機能

立方体を含むシーンを用意し、デフォルトのPrincipledMaterial とCustomMaterial から始めましょう:

PrincipledMaterialCustomMaterial
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 { }
         }
    }
}

これら2つはまったく同じ結果になります。なぜなら、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 のカスタムマテリアルおよびポストプロセッシングエフェクトでは、参照先ファイル内にソース形式のシェーダースニペットが依然として提供されています。 これは、ShaderEffect とは異なります。 では、シェーダーはそれ自体で完全であり、エンジンによる追加の修正は行われないため、あらかじめ準備された.qsb シェーダーパックとして提供されることが想定されています。

注: Qt Quick 3Dでは 、URL はローカルリソースのみを参照できます。リモートコンテンツのスキームはサポートされていません。

注: 使用されるシェーディング言語は 、Vulkan互換のGLSLです。.vert および.frag ファイルは、それ自体では完全なシェーダーではないため、しばしばsnippets と呼ばれます。そのため、これらのスニペットからは、uniformブロック、入力変数、出力変数、またはsampler uniformが直接提供されません。代わりに、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 といった、あらかじめ定義された名前の関数を1つ以上提供することが期待されます。ここでは、MAIN に焦点を当てましょう。

ここに示されているように、MAIN() を空にした場合の最終結果は、以前とまったく同じです。

さらに興味深い内容に進む前に、カスタム頂点シェーダーのスニペットで最もよく使われる特別なキーワードの概要を見てみましょう。これは完全なリストではありません。完全なリファレンスについては、CustomMaterial のページを参照してください。

キーワード型説明
MAINvoid MAIN() はエントリポイントです。この関数はカスタム頂点シェーダースニペットに必ず含まれていなければなりません。そうでなければ、スニペットを作成する意味がありません。
VERTEXvec3シェーダーが入力として受け取る頂点の位置です。カスタムマテリアルにおける頂点シェーダーの一般的な用途は、ベクトル全体またはその一部の成分に値を割り当てるだけで、このベクトルの x、y、z の値を変更(変位)させることです。
NORMALvec3入力メッシュデータからの頂点法線。法線が指定されていない場合は、すべてゼロになります。VERTEXと同様に、シェーダーはこの値を任意に変更できます。変更された値は、フラグメントステージでのライティング計算を含め、パイプラインの残りの部分で使用されます。
UV0vec2入力メッシュデータからの最初のテクスチャ座標セット。UV値が指定されていない場合は、すべてゼロになります。VERTEXやNORMALと同様に、この値は変更可能です。
MODELVIEWPROJECTION_MATRIXmat4モデル・ビュー・投影行列。どのグラフィックスAPIでレンダリングが行われるかにかかわらず動作を統一するため、このレベルではすべての頂点データおよび変換行列がOpenGLの規約に従います(Y軸が上向き、OpenGL互換の投影行列)。読み取り専用です。
MODEL_MATRIXmat4モデル(ワールド)行列。読み取り専用。
NORMAL_MATRIXmat3モデル行列の左上隅の3×3スライスの転置逆行列。読み取り専用。
CAMERA_POSITIONvec3ワールド座標系におけるカメラの位置。このページの例では、(0, 0, 600) となります。読み取り専用。
CAMERA_DIRECTIONvec3カメラの方向ベクトル。このページの例では、(0, 0, -1) となります。読み取り専用です。
CAMERA_PROPERTIESvec2カメラのニアクリップ値およびファークリップ値。このページの例では、(10, 10000) です。読み取り専用です。
POINT_SIZEfloatcustom geometry がメッシュに対してそのようなジオメトリを提供している場合など、点のトポロジーを使用してレンダリングする場合にのみ関連します。この値に書き込むことは、pointSize on a PrincipledMaterial を設定することと同等です。
POSITIONvec4gl_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 型シェーダー型備考
real、int、boolfloat、int、bool
colorvec4sRGB からリニアへの変換は暗黙的に行われます
vector2dvec2
vector3dvec3
vector4dvec4
matrix4x4mat4
クォータニオンvec4スカラー値はw
rectvec4
点、サイズvec2
TextureInputsampler2D

例の改良

先に進む前に、この例をもう少し見栄えの良いものにしましょう。回転させた矩形メッシュを追加し、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_COLORvec4ベースカラーとアルファ値。PrincipledMaterial::baseColor に対応します。フラグメントの最終的なアルファ値は、モデルの不透明度にベースカラーのアルファ値を乗じたものになります。デフォルト値は(1.0, 1.0, 1.0, 1.0) です。
EMISSIVE_COLORvec3自己発光の色。PrincipledMaterial::emissiveFactor に対応します。デフォルト値は(0.0, 0.0, 0.0) です。
METALNESSfloatMetalness 0~1の範囲の値。デフォルトは0で、これはマテリアルが誘電体(非金属)であることを意味します。
ROUGHNESSfloatRoughness 0~1の範囲の値。デフォルト値は0です。値を大きくすると、鏡面反射のハイライトが和らぎ、反射がぼやけます。
SPECULAR_AMOUNTfloatThe strength of specularity 0~1の範囲。デフォルト値は0.5 です。metalness が1 に設定されている金属系のオブジェクトの場合、この値は影響しません。SPECULAR_AMOUNT とMETALNESS の両方の値が0より大きく1より小さい場合、結果は2つのマテリアルモデルのブレンドとなります。
NORMALvec3ワールド空間における補間された法線。フェイスカリングが無効な場合、両面性を考慮して調整されます。読み取り専用です。
UV0vec2補間されたテクスチャ座標。読み取り専用。
VAR_WORLD_POSITIONvec3ワールド座標系における補間された頂点位置。読み取り専用。

キューブのベースカラーを赤にしてみましょう:

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座標を赤と緑のグラデーションで表示した立方体

ついでに、球体上で法線も可視化してみましょう。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);
}

表面の法線をRGBカラーで可視化した球体

色

ここで、ひとまずティーポットのモデルに切り替えて、マテリアルを金属質と誘電質のブレンドにし、そのベースカラーを緑に設定してみましょう。「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;
}

RGBの色値が直接指定された緑色のティーポット

これでは完全には適切とは言えません。2つ目のアプローチと比較してみましょう:

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;
}

QMLのcolorプロパティとsRGB変換を用いた緑色のティーポット

PrincipledMaterial に変更すると、PrincipledMaterial::baseColor を「green」に設定し、メタリティやその他のプロパティに従うことで、結果が2番目のアプローチと同一になることが確認できます:

main.qmlの変更結果
materials: PrincipledMaterial {
    baseColor: "green"
    metalness: 0.6
    specularAmount: 0.4
    roughness: 0.4
}

標準的な素材で作られた緑色のティーポットで、色が正しく再現されている

uColor プロパティの型をvector4d に変更した場合、あるいはcolor 以外の型に変更した場合、結果は一変し、最初のアプローチと同じ結果になります。

なぜでしょうか?

その答えは、DefaultMaterialやPrincipledMaterial のカラープロパティ、およびCustomMaterial 内でcolor 型を持つカスタムプロパティに対して暗黙的に行われるsRGBからリニアへの変換にあります。 他の値に対してはこのような変換は行われないため、シェーダーが色値をハードコードしている場合、またはcolor とは異なる型のQMLプロパティに基づいている場合、ソース値がsRGB色空間にある場合は、シェーダー側で線形化を行う必要があります。Qt Quick の3Dでは、フラグメントシェーディングの結果に対してtonemapping を実行するため、リニア化への変換は重要です。この処理では、sRGB空間の値が入力として想定されています。

"green" などの組み込みのQColor 定数は、すべてsRGB空間で指定されています。したがって、sRGB空間のRGB値(0, 128, 0) と一致する結果を得たい場合、最初にvec4(0.0, 0.5, 0.0, 1.0) をBASE_COLOR に代入するだけでは不十分です。 このような色値を線形化する式については、CustomMaterial の「BASE_COLOR 」のドキュメントを参照してください。これは、テクスチャのサンプリングによって取得された色値にも同様に適用されます。ソース画像データが sRGB 色空間でない場合、変換が必要となります(tonemapping が無効になっている場合を除く)。

ブレンディング

アルファブレンディングを行うことを想定している場合、単に1.0 より小さい値をBASE_COLOR.a に書き込むだけでは不十分です。このようなマテリアルでは、望ましい結果を得るために、sourceBlend およびdestinationBlend プロパティの値を変更することが非常に多いでしょう。

また、合成アルファ値は、Node opacity にマテリアルのアルファ値を乗じた値になる点にも留意してください。

これを視覚化するために、アルファ値が0.5 である赤をBASE_COLOR に割り当てるシェーダーを使用してみましょう:

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);
}

ブレンドモードによる不透明度の変化を示す3つの立方体

最初のキューブは色のアルファ値に0.5を書き込んでいますが、アルファブレンディングが有効になっていないため、目に見える変化は生じません。2番目のキューブは、CustomMaterial プロパティを使用して簡易的なアルファブレンディングを有効にしています。3番目のキューブは、Modelに不透明度0.5も割り当てており、これにより実質的な不透明度は0.25となります。

頂点シェーダーとフラグメントシェーダー間のデータ受け渡し

頂点ごとに値を計算し(例えば、単一の三角形の場合、その3つの頂点について)、それをフラグメントステージに渡します。そこで、各フラグメント(例えば、ラスタライズされた三角形に覆われるすべてのフラグメント)に対して、補間された値が利用可能になります。 カスタムマテリアルシェーダーのスニペットでは、VARYING キーワードによってこれが可能になります。これはGLSL 120やGLSL ES 100に似た構文を提供しますが、実行時に使用されるグラフィックスAPIに関係なく動作します。エンジンが、必要に応じてvarying宣言の書き換えを処理します。

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結果

ソースデータとして使用されたQtロゴのテクスチャ

各面にQtのロゴテクスチャが適用された立方体

VARYING の宣言に注意してください。名前と型は一致する必要があります。フラグメントシェーダー内のuv は、現在のフラグメントに対する補間されたUV座標を公開します。

他のタイプのデータも、同様の方法でフラグメントステージに渡すことができます。多くの場合、一般的なニーズの多くをカバーする組み込み変数が用意されているため、マテリアル独自のvaryingを設定する必要はないことに注意してください。 これには、(補間された)法線、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);
}

各面にQtのロゴテクスチャが適用された立方体

変数の補間を無効にするには、頂点シェーダーとフラグメントシェーダーの両方のスニペットで `flat ` キーワードを使用します。例:

VARYING flat vec2 v;

テクスチャ

CustomMaterial には組み込みのテクスチャマップが存在しません。つまり、例えばPrincipledMaterial::baseColorMap に相当する機能はありません。これは、同様の機能を実装することが多くの場合容易である一方で、DefaultMaterialやPrincipledMaterial に組み込まれている機能よりもはるかに高い柔軟性を提供するためです。 単にテクスチャをサンプリングするだけでなく、カスタムフラグメントシェーダーのスニペットでは、BASE_COLOR 、EMISSIVE_COLOR 、ROUGHNESS などに割り当てる値を計算する際、さまざまなソースからのデータを自由に組み合わせたりブレンドしたりできます。これらの計算は、QMLプロパティを通じて提供されるデータ、頂点ステージから送られてきた補間データ、テクスチャサンプリングから取得した値、およびハードコードされた値に基づいて行うことができます。

前の例が示すように、テクスチャを頂点シェーダー、フラグメントシェーダー、あるいはその両方に公開することは、スカラーおよびベクトルのユニフォーム値の場合と非常に似ています。型がTextureInput であるQMLプロパティは、シェーダーコード内のsampler2D に自動的に関連付けられます。いつものように、シェーダーコード内でこのサンプラーを宣言する必要はありません。

TextureInput は、追加のenabled プロパティを持つTexture を参照します。Texture は、3つの方法でデータを取得できます: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サブツリー(2つの子要素を持つRectangle:もう1つのRectangleとText)が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 は、シーン内のすべての オブジェクトがレンダリングされた状態の深度バッファの内容をテクスチャとしてサンプリングすることを可能にします。同様に、 は であり、透明なマテリアルやSCREEN_TEXTUREを使用しているマテリアルを除いたシーンの内容を含むテクスチャをサンプリングすることを可能にします。 このテクスチャは、レンダリング先のフレームバッファの内容を必要とするマテリアルに使用できます。SCREEN_TEXTUREテクスチャは、 と同じクリアモードを使用します。これらのテクスチャのサイズは、 のピクセルサイズと一致します。opaque SCREEN_TEXTURE sampler2D 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 の高度な機能の一つは、フラグメントカラーの計算に使用される照明方程式を再実装する関数を、フラグメントシェーダー内で定義できることです。ライトプロセッサ関数が存在する場合、シーン内の各ライトについて、フラグメントごとに 1 回ずつ呼び出されます。 ライトの種類ごとに専用の関数が用意されており、アンビエント光やスペキュラー光の寄与についても同様です。対応するライトプロセッサ関数が存在しない場合、PrincipledMaterial と同様に標準的な計算が使用されます。ライトプロセッサが存在しても関数本体が空の場合、シーン内のその種類のライトからの寄与はないことを意味します。

DIRECTIONAL_LIGHT 、POINT_LIGHT 、SPOT_LIGHT 、AMBIENT_LIGHT 、SPECULAR_LIGHT などの関数の詳細については、『CustomMaterial 』のドキュメントを参照してください。

シェーディングなしのカスタムマテリアル

CustomMaterial にはもう1つのタイプがあります。それはunshaded のカスタムマテリアルです。これまでのすべての例では、shaded のカスタムマテリアルを使用しており、shadingMode プロパティはデフォルトのCustomMaterial.Shaded値のままにしていました。

このプロパティをCustomMaterial.Unshadedに変更するとどうなるでしょうか?

まず第一に、BASE_COLOR 、EMISSIVE_COLOR 、METALNESS などのキーワードは、もはや期待通りの効果を発揮しなくなります。これは、アンシェーデッドマテリアルが、その名前が示す通り、標準的なシェーディングコードの多くを自動的に適用しないためであり、シーン内のライト、画像ベースのライティング、シャドウ、アンビエントオクルージョンを無視することになります。 その代わり、アンシェーデッドマテリアルでは、FRAGCOLOR キーワードを通じてシェーダーに完全な制御権が与えられます。これはgl_FragColorと同様で、FRAGCOLOR に割り当てられた色が、Qt Quick 3Dによるさらなる調整を経ることなく、フラグメントの結果および最終的な色となります。

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 の結果に対して1つ以上のフラグメントシェーダーを適用します。これらのフラグメントシェーダーからの出力が、元のレンダリング結果の代わりに表示されます。これは概念的に、Qt Quick のShaderEffect およびShaderEffectSource と非常に似ています。

注:ポストプロセッシング エフェクトは、View3D のrenderMode がView3D.Offscreenに設定されている場合にのみ利用可能です。

エフェクトに対してカスタム頂点シェーダーのスニペットを指定することも可能ですが、その有用性は限られているため、使用される機会は比較的少ないと予想されます。ポストプロセッシングエフェクトの頂点入力はクワッド(2つの三角形または三角形ストリップ)であり、その頂点を変形させたり変位させたりしても、多くの場合、あまり意味がありません。 ただし、VARYING キーワードを使用してデータを計算し、フラグメントシェーダーに渡すために、頂点シェーダーを用意することは理にかなっている場合があります。通常通り、フラグメントシェーダーは、現在のフラグメント座標に基づいて補間された値を受け取ります。

Effect に関連付けられたシェーダスニペットの構文は、シェーディングされないCustomMaterial 用のシェーダと同一です。組み込みの特殊キーワードに関しては、VARYING 、MAIN 、FRAGCOLOR (フラグメントシェーダのみ)、POSITION (頂点シェーダのみ)、VERTEX (頂点シェーダのみ)、およびMODELVIEWPROJECTION_MATRIX は、CustomMaterial と同様に機能します。

Effect のフラグメントシェーダーにおいて最も重要な特殊キーワードは、以下の通りです:

名前型説明
INPUTsampler2D または sampler2DArray入力テクスチャ用のサンプラー。エフェクトでは通常、INPUT_UV を使用してこれをサンプリングします。
INPUT_UVvec2INPUT のサンプリング用UV座標。
INPUT_SIZEvec2INPUT テクスチャのサイズ(ピクセル単位)。これは、textureSize() を呼び出す代わりに便利に使用できます。
OUTPUT_SIZEvec2出力テクスチャのサイズ(ピクセル単位)。多くの場合、INPUT_SIZE と同じになりますが、マルチパスエフェクトでは、異なるサイズの中間テクスチャに出力するパスが含まれる場合があります。
DEPTH_TEXTUREsampler2Dシーン内の不透明なオブジェクトを含む深度バッファの内容を持つ深度テクスチャ。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、passes、shaders)です。要素が1つのみの場合はリスト[ ] の構文を省略できますが、これらのプロパティはすべてリストであり、複数の要素を含むことができます。なぜでしょうか?

  • effects View3D はリストである理由は、複数のエフェクトを連鎖させることができるためです。エフェクトは、リストに追加された順序で適用されます。これにより、 に対して2つ以上のエフェクトを簡単に組み合わせて適用することが可能になり、これは で 項目をネストさせることで実現できるものと同様です。 次のエフェクトの テクスチャは、常に前のエフェクトの出力を含むテクスチャになります。一連のエフェクトの最後にあるエフェクトの出力が、 の最終出力として使用されます。View3D Qt Quick ShaderEffect INPUT View3D
  • passes はリスト形式となっています。これは、ShaderEffect とは異なり、Effectにはマルチパス機能が組み込まれているためです。マルチパスエフェクトは、effects で複数の独立したエフェクトを連結するよりも強力です。パスは、エフェクトの元の入力テクスチャに加えて、一時的な中間テクスチャに出力することができ、そのテクスチャを後続のパスの入力として使用できるからです。 これにより、最終的なフラグメントカラーを得るために、複数のテクスチャを計算・レンダリングし、それらをブレンドする複雑なエフェクトを作成することが可能になります。この高度な使用例については、ここでは扱いません。詳細については、Effect のドキュメントページを参照してください。
  • shaders はリストです。これは、1つのエフェクトに頂点シェーダーとフラグメントシェーダーの両方が関連付けられている場合があるためです。

複数のエフェクトの連鎖

前の例のエフェクトを、組み込みの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 * FRAMEBUFFER_Y_UP;
        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;
}

ここで、おそらく意外な疑問が浮かぶでしょう。なぜこれが悪い例なのでしょうか?

より正確に言えば、これは悪い例というわけではありませんが、避ける方が得策であることが多いパターンを示していると言えます。

このようにエフェクトを連鎖させることは有用ですが、パフォーマンスへの影響を念頭に置くことが重要です。2回のレンダリングパス(1回目は赤のカラーチャンネルを調整したテクスチャを生成するため、2回目は歪みを計算するため)を行うことは、1回で済む場合、かなり無駄です。 フラグメントシェーダーのスニペットを組み合わせれば、1 つのエフェクトだけで同じ結果を得ることができたはずです。

C++ からのメッシュおよびテクスチャデータの定義

メッシュデータとテクスチャ画像データの両方を手続き的に生成するには、同様の手順に従います:

  • QQuick3DGeometry をサブクラス化するか、QQuick3DTextureData
  • コンストラクタ内で基底クラスのプロテクトされたメンバ関数を呼び出し、目的の頂点データまたは画像データを設定する
  • その後、ある時点で動的な変更が必要になった場合は、新しいデータを設定し、update() を呼び出します
  • 実装が完了したら、QMLでそのクラスが認識されるように登録する必要があります
  • Model これにより、QML内のTexture オブジェクトは、Model::geometry またはTexture::textureData プロパティを設定することで、カスタム頂点データまたは画像データプロバイダを使用できるようになります

カスタム頂点データ

頂点データとは、メッシュを構成する(通常はfloat )値のシーケンスを指します。.mesh ファイルをロードする代わりに、カスタムジオメトリプロバイダが同じデータを提供します。 頂点データは、位置、テクスチャ(UV)座標、法線などのattributes で構成されます。属性の仕様では、どのような属性が存在するか、コンポーネントの型(例えば、x、 y、zの値からなる3成分のfloatベクトル)、提供されたデータ内の開始オフセット、およびストライド(同じ属性の次の要素を指すためにオフセットに加える必要がある増分)が何であるかを記述します。

OpenGLやVulkanなどのグラフィックスAPIを直接扱った経験がある方なら、この仕組みに馴染みがあるかもしれません。これらのAPIにおける頂点入力の指定方法は、.mesh ファイルやQQuick3DGeometry インスタンスで定義される内容と概ね対応しているからです。

さらに、メッシュのトポロジー(プリミティブタイプ)も指定する必要があります。インデックス描画を行う場合は、インデックスバッファ用のデータも提供する必要があります。

組み込みのカスタムジオメトリ実装が1つあります。QtQuick3D の.Helpersモジュールには、GridGeometry 型が含まれています。これにより、カスタムQQuick3DGeometry サブクラスを実装することなく、線プリミティブを使用してシーン内にグリッドをレンダリングできます。

もう1つの一般的なユースケースは、点のレンダリングです。属性の指定は最小限で済むため、これは比較的簡単に行えます。各頂点に対して3つの浮動小数点数(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 - プロシージャルテクスチャの例。

© 2026 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.