DirectXチュートリアル(第I部): 最初の三角形の描画
目次
はじめに
DirectXを使用して原始的な三角形を描くためだけに、非常に多くのコードを記述し、巨大なC++構造を設定する必要があることがわかったとき、思い浮かぶのは「入門儀礼」です。テクスチャ、変換マトリックス、シャドウなど、より複雑なものについては言うまでもありません。幸いなことに、MetaQuotesは、最も必要な機能だけを残してルーチン全体を隠すことによって、これに対処しました。ただし、これには副作用があります。DirectXの初心者は、全体像がわからず、すべてが発生している理由と方法を理解できません。MQLで記述すべきコードはまだたくさんあります。
内部がどうなっているかを理解できなければ、DirectXは理解しにくく、なぜこれほど難しくてまぎらわしいのか。もっと単純にできないのかとの疑問がわきます。そして、これは最初の段階にすぎません。後にはHLSLシェーダー言語とビデオカードプログラミングの奇妙さも学ぶことになります。これらすべての混乱を避けるために、あまり詳しくでなくていいので、DirectXの内部構造を考察することをお勧めします。次に、画面に三角形を表示する小さなスクリプトをMQLで記述します。
DirectX API
DirectXの歴史
DirectXは、Microsoftプラットフォームでマルチメディアとビデオを操作するためのAPI(アプリケーションプログラミングインターフェイス)のセットです。これは主にゲームを作成するために開発されましたが、時間の経過とともに開発者がエンジニアリングおよび数学ソフトウェアで使用し始めました。DirectXを使用すると、低レベルの機能にアクセスしなくても、グラフィック、サウンド、入力、ネットワークを操作できます。APIは、クロスプラットフォームOpenGLの代替として登場しました。Windows 95を作成した際、Microsoftは大幅な変更を実装しました。これにより、環境でのアプリケーションやゲームの開発がより困難になり、このオペレーティングシステムの人気に影響を与える可能性がありました。DirectXは、より多くのプログラマーがWindows用のゲームを開発できるようにするためのソリューションとして作成されました。DirectXの開発は、Craig Eisler、Alex St. John、EricEngstromによって始められました。
- 1995年9月: 最初のリリース。かなり原始的なバージョンで、WindowsAPIへのアドオンとして実行されました。あまり注目されませんでした。開発者は主にDOSを使用していましたが、新しいOSではシステム要件が高くなりました。また、OpenGLはその時までにすでに存在していました。MicrosoftがDirectXを引き続きサポートするかどうかは明確ではありませんでした。
- 1996年6月: バージョン2リリース。
- 1996年9月: バージョン3。
- 1997年8月: Microsoftはバージョン4の代わりにバージョン5をリリースしました。このバージョンでコードを書くのが簡単になり、プログラマーはそれに注意を向けるようになりました。
- 1998年8月: バージョン6。作業はさらに簡素化されました。
- 1999年9月: バージョン7。ビデオメモリで頂点バッファを作成することが可能になりました(OpenGLに比べた大きなメリット)。
- 2000年11月: バージョン8。重要な瞬間です。これまで業界に追いつくことを試みていたDirectXは、バージョン8で業界を追い抜きました。Microsoftはグラフィックカードメーカーとの協力を開始しました。頂点シェーダーとピクセルシェーダーが登場しました。ワークステーションを必要とするOpenGLとは異なり、開発にはPCのみが必要でした。
- 2002年12月: バージョン9。DirectXは業界標準になりました。HLSLシェーダー言語が登場しました。おそらく、これはDirectXの最も寿命の長いバージョンでした。ソケット775のように...
- 2006年11月:バージョン10。バージョン9とは異なって、一般的でないVistaオペレーティングシステムにバインドされていました。これらの要因は、バージョン10の成功に悪影響を及ぼしました。Microsoftはジオメトリシェーダーを追加しました。
- 2009年10月:バージョン11。テッセレーションとコンピュートシェーダーが追加され、マルチコアプロセッサでの作業が改善されました。
- 2015年7月:バージョン12。低レベルAPI。このバージョンでは、マルチコアプロセッサとの互換性がさらに向上し、さまざまなベンダーの複数のビデオカードのリソースを組み合わせる機能、レイトレースを提供しました。
Direct3D
Direct3Dは、より大きなDirectX APIの多数あるコンポーネントの1つです。グラフィックスを担当し、アプリケーションとグラフィックスカードドライバーの間の仲介を努めます。Direct3Dは、COM(コンポーネントオブジェクトモデル)に基づいています。COMは1993年にMicrosoftによって導入されたアプリケーションバイナリインターフェイス(ABI)標準で、さまざまなプログラミング言語でプロセス間通信(IPC)のオブジェクトを作成するために使用されます。COMは、作成環境の外部で使用できるオブジェクトを実装するための言語に依存しない方法を提供することを目的としたソリューションとして登場しました。COMでは、オブジェクトは実装とは別の明確に定義されたインターフェイスを備えているため、内部の実装を知らなくてもオブジェクトを再利用できます。COMオブジェクトでは、参照カウントを使用して独自の作成と破棄を行います。
インターフェイス
デバイス
Direct3Dのすべてはデバイスで始まります。これは、リソース(バッファ、テクスチャ、シェーダー、状態オブジェクト)およびグラフィックアダプターの機能の列挙を作成するために使用されます。デバイスは、ユーザーのシステムにある仮想アダプターです。アダプターは、実際のビデオカードまたはそのソフトウェアエミュレーションのいずれかです。ハードウェアデバイスは、最高のパフォーマンスを提供するため、最も頻繁に使用されます。デバイスは、これらすべてのアダプターに統合されたインターフェイスを提供し、それらを使用してグラフィックを1つ以上の出力にレンダリングします。
デバイス
デバイスの文脈
デバイスコンテキストは、レンダリングに関連するすべての責任を負います。これには、パイプラインの構成とレンダリング用のコマンドの作成が含まれます。デバイスコンテキストは、DirectXのバージョン11で登場しました。それまでレンダリングははデバイスによって実装されていました。コンテキストには、即時コンテキストと遅延コンテキストの2つのタイプがあります。
即時コンテキストは、ビデオカード上のデータへのアクセスと、デバイスでコマンドリストをすぐに実行する機能を備えています。各デバイスには即時コンテキストが1つだけあって、一度にアクセスできるスレッドは1つだけです。複数のスレッドへのアクセスを有効にするには、同期を使用する必要があります。
遅延コンテキストではコマンドリストにコマンドを追加して、後で即時コンテキストで実行します。したがって、すべてのコマンドは最終的に即時コンテキストを通過します。遅延コンテキストにはある程度のオーバーヘッドが伴うため、遅延コンテキストを使用するメリットがあるのは、リソースを大量に消費するタスクを並列化する場合にのみです。複数の遅延コンテキストを作成して別々のスレッドからそれぞれにアクセスできます。ただし、複数のスレッドから同じ遅延コンテキストにアクセスするには、即時コンテキストの場合と同様、同期が必要です。
スワップチェーン
スワップチェーンは、1つ以上のバックバッファを作成するように設計されています。これらのバッファは、画面に表示されるまでレンダリングされた画像を保存します。フロントバッファとバックバッファは次のように動作します。フロントバッファは、画面に表示されるものです。バックバッファは、レンダリングされるイメージです。次に、バッファが交換されます。フロントバッファがバックになり、バックバッファがフロントになります。プロセス全体が何度も繰り返されますため、次の画像が「舞台裏」でレンダリングされている間、常に画像を見ることができます。
スワップチェーン
デバイス、デバイスコンテキスト、スワップチェーンは、画像をレンダリングするために必要な主要コンポーネントです。
入力レイアウト
入力レイアウトは、頂点バッファの構造についてパイプラインに通知します。目的のために必要なのは座標だけであるため、特別な構造体を使用せずに、float4型の頂点の配列を渡すことができます。float4は、4つのfloat変数で構成される構造体です。
struct float4 { float x; float y; float z; float w; };
たとえば、座標と2つの色で構成されるより複雑な頂点構造体について考えてみます。
struct Vertex
{
float4 Pos;
float4 Color0;
float4 Color1;
};
この構造体のMQLでの入力レイアウトは、次のようになります。
DXVertexLayout layout[3] = {{"POSITION", 0, DX_FORMAT_R32G32B32A32_FLOAT}, {"COLOR", 0, DX_FORMAT_R32G32B32A32_FLOAT}, {"COLOR", 1, DX_FORMAT_R32G32B32A32_FLOAT}};
layout配列の各要素は、Vertex構造体の対応する要素を記述します。
- DXVertexLayout構造体の最初の要素は、セマンティック名です。これは、頂点シェーダーの構造体の要素とVertex構造体の要素をマップするために使用されます。「POSITION」はその値が座標を担当することを意味し、「COLOR」は色に使用されます。
- 2番目の要素はセマンティックインデックスです。同じタイプの複数のパラメータ(たとえば、2つのカラー値)を渡す必要がある場合、最初のパラメータはインデックス0で渡され、2番目のパラメータはインデックス1で渡されます。
- 最後の要素は、値がVertex構造体で表される型を記述します。DX_FORMAT_R32G32B32A32_FLOATは、文字通り、各コンポーネントに対して32ビット浮動小数点値で表されるRGBAカラーであることを意味します。これは混乱を招く可能性があります。この型はVertex構造体のfloat4と同様、4つの32ビット浮動小数点値に関する情報を提供し、座標を渡すために使用できます。
プリミティブトポロジ
頂点バッファには点に関する情報が格納されますが、プリミティブ内で点が相互にどのように配置されているかはわかりません。これは、プリミティブトポロジが使用される目的です。ポイントリストは、バッファが個々の点を格納していることを意味します。ラインストリップは、ポリラインを形成する接続点としてバッファを表します。ラインリストの2つの点ごとに、1つの線が記述されます。三角形ストリップと三角形リストは、線と同様に、三角形の点の順序を設定します。
Topology
HLSL
High Level Shading Language(HLSL)は、シェーダーを作成するためのCに似た言語です。シェーダーは、グラフィックカード上で実行するように設計されたプログラムです。すべてのGPGPU言語でのプログラミングは非常に似ており、グラフィックカードの設計に関連する特定の機能があります。OpenCL、Cuda、OpenGLのいずれかの経験があれば、HLSLはすぐに理解できます。ただし、CPU用のプログラムのみを作成したことがある場合は、新しいパラダイムに切り替えるのが難しいかもしれません。多くの場合、プロセッサに従来から使用されている最適化方法は機能しません。例として、不要な計算を回避したり最適なアルゴリズムを選択したりするために「if」ステートメントを使用するのはプロセッサでは正しいことですが、GPUでは、逆に、プログラムの実行時間が長くなる可能性があります。最大値を取得するには、関連するレジスタの数を数える必要がある場合があります。グラフィックカードをプログラミングする際、高性能のための3つの主要な原則は、並列処理、スループット、占有率です。
グラフィックスパイプライン
パイプラインは、3Dシーンを2D表示表現に変換するように設計されています。パイプラインは、ビデオカードの内部構造を反映しています。次の図は、データストリームがパイプライン入力からすべてのステージを介して出力に流れる様子を示しています。楕円はHLSL言語を使用してプログラムされたステージ(シェーダー)を示し、長方形は固定ステージを示しています。それらのいくつかはオプションであり、簡単にスキップできます。
グラフィックスパイプライン
-
Input Assemblerステージは、頂点バッファとインデックスバッファからデータを受け取り、頂点シェーダー用にデータを準備します。
- Vertex Shaderステージは、頂点を使用して操作を実行します。プログラム可能なステージ。パイプラインでは必須です。
- Hull Shaderステージは、テッセレーションのレベルに責任があります。プログラム可能なステージ。オプション。
- Tessellatorステージは、より小さなプリミティブを作成します。固定ステージ。オプション。
- Domain Shaderステージは、テッセレーション後の最終的な頂点値を計算します。プログラム可能なステージ。オプション。
- Geometry Shaderステージは、プリミティブ(点、線、三角形)にさまざまな変換を適用します. プログラム可能なステージ。オプション。
- Stream Outputステージは、データをGPUメモリに転送し、そこからパイプラインに送り返すことができます。固定ステージ。オプション。
- Rasterizerステージは、スコープに含まれないものをすべて切り取り、ピクセルシェーダーのデータを準備します。固定ステージ。
-
Pixel Shaderステージはピクセル操作を実行します。プログラム可能なステージ。パイプラインでは必須です。
- Output Mergerステージが最終イメージを形成します。固定ステージ。
言及する価値のあるもう1つのシェーダーは、別のパイプラインであるCompute Shader (DirectCompute)です。このシェーダーは、OpenCLやCudaと同様に、汎用計算用に設計されています。プログラム可能なステージ。オプション。
MetaQuotesによるDirectXの実装には、DirectComputeとテッセレーションステージは含まれていません。したがって、頂点、ジオメトリ、ピクセルの3つのシェーダーしかありません。
3Dグラフィック
プリミティブ
プリミティブのレンダリングはグラフィックスAPIの主な目的です。最新のビデオカードは、多数の三角形をすばやくレンダリングできるようになっています。実際、現在のコンピューターグラフィックスの開発段階では、3Dオブジェクトを描画する最も効果的な方法は、ポリゴンからサーフェスを作成することです。サーフェスは、3つの点を指定するだけで記述できます。3Dモデリングソフトウェアは多くの場合長方形を使用しますが、グラフィックカードは引き続きポリゴンを三角形に強制します。
三角形のメッシュ
頂点
Direct3Dで三角形をレンダリングするには、3つの頂点を指定する必要があります。頂点は空間内の点の位置のように見えるかもしれませんが、Direct3Dではそれ以上のものです。頂点の位置に加えて、色、テクスチャ座標、法線を渡すことができます。一般的に、行列の変換は通常、座標を正規化するために使用されます。現時点でそれを複雑にしないために、ラスター化の段階で、X軸とY軸に沿った頂点の座標は[-1; 1]内、Z軸に沿った座標は-0から1までになければならないという事実を考慮に入れてください。
色
コンピューターグラフィックスの色には、赤、緑、青の3つの要素があります。これは、人間の目の構造的特徴に関連しています。表示ピクセルも、これらの色の3つのサブピクセルで構成されます。MQLにはColorToARGB関数があり、Webの色をARGB形式に変換して、色に加えて透明度を保存します。コンポーネントが[0;1]の範囲にある場合、色は正規化できますが、正規化されていません。たとえば、32ビットカラーのコンポーネントの値は0〜255(2 ^ 8-1)になります。最新のディスプレイのほとんどは、32ビットカラーで動作します。
MQLでのアクションのシーケンス
MQLでDirectXを使用して画像を表示するには、次の手順を実行する必要があります。
- ObjectCreateを使用してビットマップラベルまたはビットマップオブジェクトを作成します。
- ResourceCreateを使用して動的なグラフィカルリソースを作成します。
- ObjectSetStringとOBJPROP_BMPFILEパラメータを使用して、リソースをオブジェクトにリンクします。
- シェーダー用のファイルを作成します(またはシェーダーを文字列変数に保存します)。
- HLSLで頂点シェーダーとピクセルシェーダーを記述します。
- #resource"FileName.hlsl"を文字列variable_name;として使用して、シェーダーファイルを接続します。
- DXVertexLayout型の配列で頂点の形式を記述します。
- DXContextCreateを使用してコンテキストを作成します。
- DXShaderCreateとDX_SHADER_VERTEXパラメータを使用して、頂点シェーダーを作成します。
- DXShaderCreateとDX_SHADER_PIXELパラメーターを使用して、頂点シェーダーを作成します。
- DXBufferCreateとDX_BUFFER_VERTEXパラメーターを使用して頂点バッファを作成します。
- 必要に応じて、DXBufferCreateとDX_BUFFER_INDEXパラメータを使用して、インデックスバッファを作成します。
- DXShaderSetLayoutを使用して頂点形式を渡します。
- DXPrimiveTopologySetを使用してプリミティブのトポロジを設定します。
- DXShaderSetを使用して頂点シェーダーとピクセルシェーダーをバインドします。
- DXBufferSetを使用して、頂点(および存在する場合はインデックス)バッファをバインドします。
- DXContextClearDepthを使用してZバッファをクリアします。
- 必要に応じて、DXContextClearColorsを使用してカラーバッファをクリアします。
- DXDraw(またはインデックスバッファが指定されている場合はDXDrawIndexed)を使用して、レンダリングコマンドを送信します。
- DXContextGetColorsを使用して結果をグラフィックリソースに渡します。
- ResourceCreateを使用してグラフィックリソースを更新します。
- ChartRedrawを使用して忘れずにチャートを更新します。
- DXReleaseを使用して使用後にクリーンアップします。
- ResourceFreeを使用してグラフィックリソースを削除します。
- ObjectDeleteを使用してグラフィックオブジェクトを削除します。
まだついてきていますか。実際、すべてが見た目よりも簡単なことが後でわかります。
実践
クラスの概要
DirectXを使用するプロセス全体は、キャンバスの作成、デバイスの初期化、頂点シェーダーとピクセルシェーダーの作成、結果の画像の画面への表示、リソースの解放など、いくつかのステージに分けることができます。クラスは次のようになります。
class DXTutorial { private: int m_width; int m_height; uint m_image[]; string m_canvas; string m_resource; int m_dx_context; int m_dx_vertex_shader; int m_dx_pixel_shader; int m_dx_buffer; bool InitCanvas(); bool InitDevice(float4 &vertex[]); void Deinit(); public: void DXTutorial() { m_dx_context = 0; m_dx_vertex_shader = 0; m_dx_pixel_shader = 0; m_dx_buffer = 0; } void ~DXTutorial() { Deinit(); } bool Init(float4 &vertex[], int width, int height); bool Draw(); };
Privateメンバー
- m_widthとm_height - キャンバスの幅と高さ。ビットマップラベルオブジェクト、動的グラフィックリソース、グラフィックコンテキストを作成するときに使用されます。値は初期化中に設定されますが、手動で設定することもできます。
- m_image - グラフィックリソースを作成するときに使用される配列。DirectX操作の結果が渡されます。
- m_canvas - グラフィカルオブジェクトの名前、m_resource - グラフィックリソースの名前。初期化および非初期化中に使用されます。
- m_dx_context - 最も重要なグラフィックコンテキストハンドル。すべてのDirectX操作に参加します。グラフィックコンテキストが作成されるときに初期化されます。
- m_dx_vertex_shader - 頂点シェーダーのハンドル。頂点マークアップの設定、グラフィックスコンテキストへのバインド、初期化解除の際に使用されます。頂点シェーダーのコンパイル時に初期化されます。
- m_dx_pixel_shader - ピクセルシェーダーハンドル。グラフィックスコンテキストにバインドするとき、および初期化解除時に使用されます。ピクセルシェーダーのコンパイル時に初期化されます。
- m_dx_buffer - 頂点バッファハンドル。グラフィックスコンテキストにバインドするとき、および初期化解除時に使用されます。頂点バッファが作成されるときに初期化されます。
初期化および非初期化のメソッド
- InitCanvas() - 画像を表示するためのキャンバスを作成します。ビットマップラベルオブジェクトと動的グラフィックリソースが使用されます。背景は黒で塗りつぶされています。操作の進行状況を返します。
- InitDevice() - DirectXを初期化します。グラフィックコンテキスト、頂点シェーダーとピクセルシェーダー、頂点バッファを作成します。プリミティブの型を設定し、頂点をマークします。頂点の配列を入力として受け取ります。操作の進行状況を返します。
- Deinit() - 使用済みリソースを解放します。グラフィックスコンテキスト、頂点シェーダーとピクセルシェーダー、頂点バッファ、ビットマップラベルオブジェクト、動的グラフィックスリソースを削除します。
Publicメンバー
- DXTutorial() - コンストラクタ。DirectXハンドルを0に設定します。
- ~DXTutorial() - デストラクタ。 Deinit()メソッドを呼び出します。
- Init() - 作業の準備をします。頂点の配列とオプションの高さと幅を入力として受け取ります。 受信したデータを検証し、InitCanvas()およびInitDevice()を呼び出します。操作の進行状況を返します。
- Draw() - 画面に画像を表示します。 カラーバッファとZバッファをクリアし、画像をグラフィックリソースに出力します。操作の進行状況を返します。
頂点の配列
頂点には座標に関する情報しか含まれていないため、簡単にするために、4つのfloat変数を含む構造体を使用します。X、Y、Zは3次元空間の座標です。Wは補助定数であり、行列操作に使用され、1に等しくなければなりません。
struct float4 { float x; float y; float z; float w; };
三角形には3つの頂点が必要なので、サイズが3の配列を使用します。
float4 vertex[3] = {{-0.5f, -0.5f, 0.0f, 1.0f}, {0.0f, 0.5f, 0.0f, 1.0f}, {0.5f, -0.5f, 0.0f, 1.0f}};
初期化
頂点の配列とキャンバスサイズをオブジェクトに渡します。入力データを確認します。渡された幅または高さが1未満の場合、パラメータは500ピクセルに設定されます。頂点配列のサイズは3でなければなりません。次に、各頂点がループでチェックされます。XおよびY座標は[-1;1]の範囲内である必要があり、Zは0に等しくなければなりません。この値に強制的にリセットされます。Wは1でなければならず、強制的にリセットされます。canvasおよびDirectX初期化関数を呼び出します。
bool DXTutorial::Init(float4 &vertex[], int width = 500, int height = 500) { if(width <= 0) { m_width = 500; Print("Warning: width changed to 500"); } else { m_width = width; } if(height <= 0) { m_height = 500; Print("Warning: height changed to 500"); } else { m_height = height; } if(ArraySize(vertex) != 3) { Print("Error: 3 vertex are needed for a triangle"); return(false); } for(int i = 0; i < 3; i++) { if(vertex[i].w != 1) { vertex[i].w = 1.0f; Print("Warning: vertex.w changed to 1"); } if(vertex[i].z != 0) { vertex[i].z = 0.0f; Print("Warning: vertex.z changed to 0"); } if(fabs(vertex[i].x) > 1 || fabs(vertex[i].y) > 1) { Print("Error: vertex coordinates must be in the range [-1;1]"); return(false); } } ResetLastError(); if(!InitCanvas()) { return(false); } if(!InitDevice(vertex)) { return(false); } return(true); }
キャンバスの作成
InitCanvas()関数は、座標がピクセル単位で設定されているビットマップラベルオブジェクトを作成します。次に、動的グラフィックリソースがこのオブジェクトにバインドされ、DirectXからの画像が出力されます。
bool DXTutorial::InitCanvas() { m_canvas = "DXTutorialCanvas"; m_resource = "::DXTutorialResource"; int area = m_width * m_height; if(!ObjectCreate(0, m_canvas, OBJ_BITMAP_LABEL, 0, 0, 0)) { Print("Error: failed to create an object to draw"); return(false); } if(!ObjectSetInteger(0, m_canvas, OBJPROP_XDISTANCE, 100)) { Print("Warning: failed to move the object horizontally"); } if(!ObjectSetInteger(0, m_canvas, OBJPROP_YDISTANCE, 100)) { Print("Warning: failed to move the object vertically"); } if(ArrayResize(m_image, area) != area) { Print("Error: failed to resize the array for the graphical resource"); return(false); } if(ArrayInitialize(m_image, ColorToARGB(clrBlack)) != area) { Print("Warning: failed to initialize array for graphical resource"); } if(!ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Error: failed to create a resource to draw"); return(false); } if(!ObjectSetString(0, m_canvas, OBJPROP_BMPFILE, m_resource)) { Print("Error: failed to bind resource to object"); return(false); } return(true); }
コードをさらに詳しく考えてみましょう。
m_canvas = "DXTutorialCanvas";
グラフィカルリソースの名前「DXTutorialCanvas」を指定します。
m_resource = "::DXTutorialResource";
動的グラフィカルリソースの名前「::DXTutorialResource」を指定します。
int area = m_width * m_height;
メソッドでは、幅と高さの積が数回必要になるため、事前に計算して結果を保存します。
ObjectCreate(0, m_canvas, OBJ_BITMAP_LABEL, 0, 0, 0)
「DXTutorialCanvas」という名前のビットマップラベルオブジェクトを作成します。
ObjectSetInteger(0, m_canvas, OBJPROP_XDISTANCE, 100)
オブジェクトをチャートの左上隅から右に100ピクセル移動します。
ObjectSetInteger(0, m_canvas, OBJPROP_YDISTANCE, 100)
オブジェクトをチャートの左上隅から100ピクセル下に移動します。
ArrayResize(m_image, area)
描画する配列のサイズを変更します。
ArrayInitialize(m_image, ColorToARGB(clrBlack))
配列を黒色で塗りつぶします。配列の色はARGB形式で保存する必要があります。便宜上、標準のColorToARGB関数を使用して、色を必要な形式に変換します。
ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)
幅がm_width、高さがm_heightの「::DXTutorialResource」という名前の動的グラフィカルリソースを作成します。COLOR_FORMAT_ARGB_NORMALIZEを使用して、透明度のある色の使用法を示します。m_image配列をデータソースとして使用します。
ObjectSetString(0, m_canvas, OBJPROP_BMPFILE, m_resource)
オブジェクトとリソースを関連付けます。以前は、オブジェクトのサイズはリソースのサイズに自動的に調整されるため、指定しませんでした。
DirectXの初期化
最も興味深い部分に移りましょう。
bool DXTutorial::InitDevice(float4 &vertex[]) { DXVertexLayout layout[1] = {{"POSITION", 0, DX_FORMAT_R32G32B32A32_FLOAT }}; string shader_error = ""; m_dx_context = DXContextCreate(m_width, m_height); if(m_dx_context == INVALID_HANDLE) { Print("Error: failed to create graphics context: ", GetLastError()); return(false); } m_dx_vertex_shader = DXShaderCreate(m_dx_context, DX_SHADER_VERTEX, shader, "VShader", shader_error); if(m_dx_vertex_shader == INVALID_HANDLE) { Print("Error: failed to create vertex shader: ", GetLastError()); Print("Shader compilation error: ", shader_error); return(false); } m_dx_pixel_shader = DXShaderCreate(m_dx_context, DX_SHADER_PIXEL, shader, "PShader", shader_error); if(m_dx_pixel_shader == INVALID_HANDLE) { Print("Error: failed to create pixel shader: ", GetLastError()); Print("Shader compilation error: ", shader_error); return(false); } m_dx_buffer = DXBufferCreate(m_dx_context, DX_BUFFER_VERTEX, vertex); if(m_dx_buffer == INVALID_HANDLE) { Print("Error: failed to create vertex buffer: ", GetLastError()); return(false); } if(!DXShaderSetLayout(m_dx_vertex_shader, layout)) { Print("Error: failed to set vertex layout: ", GetLastError()); return(false); } if(!DXPrimiveTopologySet(m_dx_context, DX_PRIMITIVE_TOPOLOGY_TRIANGLELIST)) { Print("Error: failed to set primitive type: ", GetLastError()); return(false); } if(!DXShaderSet(m_dx_context, m_dx_vertex_shader)) { Print("Error, failed to set vertex shader: ", GetLastError()); return(false); } if(!DXShaderSet(m_dx_context, m_dx_pixel_shader)) { Print("Error: failed to set pixel shader: ", GetLastError()); return(false); } if(!DXBufferSet(m_dx_context, m_dx_buffer)) { Print("Error: failed to set buffer to render: ", GetLastError()); return(false); } return(true); }
コードを分析しましょう。
DXVertexLayout layout[1] = {{"POSITION", 0, DX_FORMAT_R32G32B32A32_FLOAT }};
この行は、頂点の形式を説明しています。この情報は、グラフィックカードが頂点の入力配列を正しく処理するために必要です。この場合、頂点には位置情報のみが格納されるため、配列サイズは1になります。ただし、頂点の色に関する情報を追加する場合は、別の配列セルが必要になります。「POSITION」は、情報が座標に関連していることを意味します。0はセマンティックインデックスです。1つの頂点で2つの異なる座標を渡す必要がある場合は、最初の頂点にインデックス0を使用し、2番目の頂点に1を使用できます。DX_FORMAT_R32G32B32A32_FLOAT - 情報表現形式。この場合、4つの32ビット浮動小数点数。
string shader_error = "";
この変数は、シェーダーのコンパイルエラーを格納します。
m_dx_context = DXContextCreate(m_width, m_height);
幅がm_width、高さがm_heightのグラフィックスコンテキストを作成します。ハンドルを覚えておいてください。
m_dx_vertex_shader = DXShaderCreate(m_dx_context, DX_SHADER_VERTEX, shader, "VShader", shader_error);
頂点シェーダーを作成し、ハンドルを保存します。DX_SHADER_VERTEXは、シェーダータイプ(頂点)を示します。文字列shaderは、頂点シェーダーとピクセルシェーダーのソースコードを格納しますが、それらを別々のファイルに格納し、リソースとして含めることをお勧めします。「VShader」はエントリポイントの名前(通常のプログラムでは関数「main」)です。シェーダーのコンパイルエラーが発生した場合、追加情報がshader_errorに書き込まれます。たとえば、エントリポイント「VSha」を指定すると、変数には「error X3501: 'VSha': entrypoint not found」というテキストが含まれます。
m_dx_pixel_shader = DXShaderCreate(m_dx_context, DX_SHADER_PIXEL, shader, "PShader", shader_error);
同じことがピクセルシェーダーにも関係します。ここで適切なタイプとエントリポイントを指定します。
m_dx_buffer = DXBufferCreate(m_dx_context, DX_BUFFER_VERTEX, vertex);
バッファを作成してハンドルを保存し、それが頂点バッファであることを示します。頂点の配列を渡します。
DXShaderSetLayout(m_dx_vertex_shader, layout)
頂点のレイアウトに関する情報を渡します。
DXPrimiveTopologySet(m_dx_context, DX_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
プリミティブのタイプ「三角形のリスト」を設定します。
DXShaderSet(m_dx_context, m_dx_vertex_shader)
頂点シェーダーに関する情報を渡します。
DXShaderSet(m_dx_context, m_dx_pixel_shader)
ピクセルシェーダーに関する情報を渡します。
DXBufferSet(m_dx_context, m_dx_buffer)
バッファに関する情報を渡します。
画像表示
DirectXは画像を配列に出力します。この配列に基づいてグラフィックリソースが作成されます。
bool DXTutorial::Draw() { DXVector dx_color{1.0f, 0.0f, 0.0f, 0.5f}; if(!DXContextClearColors(m_dx_context, dx_color)) { Print("Error: failed to clear the color buffer: ", GetLastError()); return(false); } if(!DXContextClearDepth(m_dx_context)) { Print("Error: failed to clear the depth buffer: ", GetLastError()); return(false); } if(!DXDraw(m_dx_context)) { Print("Error: failed to draw vertices of the vertex buffer: ", GetLastError()); return(false); } if(!DXContextGetColors(m_dx_context, m_image)) { Print("Error: unable to get image from the graphics context: ", GetLastError()); return(false); } if(!ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Error: failed to create a resource to draw"); return(false); } return(true); }
メソッドをさらに詳しく分析してみましょう。
DXVector dx_color{1.0f, 0.0f, 0.0f, 0.5f};
DXVector型のdx_color変数が作成されます。半透明の赤い色が割り当てられています。0から1までの値を持つRGBA形式。
DXContextClearColors(m_dx_context, dx_color)
バッファを色dx_colorで満たします。
DXContextClearDepth(m_dx_context)
Zバッファをクリアします。
DXDraw(m_dx_context)
レンダリングタスクをDirectXに送信します。
DXContextGetColors(m_dx_context, m_image)
結果をm_image配列に返します。
ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)
動的グラフィックリソースを更新します。
リソースのリリース
DirectXでは、リソースを手動で解放するとともに、グラフィカルオブジェクトとリソースを削除する必要があります。リソースを解放する必要があるかどうかを確認してから、DXReleaseを呼び出します。動的グラフィカルリソースはResourceFreeを介して削除されます。グラフィックオブジェクトはObjectDeleteを介して解放されます。
void DXTutorial::Deinit() { if(m_dx_pixel_shader > 0 && !DXRelease(m_dx_pixel_shader)) { Print("Error: failed to release the pixel shader handle: ", GetLastError()); } if(m_dx_vertex_shader > 0 && !DXRelease(m_dx_vertex_shader)) { Print("Error: failed to release the vertex shader handle: ", GetLastError()); } if(m_dx_buffer > 0 && !DXRelease(m_dx_buffer)) { Print("Error: failed to release the vertex buffer handle: ", GetLastError()); } if(m_dx_context > 0 && !DXRelease(m_dx_context)) { Print("Error: failed to release the graphics context handle: ", GetLastError()); } if(!ResourceFree(m_resource)) { Print("Error: failed to delete the graphics resource"); } if(!ObjectDelete(0, m_canvas)) { Print("Error: failed to delete graphical object"); } }
シェーダー
シェーダーはシェーダー文字列に保存されますが、ボリュームが大きい場合は、それらを別々の外部ファイルに入れて、リソースとして接続することをお勧めします。
string shader = "float4 VShader( float4 Pos : POSITION ) : SV_POSITION \r\n" " { \r\n" " return Pos; \r\n" " } \r\n" " \r\n" "float4 PShader( float4 Pos : SV_POSITION ) : SV_TARGET \r\n" " { \r\n" " return float4( 0.0f, 1.0f, 0.0f, 1.0f ); \r\n" " } \r\n";
シェーダーはグラフィックカード用のプログラムで、DirectXでは、Cに似たHLSL言語で記述されています。シェーダーのfloat4は、構造体とは対照的に、組み込みのデータ型です。この場合、VShaderは頂点シェーダーであり、PShaderはピクセルシェーダーです。POSITION - 入力データが座標であることを示すセマンティック。意味はDXVertexLayoutと同じです。SV_POSITION - これもセマンティックですが、出力値に使用されます。SV _プレフィックスは、これがシステム値であることを示します。SV_TARGET - セマンティック。値がテクスチャまたはピクセルバッファに書き込まれることを示します。ここで何が起こっているのでしょうか。座標は頂点シェーダーに入力され、頂点シェーダーは変更せずに出力に渡します。ピクセルシェーダー(ラスター化ステージから)は、色が緑に設定されている補間値を受け取ります。
OnStart
DXTutorialクラスのインスタンスが関数に作成されます。Init関数が呼び出され、頂点の配列が渡されます。次に、Draw関数が呼び出されます。その後、スクリプトの実行は終了します。
void OnStart() { float4 vertex[3] = {{-0.5f, -0.5f, 0.0f, 1.0f}, {0.0f, 0.5f, 0.0f, 1.0f}, {0.5f, -0.5f, 0.0f, 1.0f}}; DXTutorial dx; if(!dx.Init(vertex)) return; ChartRedraw(); Sleep(1000); if(!dx.Draw()) return; ChartRedraw(); Sleep(1000); }
終わりに
この記事では、DirectXの歴史について考察して、それが何であるか、そしてその目的を理解しようとしました。APIの内部構造についても検討しました。最新のグラフィックカードで頂点をピクセルに変換するパイプラインを見てきました。 また、この記事では、DirectXの操作に必要なアクションのリストとMQLの小さな例を提供します。最後に、最初の三角形をレンダリングしました。お疲れさまでした。しかし、DirectXを使用した本格的な操作のために学ぶべき、他にも多くの新しく興味深いことがあります。 これには、頂点、HLSLシェーダープログラミング言語、マトリックス、テクスチャ、法線、および多数の特殊効果を使用したさまざまな変換に加えて、他のデータの転送が含まれます。
参考文献とリンク
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/10425
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索