Qt 3D Studio ランタイムの改善計画

この記事は The Qt BlogTowards an Improved Qt 3D Studio Runtime を翻訳したものです。
執筆: Laszlo Agocs, 2017年12月11日

Qt 3D Studio 1.0 が無事リリースされたため、今後の開発計画についてお知らせしたいと思います。リリースのアナウンスで概要が示されたとおり、Qt 3D 上で描画やシーンの実行を行うランタイムへの移行作業が進行中です。というわけで、この記事では具体的な内容を紹介したいと思います。

概要

シーンとレイヤー

Qt 3D Studio は 3D のシーンを簡単に開発できるデザインツールで、主に 3D のユーザーインターフェースをターゲットにしています。3D モデルやテクスチャマップのようなアセットがインポートされると、デザイナーはシーンを生成し、 3D モデルを配置し、変形し、マテリアルを指定し、キーフレームベースのアニメーションを対象となるモデルやマテリアルやレイヤーのプロパティに設定します。レイヤーのコンセプトは Photoshop のようなツールに馴染みがある人には自然なもので、個々のレイヤーにはそれぞれ 3D のシーンがカメラとともに定義されます。そしてレイヤーの位置やサイズ、合成のモードなどに応じてすべてのレイヤーを合成されたものが描画され、最終的な出力となります。

レイヤーのレベルではマルチサンプリングやスーパーサンプリング、プログレッシブやテンポラルアンチエイリアシングといったいくつかのアンチエイリアスの種類が利用可能です。詳細は ドキュメント
を参照してください。

スライド

レイヤーを補完する仕組みとしてスライドがあります。スライドは、例えば「状態」のようなもので、Powerpoint のようなプレゼンテーションツールではありません。アクティブ(可視)なオブジェクトを定義したり、シーンによる様々なオブジェクトのプロパティの変更を定義したり、スライドがアクティブになった場合のアニメーションを設定したりすることができます。「マスタースライド」という特殊なスライドでは、すべてのスライドに共通なオブジェクトやアニメーションを定義する事が可能です。

マテリアル

ピクセルベースのライトや Directional Light、Point Light、Area Light、シャドーマッピング、スクリーンスペースアンビエントオクルージョン(SSAO)、画像によるライティング など数多くの機能を提供しているデフォルトのマテリアルでは不十分な場合にはカスタムのマテリアルを摘要することが可能です。カスタム(フラグメント)シェーダーとその入力となるプロパティにセットする機能を提供します。ビルトインのマテリアルと同様、これらのプロパティは編集可能でアニメーションにも対応します。一般的なカスタムマテリアルは1つのシェーダーで構成されますが、複数のシェーダーを利用することも可能で、複数のパスを定義し、直前のパスの結果に対しての処理を順番に実行することも可能です。

エフェクト

レイヤーに配置されたコンテンツに対してエフェクトをかける際は、ポストプロセスエフェクトが利用可能です。これはカスタムマテリアルと似ていますが、レイヤーの 3D の描画結果が入力になります。コンセプト的には Qt QuickShaderEffect エレメントに対応しますが、それ以上の効果を生み出すことが可能です。

サブプレゼンテーション

1つのプレゼンテーション(.uip ファイル)は(複数のレイヤーを含めることができるため、複数の 3D のシーンの2次元的な重ね合わせのようですが)1つのシーンを表していますが、複数のプレゼンテーションをロードし平行して実行することが可能です。この場合、一つのプレゼンテーションが「メイン」として機能し、これはスクリーンに描画されます。その他のものは「サブプレゼンテーション」として、まずオフスクリーンに描画され、メインのシーンのマテリアルのテクスチャマップとして利用されます。また、メインのプレゼンテーションのレイヤーのソースとして利用することも可能です。

Qt 3D Studio はこの仕組みを利用して Qt Quick と連携して動くことが可能になります。これは QQuickRenderControlを通して行われます。これにより、Qt Quick のシーンを Qt 3D Studio のシーン内に表示することが可能になります。

以下の一覧はとても長いですがすべてをカバーはできていません。詳細は ドキュメント をご覧ください。

Qt 3D Studio in actionQt 3D Studio の動作イメージ

このスクリーンショットはこれまで紹介したいくつもの機能が含まれています。

  • 左側はスライド管理部になります
  • <li右側は基本オブジェクト(ドラッグでシーンに追加可能)の表示と、プレゼンテーションと、(ドラッグ&ドロップで 3D モデルやテクスチャマップをインポートでき、ドラッグでシーンに追加可能な)アセットブラウザになります

  • 下部は(このサンプルでは2つのレイヤーを含み、それぞれにカメラとライトとモデルが含まれる)シーンブラウザと、キーフレームの定義や編集用のタイムラインになります
  • 右下は、現在選択されているモデルやマテリアルなどのプロパティの一覧になります

ランタイムとは?

ランタイムはメインのエディタアプリケーションを補完するコンポーネントで、エディタで作られたプレゼンテーションの描画と実行のためのC++ と OpenGL によるエンジンになり、Qt 3D Studio と共に提供されているビューアーアプリケーションや独自の Qt アプリケーションで使われるものになります。Qt 3D Studio のシーンを Qt QuickQWidget、QWindow ベースのアプリケーションに埋め込むための API を提供し、動画の生成用のようなオフスクリーンへの描画も提供します。QML や C++ 向けに描画の API を提供しており、実行中のシーン内のオブジェクトのプロパティの変更や、スライドやアニメーションの制御が可能になっています。詳細はドキュメントをご覧ください。

Qt 3D Studio Viewer exampleQt 3D Studio Viewer アプリでのシーンの再生

Qt のアプリケーション開発者にとって必要な API に関しては、2つのサンプルアプリのソースコードを見てみることにしましょう。一つ目はとても直感的な QWindow ベースのアプリケーション(example/studio3d/surfaceviewer/ にあるもの)です。

#include <QtStudio3D/Q3DSSurfaceViewer>
#include <QtStudio3D/Q3DSViewerSettings>
#include <QtStudio3D/Q3DSPresentation>
#include <QtStudio3D/Q3DSSceneElement>
#include <QtStudio3D/Q3DSElement>
...
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);

QWindow window;

QSize size(1200, 800);
window.resize(size);
window.setSurfaceType(QSurface::OpenGLSurface);
window.setTitle(QStringLiteral("Qt 3D Studio surface viewer example"));
window.create();

QOpenGLContext context;
context.setFormat(window.format());
context.create();

Q3DSSurfaceViewer viewer;
viewer.presentation()->setSource(QUrl(QStringLiteral("qrc:/presentation/circling_cube.uip")));
viewer.setUpdateInterval(0);
viewer.settings()->setScaleMode(Q3DSViewerSettings::ScaleModeFill);
viewer.settings()->setShowRenderStats(true);

Q3DSSceneElement sceneElement(viewer.presentation(), QStringLiteral("Scene"));
Q3DSElement counterElement(viewer.presentation(), QStringLiteral("Scene.Layer.Loopcounter"));

viewer.initialize(&window, &context);

window.show();

int n = 0;
QString loopCounter = QStringLiteral("Loop %1");
QObject::connect(&sceneElement, &Q3DSSceneElement::currentSlideIndexChanged, [&]() {
if (sceneElement.currentSlideIndex() == 1)
n++;
counterElement.setAttribute(QStringLiteral("textstring"), loopCounter.arg(n));
});

return app.exec();
}

実際は Qt Quick の Studio3Dエレメントを使う方が多いでしょう。これの実装は QQuickFramebufferObjectになっています。(以下のコードは examples/qtudio3d/qmldynamickeyframes/ のものです)

import QtQuick 2.7
import QtStudio3D 1.0

Item {
...

Studio3D {
id: studio3D
anchors.fill: parent

// ViewerSettings item is used to specify presentation independent viewer settings.
ViewerSettings {
scaleMode: ViewerSettings.ScaleModeFill
showRenderStats: false
}

// Presentation item is used to control the presentation.
Presentation {
source: "qrc:/presentation/dyn_key.uip"

// SceneElement item is used to listen for slide changes of a scene in the presentation.
// You can also change the slides via its properties.
SceneElement {
id: scene
elementPath: "Scene"
onCurrentSlideIndexChanged: {
console.log("Current slide : " + currentSlideIndex + " ("
+ currentSlideName + ")");
}
onPreviousSlideIndexChanged: {
console.log("Previous slide: " + previousSlideIndex + " ("
+ previousSlideName + ")");
}
}

// Element item is used to change element attributes
Element {
id: materialElement
elementPath: "Scene.Layer.Sphere.Material"
}

property int desiredSlideIndex: 1
property int colorIndex: 0
property var colorArray: [
[1.0, 1.0, 1.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 0.0, 1.0],
[1.0, 1.0, 0.0]
]

function nextSlide() {
// Separate desiredSlideIndex variable is used to keep track of the desired slide,
// because SceneElement's currentSlideIndex property works asynchronously.
// This way the button click always changes the slide even if you click
// it twice during the same frame.
desiredSlideIndex = desiredSlideIndex != 3 ? desiredSlideIndex + 1 : 1;
scene.currentSlideIndex = desiredSlideIndex
slideButtonText.text = "Change Slide (" + desiredSlideIndex + ")"
}

function resetTime() {
scene.goToTime(0);
}

function changeColor() {
colorIndex = colorIndex >= colorArray.length - 1 ? colorIndex = 0 : colorIndex + 1;
materialElement.setAttribute("diffuse.r", colorArray[colorIndex][0]);
materialElement.setAttribute("diffuse.g", colorArray[colorIndex][1]);
materialElement.setAttribute("diffuse.b", colorArray[colorIndex][2]);
changeColorButton.color = Qt.rgba(colorArray[colorIndex][0],
colorArray[colorIndex][1],
colorArray[colorIndex][2], 1.0);
}
}
onRunningChanged: console.log("Presentation running")
}
...

(誰かに質問される前に:Qt の UI 技術の出力結果を別の Qt の UI 技術に埋め込めることで、様々な世界が広がります。しかし、Qt Quick のシーンを Qt 3D Studio のシーンに埋め込んで、それを Qt Quick に埋め込み、さらに Qt Quick の一部である Qt 3D のシーンに埋め込むような使い方は一見素晴らしいようですが、やり過ぎないように注意する必要があります。)

もちろん、エディタアプリケーションもシーンを表示し、編集し、制御する必要があります。現時点(1.0)ではすべてが統合されているわけではなく、エディタアプリとビューアーアプリと独自アプリでシーンの描画のパスが異なっています。長期的には統合的なアプローチを目指していますが、当面は一般的な Qt アプリケーションの視点でランタイムの改善にフォーカスをしています。

Qt 3D Studio 2.0 に向けて

C++ と OpenGL で十分でしょ?

1.0 のラインタイムは既に 様々なプラットフォームで動作可能 で、Qt のインテグレーションも既に紹介したとおりです。しかし、このランタイムは NVIDIA によるコードがベースとなっていて、ごく一部のみが Qt 化されていて、OpenGL に強く依存しているため、まだまだ改善の余地があります。ラッキーなことに、バージョン 1.0 で作られてプレゼンテーションの互換性を完全に保ったまま先に進める、とても良い方法が Qt にはあります。

Qt 3D 2.0(Qt 4 時代に作られた Qt 3D 1.0 と誤解しないように)は KDAB のエンジニアにより Qt 5 に追加された機能です。概要は こちらの記事 をご覧ください。Qt 3D は、必要な部品を提供するだけではなく、 Entity-Component システムやフレームグラフというコンセプトを導入したことにより、マルチパスの 3D シーンを、データドリブンの手法で描画するための素晴らしいツールとして利用することが可能になっています。Qt 3D は高度に抽象化を実現しているため、OpenGL 以外のグラフィックスの API が主役になるような未来に柔軟に対応していく際には非常に役に立ちます。

このため、今年の4月に「Dragon3」という内部のコードネームで .uip のプレゼンテーションをロードし Qt 3D 上で実行するための試作を開始しました。新しいソースコードはいつもの Qt のスタイルと規約で記述されているため、よりよく、メンテナンスがしやすく、Qt のエコシステムで確実に成長していけると思います。このプロトは近頃は Qt 3D Studio Runtime 2.0 と呼ばれるようになっていて、Qt 3D Studio 2.0 と一緒に今年の5月頃にリリースされる予定です。Qt 3D Studio 自身 と同様ソースコードはすでに 公開されています が、絶賛開発中であることをご理解ください。

もうしばらく待つ方がいいですか?

その必要はありません。すでにお伝えしたとおり互換性は確保します。みなさんに使い始めていただくために 1.0 をリリースいたしました。また、開発中のコードで遊んでみたい方は qt3d-runtime リポジトリ(と対応する qtbase と qt3d の dev ブランチ(現在の 5.11))をチェックアウトしてください。
続きは別のブログ記事を書く予定です。それまで待てない方や詳細が気になる方は、私が今年の Qt World Summit で発表した際の資料 を是非ご覧ください。
以上です。Happy 3D Hacking!


Blog Topics:

Comments