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

Qt Quick 3DのビルトインマテリアルであるDefaultMaterialPrincipledMaterial は、プロパティによって幅広いカスタマイズが可能ですが、頂点やフラグメントシェーダレベルでのプログラマビリティは提供されていません。これを可能にするために、CustomMaterial タイプが提供されています。

モデルPrincipledMaterial頂点を変換するCustomMaterial

View3D の出力がQt Quick に渡される前に、カラーバッファの処理が 1 回以上実行され、 オプションで深度バッファが考慮される、後処理エフェクトも 2 種類あります:

  • グロー/ブルーム、被写界深度、ビネット、レンズフレアなど、ExtendedSceneEnvironment を介して設定可能なビルトイン後処理ステップ、
  • custom フラグメントシェーダコードの形でアプリケーションによって実装されるエフェクトと、 オブジェクト内の処理パスの指定。Effect

実際には、ポスト処理エフェクトの第3のカテゴリがあります。Qt Quick を介して実装された2Dエフェクトは、3Dレンダラーが関与することなく、View3D アイテムの出力で動作します。たとえば、View3D アイテムにぼかしを適用する場合、Qt Quick の既存の機能(MultiEffect など)を使用するのが最も簡単な方法です。3Dポスト処理システムは、深度バッファやスクリーンテクスチャなどの3Dシーンコンセプトを含む複雑なエフェクトや、HDRトーンマッピングを処理する必要がある場合、または中間バッファなどで複数のパスを必要とする場合に有益になります。3Dシーンやレンダラーへの洞察を必要としないシンプルな2Dエフェクトは、ShaderEffect またはMultiEffect で実装できます。

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

プログラム可能なマテリアルとポストプロセスに加えて、通常ファイル(.mesh ファイルまたは.png のようなイメージ)の形で提供される 2 種類のデータがあります:

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

QByteArrayこのようなデータは時間と共に変更することもでき、プロシージャルに生成し、後でModel またはTexture のデータを変更することができます。

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

マテリアル、エフェクト、ジオメトリ、およびテクスチャをカスタマイズし、動的にするためのこれら4つのアプローチにより、シェーダのプログラマビリティと、シェーダが入力として取得するデータの手続き的生成が可能になります。以下のセクションでは、これらの機能の概要を説明します。完全なリファレンスは、それぞれのタイプのドキュメンテーションページにあります:

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

立方体のあるシーンで、デフォルトのPrincipledMaterialCustomMaterial から始めてみましょう:

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

頂点シェーダやフラグメントシェーダのコードが追加されていない場合、CustomMaterial は事実上PrincipledMaterial であるため、これらは両方ともまったく同じ結果になります。

注: baseColormetalnessbaseColorMap などのプロパティは、CustomMaterial QML タイプには同等のプロパティがありません。これは設計によるもので、マテリアルのカスタマイズはシェーダコードによって 行われ、単にいくつかの固定値を提供するだけではありません。

最初の頂点シェーダ

カスタム頂点シェーダのスニペットを追加してみましょう。これはvertexShader プロパティでファイルを参照することで行います。この方法はフラグメントシェーダでも同じです。これらの参照は、Image.sourceShaderEffect.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 と呼ばれます。そのため、これらのスニペットによって直接提供されるユニフォームブロック、入出力変数、またはサンプラーユニフォームはありません。むしろ、Qt Quick 3Dエンジンがそれらを適切に修正します。

main.qml、material.vertの変更。結果
materials: CustomMaterial {
    vertexShader: "material.vert"
}
void MAIN()
{
}

カスタム頂点またはフラグメントシェーダースニペットは、MAINDIRECTIONAL_LIGHTPOINT_LIGHTSPOT_LIGHTAMBIENT_LIGHTSPECULAR_LIGHT のようなあらかじめ定義された名前を持つ1つ以上の関数を提供することが期待されます。ここではMAIN に注目しましょう。

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

もっと面白くする前に、カスタム頂点シェーダースニペットで最もよく使 われる特殊キーワードの概要を見てみましょう。これは完全なリストではありません。完全なリファレンスはCustomMaterial のページを参照してください。

キーワードタイプ説明
MAINvoid MAIN() がエントリポイントです。この関数はカスタム頂点シェーダースニペットに必ず存在しなければなりません。
VERTEXvec3シェーダが入力として受け取る頂点位置。カスタムマテリアルの頂点シェーダの一般的な使用例は、このベクトルの x、y、または z 値を変更(変位)することです。
標準vec3入力メッシュデータからの頂点法線、または法線が提供されていない場合はすべてゼロ。VERTEX と同様に、シェーダはこの値を自由に変更できます。変更された値は、フラグメントステージのライティング計算を含むパイプラインの残りの部分で使用されます。
UV0vec2入力メッシュデータからのテクスチャ座標の最初のセット、または提供されたUV値がない場合はすべてゼロ。VERTEXやNORMALと同様に、値を変更することができます。
モデルビュープロジェクションマトリックスmat4モデルビュープロジェクション行列。レンダリングがどのグラフィックスAPIで行われるかに関係なく動作を統一するために、すべての頂点データと変換行列はこのレベルでOpenGLの規約に従います。(Y軸が上向き、OpenGL互換の投影行列)読み取り専用。
MODEL_MATRIXmat4モデル(ワールド)行列。読み込み専用。
NORMAL_MATRIXmat3モデル行列の左上 3x3 スライスの転置逆行列.読み込み専用.
カメラ位置vec3ワールド空間におけるカメラの位置.このページの例では,これは(0, 0, 600) です.読み取り専用。
カメラ方向vec3カメラの方向ベクトル。このページの例では、これは(0, 0, -1) です。読み込み専用。
カメラプロパティvec2カメラの近距離クリップ値と遠距離クリップ値。このページの例では、これは(10, 10000) です。読み込み専用。
POINT_SIZEfloat点のトポロジでレンダリングするときのみ関係します。たとえば、custom 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 オブジェクトのカスタムプロパティがユニフォームにマッピングされます。上記の例では、uAmplitudeuTime が含まれます。値が変更されるたびに、更新された値がシェーダで見えるようになります。この概念はShaderEffect ですでにお馴染みかもしれません。

QML プロパティの名前と GLSL 変数の名前は一致しなければなりません。シェーダーコードには、個々のユニフォームのための個別の宣言はありません。むしろ、QML プロパティ名をそのまま使うことができます。そのため、上記の例では、頂点シェーダースニペットの中で、uTimeuAmplitude を、事前に宣言することなく参照することができます。

次の表は、QML タイプがどのようにマッピングされているかを示しています:

QML タイプシェーダタイプ備考
real, int, boolfloat, int, bool
vec4sRGBからリニアへの変換が暗黙的に行われる
vector2dvec2
ベクトル3dvec3
ベクトル4dベクトル4
行列4x4mat4
四元数vec4スカラー値はw
rectvec4
点、サイズ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_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 で、これは素材が誘電体(非金属)であることを意味する。
ラフネスフロートRoughness 値を 0-1 の範囲で指定します。デフォルト値は0です。値を大きくするとスペキュラのハイライトが柔らかくなり、反射がぼやけます。
鏡面反射量floatThe strength of specularity を 0-1 の範囲で指定します。デフ ォル ト 値は です。メ タ リ ッ ク オブジ ェ ク ト で が に設定 さ れてい る 場合は、 こ の値は意味を持ち ません。 と の両方の値が 0 より大きく 1 より小さい場合、結果は 2 つのマテリアルモデルのブレンドになります。0.5 metalness 1 SPECULAR_AMOUNT METALNESS
標準vec3ワールド空間で補間された法線。顔のカリングが無効な場合、両面性が調整されます。読み取り専用。
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と同じように、カスタム頂点シェーダースニペットが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;
}

これはまったく正しく見えません。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;
}

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 のカラープロパティ、およびCustomMaterialcolor タイプを持つカスタムプロパティに対して暗黙的に実行される sRGB から linear への変換にあります。このような変換は他の値に対しては実行されないので、シェーダがカラー値をハードコードする場合、または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 に代入するだけでは不十分です。このような色値を線形化する式については、CustomMaterialBASE_COLOR ドキュメントを参照してください。テ キ ス ト をサンプ リ ン グ し て取得 し た色値 も 同 じ です : 元画像デー タ が sRGB 色空間にない と き は、 変換が必要です (tonemapping が無効でない限り)。

ブレンド

アルファブレンディングを期待する場合、1.0 より小さい値をBASE_COLOR.a に書き込むだけでは十分ではありません。そのような素材では、sourceBlenddestinationBlend プロパティの値を変更して、望ましい結果を得ることが非常によくあります。

また、合成アルファ値は、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);
}

最初のキューブは、色のアルファ値に0.5を書き込んでいますが、アルファブレンディングが有効になっていないため、目に見える結果は得られません。2つ目のキューブでは、CustomMaterial プロパティによって単純なアルファブレンディングが有効になっている。3つ目のキューブもModelに0.5の不透明度を割り当てており、これは有効な不透明度が0.25であることを意味します。

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

頂点ごとに値を計算し(たとえば、1つの三角形を仮定し、三角形の3つの角について)、それをフラグメントステージに渡し、各フラグメント(たとえば、ラスタライズされた三角形に覆われたすべてのフラグメント)について、補間された値にアクセスできるようにします。カスタムマテリアルシェーダースニペットでは、VARYING キーワードによってこれが可能になります。これはGLSL 120やGLSL ES 100に似た構文を提供しますが、実行時に使用されるグラフィックスAPIに関係なく動作します。しかし、実行時に使用されるグラフィックス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_COLOREMISSIVE_COLORROUGHNESS などに割り当てる値を計算するときに、さまざまなソースからのデータを 自由に組み合わせ、ブレンドすることができます。QML プロパティ経由で提供されるデータ、頂点ステージから送信される補間 データ、テクスチャのサンプリングから取得した値、ハードコードされた値な どに基づいて計算することができます。

前の例が示すように、テクスチャをバーテックス、フラグメント、または両方のシェーダに公開することは、スカラやベクトルの一様な値と非常によく似ています。TextureInput タイプの QML プロパティは、シェーダコード内で自動的にsampler2D に関連付けられます。いつものように、シェーダコードでこのサンプラーを宣言する必要はありません。

TextureInputTexture を参照し、さらにenabled プロパティを追加します。Texture は、3 つの方法でデータを取得できます:from an image file, from a texture with live Qt Quick contentまたはQQuick3DTextureData を介したcan be provided from C++ です。

注: Texture プロパティに関して言えば、ソース、タイリング、フィルタリングに関連するも のだけがカスタムマテリアルで暗黙的に考慮されます。

モデル(この場合は球体)をライブ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サブツリー(Rectangle with two children: 別のRectangleとText)が、このミニシーンが変化するたびに、512x512の2Dテクスチャにレンダリングされます。このテクスチャはsomeTextureMap という名前でカスタムマテリアルに公開されます。

シェーダの V 座標の反転に注意してください。上述したように、シェーダレベルで完全にプログラム可能なカスタムマテリアルには、TexturePrincipledMaterial のような「固定」機能はありません。つまり、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 { }
        }
    }
}

では、メッシュに高さマップと法線マップを適用するシェーダーを作ろう:

高さマップ法線マップ

マテリアル.vert, マテリアル.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" }
    }
}

Note: WasdController オブジェクトは、使い慣れたキーボードとマウスでシーンをナビゲートしたり、見回したりできるので、開発やトラブルシューティングの際に非常に役立ちます。WasdController でカメラをコントロールするのは、とても簡単です:

import QtQuick3D.Helpers
View3D {
    PerspectiveCamera {
        id: camera
    }
    // ...
}
WasdController {
    controlledObject: camera
}

デプスとスクリーンテクスチャ

カスタムシェーダースニペットがDEPTH_TEXTURE またはSCREEN_TEXTURE キーワードを使用すると、対応するテクスチャを別個のレンダーパスで生成することを選択します。これは必ずしも安価な操作ではありませんが、ガラスのようなマテリアルの屈折など、さまざまなテクニックを実装できます。

DEPTH_TEXTURE は で、シーン内のすべての オブジェクトがレンダリングされた深度バッファの内容でテクスチャをサンプリングできます。同様に、 は で、透明なマテリアルやSCREEN_TEXTURE を使用しているマテリアルを除いたシーンの内容を含むテクスチャをサンプリングできます。このテクスチャは、レンダリング先のフレームバッファの内容を必要とするマテリアルに使用できます。SCREEN_TEXTURE テクスチャは、 と同じクリアモードを使用します。これらのテクスチャのサイズは、 のピクセルサイズに一致します。sampler2D 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_LIGHTPOINT_LIGHTSPOT_LIGHTAMBIENT_LIGHTSPECULAR_LIGHT などの関数の詳細については、CustomMaterial のドキュメントを参照してください。

シェーディングされていないカスタムマテリアル

CustomMaterial unshaded カスタムマテリアルです。 プロパティはデフォルトの .Shaded 値のままです。shaded shadingMode CustomMaterial

このプロパティをCustomMaterial.Unshaded に切り替えるとどうなるでしょうか?

まず、BASE_COLOREMISSIVE_COLORMETALNESS などのキーワードは、もはや望ましい効果を持ちません。これは、名前が示すように、シェーディングされていないマテリアルは、標準的なシェーディングコードの多くを自動的に修正しないため、シーン内のライト、イメージベースのライティング、シャドウ、アンビエントオクルージョンを無視するからです。むしろ、シェーディングされていないマテリアルは、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しかし、フラグメントシェーダには、もはや同様の便利さはありません。NORMALUV0VAR_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 も他のライトもありませんが、カスタムマテリアルの球体が期待通りに表示されていることに注目してください。

注意: 頂点シェーダースニペットだけを持ち、フラグメントシェーダープロパティを指定していないシェーディングされていないマテリアルは、まだ機能しますが、結果はシェーディングモードがシェーディングに設定されているかのようになります。したがって、頂点シェーダのみを持つマテリアルに対してshadingModeを切り替えることはほとんど意味がありません。

エフェクトのプログラム可能性

ポスト処理エフェクトは、1 つまたは複数のフラグメントシェーダをView3D の結果に適用します。これらのフラグメントシェーダからの出力は、元のレンダリング結果の代わりに表示されます。これはQt QuickShaderEffect およびShaderEffectSource と概念的に非常に似ています。

注: ポスト処理エフェクトは、View3DrenderModeView3D.Offscreen に設定されている場合にのみ使用できます。

カスタム頂点シェーダースニペットもエフェクトに指定できますが、有用性は 限定的であるため、比較的まれにしか使用されないと思われます。ポストプロセッシングエフェクトの頂点入力は四角形(2つの三角形または三角形ストリップ)であり、その頂点を変換したり変位させたりしても役に立たないことがよくあります。しかし、VARYING キーワードを使用してデータを計算し、フラグメントシェーダに渡すために、頂点シェーダを持つことは意味があります。通常通り、フラグメントシェーダは現在のフラグメント座標に基づいて補間された値を受け取ります。

Effect に関連するシェーダスニペットの構文は、シェーディングされていないCustomMaterial のシェーダと同じです。組み込みの特殊キーワードに関しては、VARYINGMAINFRAGCOLOR (フラグメントシェーダのみ)、POSITION (バーテックスシェーダのみ)、VERTEX (バーテックスシェーダのみ)、MODELVIEWPROJECTION_MATRIX は、CustomMaterial と同じ働きをします。

Effect フラグメントシェーダの最も重要な特殊キーワードは次のとおりです:

名前タイプ説明
入力sampler2D または sampler2DArray入力テクスチャのサンプラー。エフェクトは通常、INPUT_UV を使用してサンプリングします。
INPUT_UVvec2サンプリング用のUV座標INPUT
INPUT_SIZEvec2ピクセル単位のINPUT テクスチャのサイズ。これは textureSize() を呼び出す便利な代替手段です。
OUTPUT_SIZEvec2ピクセル単位の出力テクスチャのサイズ。多くの場合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 を持ち、したがって独自のポストプロセッシングエフェクトチェーンを持ちます。この例では、ウィンドウ全体を覆う1つのView3D

main.qmlの変更エフェクト.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 プロパティをユニフォームとして公開することは、カスタムマテリアルと同じようにエ フェクトでも機能します。このシェーダは、エフェクトのフラグメントシェーダを書くときによくある行から始まります。INPUT を UV 座標INPUT_UV でサンプリングします。その後、必要な計算を行い、最終的なフラグメントカラーをFRAGCOLOR に割り当てます。

この例で設定されている多くのプロパティは複数形です(エフェクト、パス、シェーダ)。リスト[ ] 構文は、単一の要素のみを持つ場合は省略できますが、これらのプロパティはすべてリストであり、複数の要素を保持することができます。これはなぜでしょうか?

  • effects View3D 、複数のエフェクトを連結することができるからです。エフェクトはリストに追加された順に適用されます。これは、 に2つ以上のエフェクトを一緒に簡単に適用することができ、 で アイテムを入れ子にすることで実現できることと似ています。次のエフェクトの テクスチャは、常に前のエフェクトの出力を含むテクスチャです。最後のエフェクトの出力は、 .View3D Qt Quick ShaderEffect INPUT View3D
  • passes ShaderEffect と異なり、Effect はマルチパスをビルトインでサポートしているためです。マルチパスエフェクトは、 で複数の独立したエフェクトを連結するよりも強力です。パスは一時的な中間テクスチャに出力でき、そのテクスチャはエフェクトの元の入力テクスチャに加えて、後続のパスの入力として使用できます。これにより、最終的なフラグメントカラーを得るために、複数のテクスチャを計算、レンダリング、ブレンドする複雑なエフェクトを作成できます。この高度なユースケースはここでは取り上げません。詳しくは のドキュメントページを参照してください。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;
}

さて、意外な質問かもしれませんが、なぜこれは悪い例なのでしょうか?

より正確には、これは悪い例ではなく、むしろ避けることがしばしば有益となるパターンを示している。

このようにエフェクトを連鎖させることは便利ですが、パフォーマンスへの影響に留意することが重要です。2つのレンダーパス(1つは調整された赤のカラーチャンネルでテクスチャを生成し、もう1つは歪みを計算する)を行うことは、1つで十分なのにかなり無駄です。フラグメントシェーダのスニペットを組み合わせれば、同じ結果を1つのエフェクトで達成できたでしょう。

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

プロシージャ的にメッシュとテクスチャ画像データを生成するのは、どちらも似たような手順です:

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

カスタム頂点データ

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

これは、OpenGLやVulkanなどのグラフィックスAPIを直接使用したことがある人なら、なじみがあるように思えるかもしれません。なぜなら、これらのAPIで頂点入力を指定する方法は、.mesh ファイルやQQuick3DGeometry インスタンスが定義するものと緩やかに対応しているからです。

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

QtQuick3D.Helpers モジュールにはGridGeometry タイプがあります。これにより、QQuick3DGeometry のカスタムサブクラスを実装することなく、線プリミティブでシーンのグリッドをレンダリングできます。

他の一般的な使用例として、点のレンダリングがあります。各頂点に対して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 の助けを借りて、カメラの角度を変えて見ています):

注意: 点のサイズや線の幅が 1 以外だと、グラフィックス API によっては実行時にサポートされないことがあります。これは Qt がコントロールできることではありません。そのため、点描画や線描画に頼るのではなく、別の技法を実装する必要が出てきます。

カスタムテクスチャデータ

テクスチャの場合、提供する必要があるデータは、構造的にはもっと単純です:それは生のピクセルデータで、ピクセルあたりのバイト数はテクスチャ形式によって異なります。たとえば、RGBA テクスチャはピクセルあたり4バイトを期待しますが、RGBA16F はピクセルあたり4ハーフフロートです。これはQImage が内部的に保存するものと似ています。ただし、Qt Quick 3Dテクスチャは、QImage で表現できないデータ形式を持つことがあります。たとえば、浮動小数点HDRテクスチャや圧縮テクスチャなどです。したがって、QQuick3DTextureData のデータは常に生のバイト列として提供されます。これは、OpenGLやVulkanのようなグラフィックスAPIを直接扱ったことがある人であれば、馴染みがあるように思えるかもしれません。

詳細については、QQuick3DGeometryQQuick3DTextureData のドキュメントページを参照してください。

CustomMaterial,Effect,QQuick3DGeometry,QQuick3DTextureData,Qt Quick 3D - Custom Effect Example,Qt Quick 3D - Custom Shaders Example,Qt Quick 3D - Custom Materials Example,Qt Quick 3D - Custom Geometry Example, およびQt Quick 3D - Procedural Texture Exampleも参照して ください。

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