English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
preview
MetaTrader5でDirectXを使用して3Dグラフィックスを作成する方法

MetaTrader5でDirectXを使用して3Dグラフィックスを作成する方法

MetaTrader 5統合 | 29 4月 2020, 09:18
1 119 0
MetaQuotes
MetaQuotes

3 次元のコンピュータ グラフィックスは、レンジディスプレイ上の 3 次元オブジェクトを提供します。 このようなオブジェクトは、ビューアーの位置だけでなく、時間の経過とともに変化する可能性があります。 したがって、2次元の画像は、画像の深さの錯覚を作成するためにも変更する必要があります、すなわち、回転、ズーム、照明の変化などをサポートする必要があります。 MQL5では、DirectX関数を使用してMetaTrader5ターミナルでコンピュータグラフィックスを直接作成および管理することができます。 この機能を動作させるには、ビデオ カードがDX 11およびシェーダー モデル 5.0 をサポートしている必要があります。


オブジェクトモデリング

平面に 3 次元オブジェクトを描画するには、このオブジェクトのモデルを X、Y、Z 座標で最初に取得する必要があります。 オブジェクトサーフェス上の各点を、その座標を指定して記述する必要があることを意味します。 理想的には、スケーリング中に画質を維持するために、オブジェクトサーフェス上の無限の数のポイントを記述する必要があります。 実際には、ポリゴンからなるメッシュを使用して 3D モデルを記述します。 ポリゴン数が多いメッシュの詳細は、より現実的なモデルになります。 ただし、このようなモデルを計算し、3D グラフィックスをレンダリングするには、より多くのコンピューター リソースが必要です。

ポリゴン メッシュとしてのティーポット モデル

ポリゴン メッシュのティーポット モデル。

ポリゴンを三角形に分割する方法は、初期のコンピュータグラフィックスではグラフィックカードが十分でなかったために、発案されました。 三角形を使用すると、小さなサーフェス パーツの位置を正確に説明できるだけでなく、ライトやライトの反射などの関連するパラメータを計算することもできます。 このような小さな三角形の集合体で、オブジェクトの3次元画像を作成することができます。 以下、ポリゴンと三角形は、N個の頂点を持つポリゴンよりも三角形を想像する方がはるかに簡単であるため、同義語として使用します。


三角形で構成されたキューブ。

オブジェクトの 3 次元モデルは、三角形の各頂点の座標を記述することによって作成でき、オブジェクトが移動したり、ビューワーの位置を変更したりしても、オブジェクトの各ポイントの座標をさらに計算できます。 このように、頂点、頂点を接続するエッジ、およびエッジによって形成される面を扱います。 三角形の位置がわかっている場合は、線形代数の法則を使用して面の法線を作成できます(法線はサーフェスに垂直なベクトルです)。 これより、面がどのように点灯し、そこから光がどのように反射するかを計算できます。


頂点、エッジ、面、法線を持つシンプルなオブジェクトの例。 法線は赤い矢印です。

モデル オブジェクトは、さまざまな方法で作成できます。 トポロジーは、ポリゴンが 3D メッシュをどのように形成するかを記述します。 トポロジが優れた場合、オブジェクトを記述するために最小限のポリゴン数を使用でき、オブジェクトの移動や回転がなります。

2 つのトポロジの球モデル

2 つのトポロジの球モデル。

ボリュームエフェクトは、オブジェクトポリゴン上のライトとシャドウを使用して作成されます。 したがって、3Dコンピュータグラフィックスの目的は、オブジェクトの各点の位置を計算し、ライトと影を計算し、画面上に表示することです。

図形の作成

キューブを作成するシンプルなプログラムを書きましょう。 3D graphics・ライブラリーの CCanvas3D クラスを使用します。

CCanvas3DWindow クラスは、3D ウィンドウをレンダリングし、メンバとメソッドの最小値があります。 DirectX を使用するための関数で実装された 3D グラフィックスの概念の説明を含む新しいメソッドを徐々に追加します。

//+------------------------------------------------------------------+
//| アプリケーションウィンドウ                                           |
//+------------------------------------------------------------------+
class CCanvas3DWindow
  {
protected:
   CCanvas3D         m_canvas;
   //--- キャンバスサイズ
   int               m_width;
   int               m_height;
   //--- Cube オブジェクト
   CDXBox            m_box;

public:
                     CCanvas3DWindow(void) {}
                    ~CCanvas3DWindow(void) {m_box.Shutdown();}
   //-- シーンを作成する
   virtual bool      Create(const int width,const int height){}
   //--- シーンを計算する
   void              Redraw(){}
   //--- チャートイベントを処理する
   void              OnChartChange(void) {}
  };

シーンの作成は、キャンバスの作成から始まります。 次に、投影行列に次のパラメータが設定されます。

  1. 30 度の画角(M_PI/6)から、3D シーンを見る
  2. 幅と高さの比率としてのアスペクト比
  3. near(0.1f)とfar(100.f)クリッピング平面までの距離

つまり、この 2 つの仮想壁(0.1f と 100.f)の間にあるオブジェクトだけが投影行列にレンダリングされます。 さらに、オブジェクトは水平の 30 度の画角に収まらなければなりません。 距離だけでなく、コンピュータグラフィックスのすべての座標が仮想であることに注意してください。 重要なのは距離とサイズの関係ですが、絶対値ではありません。

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
      //--- キャンバスの寸法を保存する
      m_width=width;
      m_height=height;
      //--- 3D シーンをレンダリングするキャンバスを作成する
      ResetLastError();
      if(!m_canvas.CreateBitmapLabel("3D Sample_1",0,0,m_width,m_height,COLOR_FORMAT_ARGB_NORMALIZE))
        {
         Print("Error creating canvas: ",GetLastError());
         return(false);
         }
      //--- 投影マトリックスパラメータを設定 - ビューの角度、アスペクト比、近くおよび遠くのクリップ面までの距離
      m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
      //--- キューブを作成する - それにリソースマネージャ、シーンパラメータとキューブの2つの反対側のコーナーの座標を渡します
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,5.0),DXVector3(1.0,1.0,7.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- 立方体をシーンに追加する
      m_canvas.ObjectAdd(&m_box);
      //--- シーンを再描画する
      Redraw();
      //---成功したら
      return(true);
      }

投影行列を作成したら、CDXBox クラスに基づく立方体である 3D オブジェクトの作成に進みます。 立方体を作成するには、2 つのベクトルがキューブの反対側の角を指していることを示す必要があります。 キューブの作成をデバッグ モードで見ることで、DXComputeBox() で何が起こっているかを確認できます: すべてのキューブ頂点の作成 (座標は '頂点' 配列に書き込まれます) だけでなく、キューブの端を列挙して 'indiсes' 配列に保存された三角形に分割します。 合計で、立方体には 8 つの頂点、12 個の三角形に分割された 6 つの面、および三角形の頂点を列挙する 36 個のインデックスがあります。

立方体には 8 つの頂点しかありませんが、6 つの面それぞれに法線を持つ頂点のセットを個別に指定する必要があるため、24 個のベクトルが作成されます。 法線の方向は、各面の照明の計算に影響します。 三角形の頂点がインデックスに表示される順序によって、その辺のどちらが表示されるかが決まります。 頂点とインデックスが塗りつぶされる順序は、DXUtils.mqh コードに示されています。

   for(int i=20; i<24; i++)
      vertices[i].normal=DXVector4(0.0,-1.0,0.0,0.0);

各面のテクスチャ マッピングのテクスチャ座標は、同じコードで説明されています。

//--- テクスチャ座標
   for(int i=0; i<faces; i++)
     {
      vertices[i*4+0].tcoord=DXVector2(0.0f,0.0f);
      vertices[i*4+1].tcoord=DXVector2(1.0f,0.0f);
      vertices[i*4+2].tcoord=DXVector2(1.0f,1.0f);
      vertices[i*4+3].tcoord=DXVector2(0.0f,1.0f);
      }

4 つの面ベクトルのそれぞれは、テクスチャ マッピングの 4 つの角度のうちの 1 つを設定します。 つまり、スクアッド構造が各立方体の面にマッピングされ、テクスチャがレンダリングされます。 もちろん、テクスチャが設定されている場合にのみ必要です。


シーンの計算とレンダリング

3D シーンが変更されるたびに、すべての計算を新たに実行する必要があります。 必要な計算の順序を次に示します。

  • ワールド座標で各オブジェクトの中心を計算する
  • オブジェクトの各要素の位置、すなわち各頂点の位置を計算する
  • 視聴者のピクセルの深さと表示を決定する
  • 頂点で指定されたポリゴン上の各ピクセルの位置を計算します。
  • 指定したテクスチャに従って、ポリゴン上の各ピクセルの色を設定します。
  • ライトピクセルとその反射の方向を計算する
  • 各ピクセルに拡散光を適用する
  • すべてのワールド座標をカメラ座標に変換する
  • カメラ座標を投影行列の座標に変換する
これらの操作はすべて、CCanvas3D オブジェクトの Render メソッドで実行されます。 レンダリング後、計算されたイメージは、Update メソッドを呼び出して、プロジェクション マトリックスからキャンバスに転送されます。
   //+------------------------------------------------------------------+
   //| シーンを更新                                                       |
   //+------------------------------------------------------------------+
   void              Redraw()
     {
      //--- 3D シーンを計算する
      m_canvas.Render(DX_CLEAR_COLOR|DX_CLEAR_DEPTH,ColorToARGB(clrBlack));
      //--- 現在のシーンに合わせてキャンバス上の画像を更新する
      m_canvas.Update();
      }

この例では、キューブは 1 回だけ作成され、以上変更されることはありません。 したがって、キャンバス上のフレームは、チャートのサイズ変更など、チャートに変更がある場合にのみ変更する必要があります。 この場合、キャンバスの寸法は現在のチャートの寸法に調整され、投影行列はリセットされ、キャンバス上の画像が更新されます。

   //+------------------------------------------------------------------+
   //| グラフ変更イベントの処理                                       |
   //+------------------------------------------------------------------+
   void              OnChartChange(void)
     {
      //--- 現在のグラフサイズを取得する
      int w=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
      int h=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
      //--- チャートサイズに応じてキャンバスの次元を更新
      if(w!=m_width || h!=m_height)
        {
         m_width =w;
         m_height=h;
         //--- キャンバスのサイズを変更する
         m_canvas.Resize(w,h);
         DXContextSetSize(m_canvas.DXContext(),w,h);
         //--- キャンバスサイズに応じてプロジェクション行列を更新
         m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
         //--- 3D シーンを再計算してキャンバスにレンダリングする
         Redraw();
         }
      }

Step1 Create Box.mq5"EAを起動します。 黒い背景に白い4角が表示されます。 デフォルトでは、オブジェクトの作成時に白色が設定されます。 照明はまだ設定されていません。

白い立方体と空間におけるレイアウト

白い立方体と空間におけるレイアウト

X 軸は右に向き、Y は上向き、Z は 3D シーン内に向けられます。 このような座標系は、レフトハンドと呼ばれます。

立方体の中心は、次の座標 X=0、Y=0、Z=6 の点にあります。 キューブを見る位置は、座標の中心(デフォルト値)です。 3D シーンの表示元の位置を変更する場合は、ViewPositionSet()関数を使用して、適切な座標を明示的に設定します。

プログラム操作を完了するには、"Escape" を押します。


Z 軸とビューポイントを中心としたオブジェクトの回転

シーンをアニメートするには、Z 軸を中心に立方体の回転を有効にします。 これを行うには、タイマーを追加します - そのイベントに基づいて、キューブは反時計回りに回転されます。

DXMatrixRotationZ() メソッドを使用して、指定した角度で Z 軸を中心とした回転を有効にする回転行列を作成します。 次に、パラメータとしてパラメータとして渡します。 これより、3D 空間での立方体の位置が変更されます。 Redraw() を呼び出して、キャンバス上のイメージを更新します。

   //+------------------------------------------------------------------+
   //| タイマーハンドラ                                                   |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- 回転角度を計算するための変数
      static ulong last_time=0;
      static float angle=0;
      //--- 現在の時刻を取得する
      ulong current_time=GetMicrosecondCount();
      //---デルタを計算する
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //---Z 軸を中心とする立方体の回転角度を大きくする
      angle+=deltatime;
      //--- 時刻を記録
      last_time=current_time;
      //--- Z 軸を中心とした立方体の回転角度を設定する
      DXMatrix rotation;
      DXMatrixRotationZ(rotation,angle);
      m_box.TransformMatrixSet(rotation);
      //--- 3D シーンを再計算してキャンバスにレンダリングする
      Redraw();
      }

起動後、回転する白い正方形が表示されます。

立方体は、Z 軸を反時計回りに回転します。

この例のソース コードは、ファイル"Step2 Rotation Z.mq5" にあります。 シーンを作成する際に、角度M_PI/5が指定されていますが、前の例の角度M_PI/6 より大きくなっています。 

      //--- 投影マトリックスパラメータを設定 - ビューの角度、アスペクト比、近くおよび遠くのクリップ面までの距離
      m_matrix_view_angle=(float)M_PI/5;
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- キューブを作成する - それにリソースマネージャ、シーンパラメータとキューブの2つの反対側のコーナーの座標を渡します

ただし、画面のキューブ ディメンションは視覚的に小さくなります。 投影行列を設定するときに指定したビューの角度が小さいほど、大きなフレーム部分がオブジェクトによって占められます。 望遠鏡で物体を見ることと比較することができます。:視野の角度は小さいですが、オブジェクトはより大きいです。


カメラ位置管理

CCanvas3Dクラスには、3Dの重要なシーンパラメータを設定する3つの方法があります。

パラメータはすべて組み合わせて使用するため、3D シーンでパラメータのいずれかを設定する場合は、他の 2 つのパラメータも初期化する必要があります。 少なくともシーン生成段階で行う必要があります。 フレームの上側の境界線が左右に揺れる例です。 スイングは、Create() メソッドに次の 3 つのコード行を追加することによって実装されます。

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
....       
      //--- 立方体をシーンに追加する
      m_canvas.ObjectAdd(&m_box);
      //--- シーン パラメータを設定する
      m_canvas.ViewUpDirectionSet(DXVector3(0,1,0));  //方向ベクトルを Y 軸に沿って上に設定する
      m_canvas.ViewPositionSet(DXVector3(0,0,0));     // 座標の中心から視点を設定する
      m_canvas.ViewTargetSet(DXVector3(0,0,6));       // 立方体の中心に視線点を設定する      
      //--- シーンを再描画する
      Redraw();
      //---成功したら
      return(true);
      }

OnTimer() メソッドを変更して、水平ベクトルを左右に振るようにします。

   //+------------------------------------------------------------------+
   //| タイマーハンドラ                                                   |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- 回転角度を計算するための変数
      static ulong last_time=0;
      static float max_angle=(float)M_PI/30;
      static float time=0;
      //--- 現在の時刻を取得する
      ulong current_time=GetMicrosecondCount();
      //---デルタを計算する
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //---Z 軸を中心とする立方体の回転角度を大きくする
      time+=deltatime;
      //--- 時刻を記録
      last_time=current_time;
      //--- Z 軸の周りの回転角度を設定する
      DXVector3 direction=DXVector3(0,1,0);     //上部の最初の方向
      DXMatrix rotation;                        // 回転ベクトル      
      //--- 回転行列を計算する 
      DXMatrixRotationZ(rotation,float(MathSin(time)*max_angle));
      DXVec3TransformCoord(direction,direction,rotation);
      m_canvas.ViewUpDirectionSet(direction);   // 上の新しい方向を設定する
      //--- 3D シーンを再計算してキャンバスにレンダリングする
      Redraw();
      }

この例を"Step3 ViewUpDirectionSet.mq5" として保存し、実行します。 実際には動かないが、スイング立方体のイメージが表示されます。 この効果はカメラ自体が左右に揺れたときに得られます。

トップの方向は左右にスイング

トップの方向は左右にスイング

ターゲットの座標、カメラの座標と上の方向の間に接続があることに注意してください。 したがって、カメラの位置を制御するためには、上部の方向とターゲットの座標、すなわち視線点も指定する必要があります。


オブジェクトカラー管理

コードを変更し、カメラを移動しながら、座標の中心にキューブを置きます。

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- キューブを作成する - それにリソースマネージャ、シーンパラメータとキューブの2つの反対側のコーナーの座標を渡します
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- 色を設定する 
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- 立方体をシーンに追加する
      m_canvas.ObjectAdd(&m_box);
      //--- カメラ、視線、および上部の方向の位置を設定する
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));  // 方向ベクトルを Y 軸に沿って上に設定する
      m_canvas.ViewPositionSet(DXVector3(3.0,2.0,-5.0));    // カメラを右、上、立方体の前にセットする
      m_canvas.ViewTargetSet(DXVector3(0,0,0));             // 立方体の中心に視線方向を設定する
      //--- シーンを再描画する
      Redraw();
      //---成功したら
      return(true);
      }

また、立方体を青で塗ります。 このカラーはアルファチャネルを使用した RGB カラーの形式で設定されます(アルファチャネルは最後に示されます)。値は1に正規化されています。 したがって、値1は255、0.5は127を意味します。

X 軸を中心に回転を追加し、変更を"Step4 Box Color.mq5" として保存します。

回転する立方体の右上のビュー。

回転する立方体の右上のビュー。


回転と移動

オブジェクトは、一度に 3 方向に移動および回転できます。 オブジェクトの変更はすべて、行列を使用して実装されます。 それぞれ、すなわち回転、移動および変換は、別々に計算することができます。 例を変更してみましょう:カメラビューが上からと正面からになりました。

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- カメラを座標の中心の上と前に置く
      m_canvas.ViewPositionSet(DXVector3(0.0,2.0,-5.0));
      m_canvas.ViewTargetSet(DXVector3(0.0,0.0,0.0));
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));      
      //--- キューブを作成する - それにリソースマネージャ、シーンパラメータとキューブの2つの反対側のコーナーの座標を渡します
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- 立方体の色を設定する
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- 立方体の位置と転送行列を計算する
      DXMatrix rotation,translation;
      //--- 立方体を X、Y、Z 軸に沿って順番に回転させる
      DXMatrixRotationYawPitchRoll(rotation,(float)M_PI/4,(float)M_PI/3,(float)M_PI/6);
      //-- キューブを右/下へ/内側に移動する
      DXMatrixTranslation(translation,1.0,-2.0,5.0);
      //--- 回転と転送の積として変換行列を取得する
      DXMatrix transform;
      DXMatrixMultiply(transform,rotation,translation);
      //--- 変換行列を設定する 
      m_box.TransformMatrixSet(transform);      
      //--- 立方体をシーンに追加する
      m_canvas.ObjectAdd(&m_box);    
      //--- シーンを再描画する
      Redraw();
      //---成功したら
      return(true);
      }

回転を連続して作成し、行列を転送し、変換行列を適用して、立方体をレンダリングします。 "Step5 Translation.mq5"で変更を保存し、実行します。

立方体の回転と移動

立方体の回転と移動

カメラはまだ残っており、上から少し座標の中心を指します。 立方体は3方向に回転し、右、下、内側にシーンにシフトしました。


ライティングの操作

現実的な3次元画像を得るためには、物体表面の各点の照明を計算する必要があります。 次の 3 つの照明コンポーネントの色の強度を計算するフォン シェーディング モデルを使用して行われます: アンビエント、拡散反射、およびスペキュラです。 ここでは、次のパラメータを使用します。

  • DirectionLight — 光の方向は CCanvas3D で設定されます。
  • AmbientLight — アンビエント照明の色と強度は CCanvas3D で設定されます。
  • DiffuseColor — 計算された拡散光コンポーネントは CDXMesh とその子クラスで設定されます。
  • EmissionColor — バックグラウンドライティングコンポーネントは CDXMesh とその子クラスで設定されます。
  • SpecularColor — スペキュラ コンポーネントは CDXMesh とその子クラスで設定されます。

フォン シェーディング モデル
フォン シェーディング モデル


ライトニングモデルは標準のシェーダーで実装され、モデルパラメータは CCanvas3D で設定され、オブジェクトパラメータは CDXMesh とその子クラスで設定されます。 次のように、例を変更します。

  1. 座標の中心に立方体を返します。
  2. 白に設定します。
  3. 上から下に照らす黄色の方向を追加します。
  4. 非指向性照明の青色を設定します。
      //--- ソースに黄色の色を設定し、上から下向きに
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- 周囲光の青い色を設定する 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));          
      //--- キューブを作成する - それにリソースマネージャ、シーンパラメータとキューブの2つの反対側のコーナーの座標を渡します
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
         }
      //---立方体の白色を設定する
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0)); 
      //--- 立方体に緑色のグローを追加する(放出)
      m_box.EmissionColorSet(DXColor(0.0,1.0,0.0,0.2f)); 

指向光源の位置はCanvas3Dでは設定されておらず、光が広がる方向のみが与えられます。 指向性光のソースは無限距離にあり、厳密に平行な光の流れがシーンを照らします。

m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));

ここで、光の広がりベクトルはY軸に沿ってマイナス方向、すなわち上から下向きに向いています。 さらに、指向光源のパラメータを設定する場合(LightColorSetとLightDirectionSet)、アンビエント ライトの色(AmbientColorSet)も指定する必要があります。 デフォルトでは、アンビエント ライトの色は最大強度の白色に設定されているため、すべての影は白になります。 つまり、シーン内のオブジェクトは周囲の照明から白で照明され、指向性光源のライトは白色光によって中断されます。

      //--- ソースに黄色の色を設定し、上から下向きに
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- 周囲光の青い色を設定する 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));  //指定する必要があります

以下の GIF アニメーションは、ライティングを追加したときにイメージがどのように変化するかを示します。 この例のソース コードは、ファイル"Step6 Add Light.mq5" を追加します。

黄色の光源の下に緑色の放射を持つ白い立方体、青い周囲光

黄色の光源の下に緑色の放射を持つ白い立方体、青い周囲光

上記のコードで色のメソッドをオフにして、その動作を確認してください。


アニメーション

アニメーションは、時間の経過に応じてシーン のパラメータとオブジェクトが変化していることを意味します。 使用可能なプロパティは、時間やイベントに応じて変更できます。 タイマーを 10 ミリ秒に設定します — このイベントはシーンの更新に影響します。

int OnInit()
  {
...
//--- キャンバスを作成する
   ExtAppWindow=new CCanvas3DWindow();
   if(!ExtAppWindow.Create(width,height))
      return(INIT_FAILED);
//---タイマーを設定する
   EventSetMillisecondTimer(10);
//---
   return(INIT_SUCCEEDED);
   }

適切なイベントハンドラを CCanvas3DWindow に追加します。 オブジェクトのパラメータ(回転、移動、ズームなど)と照明の方向を変更する必要があります。

   //+------------------------------------------------------------------+
   //| タイマーハンドラ                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {    
      static ulong last_time=0;
      static float time=0;       
      //--- 現在の時刻を取得する
      ulong current_time=GetMicrosecondCount();
      //---デルタを計算する
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- 経過時間値を増やす
      time+=deltatime;
      //--- 時刻を記録
      last_time=current_time;
      //--- 立方体の位置と回転行列を計算する
      DXMatrix rotation,translation,scale;
      DXMatrixRotationYawPitchRoll(rotation,time/11.0f,time/7.0f,time/5.0f);
      DXMatrixTranslation(translation,(float)sin(time/3),0.0,0.0);
      //--- 軸に沿ってキューブの圧縮/拡張を計算する
      DXMatrixScaling(scale,1.0f+0.5f*(float)sin(time/1.3f),1.0f+0.5f*(float)sin(time/1.7f),1.0f+0.5f*(float)sin(time/1.9f));
      //--- 行列を乗算して最終的な変換を得る
      DXMatrix transform;
      DXMatrixMultiply(transform,scale,rotation);
      DXMatrixMultiply(transform,transform,translation);
      //--- 変換行列を設定する
      m_box.TransformMatrixSet(transform);
      //--- Z 軸を中心とした光源の回転を計算する
      DXMatrixRotationZ(rotation,deltatime);
      DXVector3 light_direction;
      //--- 光源の現在の方向を取得する
      m_canvas.LightDirectionGet(light_direction);
      //--- 光源の新しい方向を計算し、それを設定する
      DXVec3TransformCoord(light_direction,light_direction,rotation);
      m_canvas.LightDirectionSet(light_direction);
      //--- 3D シーンを再計算し、キャンバスに描画します。
      Redraw();
      }

オブジェクトの変更は、初期キューブ状態を常に処理し、最初から回転/移動/圧縮に関連するすべての操作を適用する場合と同様に、初期値に適用されることに注意してください。 ただし、光源の方向は、現在の値からデルタ時間(deltatime)の増分によって変更されます。

動的照明を備えた回転立方体

動的に変化する光源方向を持つ回転立方体。

結果として、複雑な 3D アニメーションが作成されます。 サンプル コードは、ファイル"Step7 Animation.mq5" で使用できます。


マウスを使用してカメラの位置を制御する

3D グラフィックスの直近のアニメーション要素、ユーザーのアクションに対する反応を考えてみましょう。 この例では、マウスを使用してカメラ管理を追加します。 まず、マウス イベントをサブスクライブし、対応するハンドラを作成します。

int OnInit()
  {
...
//--- タイマーを設定する
   EventSetMillisecondTimer(10);
//--- マウス イベントの受信を有効にする:移動とボタンのクリック
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1)
//---
   return(INIT_SUCCEEDED);
   }
void OnDeinit(const int reason)
  {
//--- タイマーの削除
   EventKillTimer();
//--- マウス イベントの受信を無効にする
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,0);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,0);
//--- オブジェクトを削除する
   delete ExtAppWindow;
//--- 価格チャートを使用して通常の表示モードにチャートを返す
   ChartSetInteger(0,CHART_SHOW,true);
   }
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- チャート変更イベント
   if(id==CHARTEVENT_CHART_CHANGE)
      ExtAppWindow.OnChartChange();
//--- マウスの動きイベント
   if(id==CHARTEVENT_MOUSE_MOVE)
      ExtAppWindow.OnMouseMove((int)lparam,(int)dparam,(uint)sparam);
//--- マウス ホイール スクロール イベント
   if(id==CHARTEVENT_MOUSE_WHEEL)
      ExtAppWindow.OnMouseWheel(dparam);

CCanvas3DWindow で、マウス移動イベントハンドラを作成します。 左ボタンを押したままマウスを動かしたときに、カメラの方向角度が変わります。

   //+------------------------------------------------------------------+
   //| マウスの動きを処理する                                              |
   //+------------------------------------------------------------------+
   void              OnMouseMove(int x,int y,uint flags)
     {
      //--- マウスの左ボタン
      if((flags&1)==1)
        {
         //--- 前のマウス位置に関する情報がない
         if(m_mouse_x!=-1)
           {
            //---位置の変更時にカメラの角度を更新する
            m_camera_angles.y+=(x-m_mouse_x)/300.0f;
            m_camera_angles.x+=(y-m_mouse_y)/300.0f;
            //--- (-Pi/2,Pi2)の範囲で垂直角度を設定します
            if(m_camera_angles.x<-DX_PI*0.49f)
               m_camera_angles.x=-DX_PI*0.49f;
            if(m_camera_angles.x>DX_PI*0.49f)
               m_camera_angles.x=DX_PI*0.49f;
            //--- カメラ位置を更新する
            UpdateCameraPosition();
            }
         //--- マウスの位置を保存
         m_mouse_x=x;
         m_mouse_y=y;
         }
      else
        {
         //--- 左マウスボタンが押されていない場合、保存した位置をリセットする
         m_mouse_x=-1;
         m_mouse_y=-1;
         }
      }

カメラとシーンの中心の間の距離を変更するマウス ホイール イベント ハンドラを次に示します。

   //+------------------------------------------------------------------+
   //| マウス ホイール イベントの処理                                      |
   //+------------------------------------------------------------------+
   void              OnMouseWheel(double delta)
     {
      //--- マウススクロールでカメラと中央の距離を更新する
      m_camera_distance*=1.0-delta*0.001;
      //--- [3,50] の範囲の距離を設定します
      if(m_camera_distance>50.0)
         m_camera_distance=50.0;
      if(m_camera_distance<3.0)
         m_camera_distance=3.0;
      //--- カメラ位置を更新する
      UpdateCameraPosition();
      }

両方のハンドラは、更新されたパラメータに従ってカメラの位置を更新する UpdateCameraPosition() メソッドを呼び出します。

   //+------------------------------------------------------------------+
   //| カメラの位置を更新します。                                          |
   //+------------------------------------------------------------------+
   void              UpdateCameraPosition(void)
     {
      //--- 座標の中心までの距離を考慮したカメラの位置
      DXVector4 camera=DXVector4(0.0f,0.0f,-(float)m_camera_distance,1.0f);
      //--- X 軸を中心としたカメラの回転
      DXMatrix rotation;
      DXMatrixRotationX(rotation,m_camera_angles.x);
      DXVec4Transform(camera,camera,rotation);
      //--- Y 軸を中心としたカメラの回転
      DXMatrixRotationY(rotation,m_camera_angles.y);
      DXVec4Transform(camera,camera,rotation);
      //--- カメラを位置に設定する
      m_canvas.ViewPositionSet(DXVector3(camera));
      }

ソース コードは、以下の"Step8 Mouse Control.mq5"ファイルにあります。

マウスを使用してカメラの位置を制御する

マウスを使用してカメラの位置を制御します。


テクスチャの適用

テクスチャは、パターンまたはマテリアルを表すためにポリゴンのサーフェスに適用されるビットマップ イメージです。 テクスチャを使用すると、サーフェス上の小さなオブジェクトを再現することができ、ポリゴンを使用して作成した場合は、あまりにも多くのリソースが必要になります。 たとえば、石、木材、土、その他の材料の模倣をすることができます。

CDXMesh とその子クラスでは、テクスチャを指定できます。 標準のピクセル シェーダでは、このテクスチャは DiffuseColor と共に使用します。 オブジェクトアニメーションを削除し、石のテクスチャを適用します。 ターミナルタスクディレクトリのMQL5\Files folderに配置する必要があります。

   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- 非指向性照明の白色を設定する
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0));

      //--- 立方体の面を描画するテクスチャを追加します。
      m_box.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- 立方体をシーンに追加する
      m_canvas.ObjectAdd(&m_box);
      //--- シーンを再描画する
      Redraw();
      //---成功したら
      return(true);
      }

石の質感を持つ立方体

石の質感を持つ立方体。


カスタムオブジェクトの作成

すべてのオブジェクトは、インデックスを使用してプリミティブに接続される頂点(DXVector3)で構成されます。 最も一般的なプリミティブは三角形です。 基本的な 3D オブジェクトは、少なくとも座標 (通常、色など)、プリミティブの組み合わせ先のプリミティブの種類、およびプリミティブに結合される頂点インデックスのリストを含む頂点のリストを作成することによって作成されます。


標準ライブラリには DXVertex 頂点タイプがあり、その座標、ライティング計算、テクスチャ座標、色の標準があります。 標準の頂点シェーダは、この頂点タイプで動作します。

struct DXVertex
  {
   DXVector4         position;  // 頂点座標
   DXVector4         normal;    // 法線ベクトル
   DXVector2         tcoord;    // テクスチャを適用する面座標
   DXColor           vcolor;    // 色
  };


MQL5\Include\Canvas\DXDXUtils.mqh 補助型には、基本プリミティブのジオメトリ (頂点とインデックス) を生成し、.OBJ files .
から 3D ジオメトリをロードするための一連のメソッドがあります。
球とトーラスの作成を追加し、同じ石のテクスチャを適用します:

   virtual bool      Create(const int width,const int height)
     {
 ...     
      // --- 手動で作成されたオブジェクトの頂点とインデックス
      DXVertex vertices[];
      uint indices[];
      //--- 球の頂点とインデックスを準備する
      if(!DXComputeSphere(0.3f,50,vertices,indices))
         return(false);
      //--- 頂点に白色を設定する
      DXColor white=DXColor(1.0f,1.0f,1.0f,1.0f);
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- 球オブジェクトを作成する
      if(!m_sphere.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- 球の拡散色を設定する
      m_sphere.DiffuseColorSet(DXColor(0.0,1.0,0.0,1.0));
      //--- 白色の鏡面色を設定する
      m_sphere.SpecularColorSet(white);
      m_sphere.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- シーンに球を追加する
      m_canvas.ObjectAdd(&m_sphere);
      //--- トーラスの頂点とインデックスを準備する
      if(!DXComputeTorus(0.3f,0.1f,50,vertices,indices))
         return(false);
      //--- 頂点に白色を設定する
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- トーラス オブジェクトを作成する
      if(!m_torus.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- トーラスの拡散色を設定する
      m_torus.DiffuseColorSet(DXColor(0.0,0.0,1.0,1.0));
      m_torus.SpecularColorSet(white);
      m_torus.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- シーンにトーラスを追加する
      m_canvas.ObjectAdd(&m_torus);      
      //--- シーンを再描画する
      Redraw();
      //---成功したら
      return(true);
      }

新しいオブジェクトのアニメーションを追加します。

   void              OnTimer(void)
     {
...
      m_canvas.LightDirectionSet(light_direction);
      //--- 球軌道
      DXMatrix translation;
      DXMatrixTranslation(translation,1.1f,0,0);
      DXMatrixRotationY(rotation,time);
      DXMatrix transform;
      DXMatrixMultiply(transform,translation,rotation);
      m_sphere.TransformMatrixSet(transform);
      //--- 軸を中心とした回転を伴うトーラス軌道
      DXMatrixRotationX(rotation,time*1.3f);
      DXMatrixTranslation(translation,-2,0,0);
      DXMatrixMultiply(transform,rotation,translation);
      DXMatrixRotationY(rotation,time/1.3f);
      DXMatrixMultiply(transform,transform,rotation);
      m_torus.TransformMatrixSet(transform);           
      //--- 3D シーンを再計算し、キャンバスに描画します。
      Redraw();
      }


Three Objects.mq5として保存し、実行します。

立方体軌道上の回転図形。

立方体軌道上の回転図形。


データベースの 3D サーフェス

通常、さまざまなチャートは、線形チャート、ヒストグラム、円図などのレポートの作成やデータの分析に使用します。 MQL5は便利なグラフィックライブラリを提供しますが、2Dチャートしか構築できません。

CDXSurface クラスでは、2 次元配列に格納されたカスタム データを使用してサーフェスを視覚化できます。 次の数学関数の例を見てみましょう

z=sin(2.0*pi*sqrt(x*x+y*y))

サーフェスを描画するオブジェクトと、データを格納する配列を作成します。

   virtual bool      Create(const int width,const int height)
     {
...
      //--- データを格納するための配列を準備する
      m_data_width=m_data_height=100;
      ArrayResize(m_data,m_data_width*m_data_height);
      for(int i=0;i<m_data_width*m_data_height;i++)
         m_data[i]=0.0;
      //--- サーフェス オブジェクトを作成する
      if(!m_surface.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),m_data,m_data_width,m_data_height,2.0f,
                           DXVector3(-2.0,-0.5,-2.0),DXVector3(2.0,0.5,2.0),DXVector2(0.25,0.25),
                           CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- テクスチャと反射を作成する
      m_surface.SpecularColorSet(DXColor(1.0,1.0,1.0,1.0));
      m_surface.TextureSet(m_canvas.DXDispatcher(),"checker.bmp");
      //--- シーンにサーフェスを追加する
      m_canvas.ObjectAdd(&m_surface);
      //---成功したら
      return(true);
      }

この表面は、4x4 の底部と 1 の高さのボックス内に描画されます。 テクスチャの寸法は 0.25x0.25 です。

  • SF_TWO_SIDEDは、カメラがサーフェスの下を移動した場合にサーフェスがサーフェスの上と下の両方に描画されることを示します。
  • SF_USE_NORMALSは、指向性光源によって生じるサーフェスからの反射を計算するために、通常の計算が使用することを示します。
  • CS_COLD_TO_HOTは、サーフェスのヒートマップの色を青から赤に設定し、緑と黄色を遷移します。

サーフェスをアニメートするには、サインの下に時間を追加し、タイマーで更新します。

   void              OnTimer(void)
     {
      static ulong last_time=0;
      static float time=0;
      //--- 現在の時刻を取得する
      ulong current_time=GetMicrosecondCount();
      //---デルタを計算する
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- 経過時間値を増やす
      time+=deltatime;
      //--- 時刻を記録
      last_time=current_time;
      //--- 時間の変化を考慮してサーフェス値を計算する
      for(int i=0; i<m_data_width; i++)
        {
         double x=2.0*i/m_data_width-1;
         int offset=m_data_height*i;
         for(int j=0; j<m_data_height; j++)
           {
            double y=2.0*j/m_data_height-1;
            m_data[offset+j]=MathSin(2.0*M_PI*sqrt(x*x+y*y)-2*time);
            }
         }
      //--- サーフェスを描画するためにデータを更新する
      if(m_surface.Update(m_data,m_data_width,m_data_height,2.0f,
                          DXVector3(-2.0,-0.5,-2.0),DXVector3(2.0,0.5,2.0),DXVector2(0.25,0.25),
                          CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT))
        {
         //--- 3D シーンを再計算し、キャンバスに描画します。
         Redraw();
         }
      }

ソースコードは3D Surface.mq5で入手できます。プログラムの例はビデオに示されています。




この記事では、シンプルな幾何学的形状を作成するDirectX 関数の関数と、ビジュアル データ解析用のアニメーション化された 3D グラフィックスについて検討しました。 より複雑な例は、MetaTrader5ターミナルインストールディレクトリにあります:EA"Correlation Matrix 3D"と "Math 3D Morpher"、および "Remnant 3D"スクリプトです。 

MQL5では、サードパーティのパッケージを使用せずに重要なアルゴリズムトレードタスクを解決することができます。

  • 多くのインプットパラメータを含む複雑なトレード戦略を最適化する
  • 最適化結果の取得
  • 最も便利な 3 次元ストアでデータを視覚化する
最先端の関数を使用して株式データを視覚化し、3DグラフィックスでMetaTrader5でトレード戦略を開発しましょう!


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/7708

添付されたファイル |
Step4_Box_Color.mq5 (15.25 KB)
Step6_Add_Light.mq5 (14.27 KB)
Step7_Animation.mq5 (18.83 KB)
Step9_Texture.mq5 (23.16 KB)
Three_Objects.mq5 (27.9 KB)
3D_Surface.mq5 (24.48 KB)
MQL5.zip (199.48 KB)
トレードにおけるOLAPの適用(パート3):トレード戦略の開発の相場分析 トレードにおけるOLAPの適用(パート3):トレード戦略の開発の相場分析
この記事では、トレードに適用される OLAP テクノロジを引き続き取り扱います。 最初の 2 つの記事で紹介した機能を拡張します。 今回は、クオートの運用分析について検討します。シェイプセレクタ 集計されたヒストリーデータに基づいて、トレード戦略に関する仮説を打ち出し、テストします。 この記事では、バーパターンとアダプティブトレードを研究するためのEAを紹介します。
トレードシグナルの多通貨監視(パート1):アプリケーション構造の開発 トレードシグナルの多通貨監視(パート1):アプリケーション構造の開発
この記事では、トレードシグナルのマルチカレンシーモニターを作成するアイデアを考察し、そのプロトタイプと共に未来のアプリケーション構造を開発し、運用のフレームワークを作成します。 この記事では、トレードシグナルの生成を可能にし、トレーダーが目的のシグナルを見つけるのを助ける柔軟な多通貨アプリケーションの段階的な作成を提示します。
MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第26部): 未決取引リクエスト - 特定の条件下でのポジションのオープン MetaTraderプログラムを簡単かつ迅速に開発するためのライブラリ(第26部): 未決取引リクエスト - 特定の条件下でのポジションのオープン
この記事から始めて、特定の制限時間に達した、指定された利益を超えた、ストップロスによってポジションを決済したなどの特定の条件下で保留中リクエストを使用して取引できるようにする機能を開発します。
相場パターンを見つけるための計量的アプローチ:自己相関、ヒートマップ、散布図 相場パターンを見つけるための計量的アプローチ:自己相関、ヒートマップ、散布図
この記事では、季節的特徴の拡張である自己相関ヒートマップと散布図を紹介します。 この記事の目的は、"マーケットメモリ"が季節的な性質を持ち、任意のオーダーの増分の最大相関によって表現されることを示すものです。