English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
preview
Come creare grafica 3D utilizzando DirectX in MetaTrader 5

Come creare grafica 3D utilizzando DirectX in MetaTrader 5

MetaTrader 5Integrazione | 2 novembre 2022, 14:01
324 0
MetaQuotes
MetaQuotes

La grafica tridimensionale computerizzata dà l'impressione di oggetti tridimensionali su uno schermo piatto. Tali oggetti, così come la posizione dell'osservatore, può cambiare nel tempo. Di conseguenza, anche l'immagine bidimensionale deve cambiare per creare l'illusione della profondità dell'immagine, cioè deve supportare la rotazione, lo zoom, i cambi di luce e così via. MQL5 consente di creare e gestire la grafica computerizzata direttamente nel terminale MetaTrader 5 utilizzando le funzioni DirectX. Da notare che la scheda video deve supportare DX11 e Shader Model 5.0 affinché le funzioni funzionino.


Modellazione degli oggetti

Per disegnare un oggetto tridimensionale su uno spazio piano, occorre innanzitutto ottenere un modello di questo oggetto in coordinate X, Y e Z. Significa che ogni punto della superficie dell'oggetto deve essere descritto specificando le sue coordinate. Idealmente, si dovrebbe descrivere un numero infinito di punti sulla superficie dell'oggetto per preservare la qualità dell'immagine durante il ridimensionamento. In pratica, i modelli 3D vengono descritti utilizzando una rete costituita da poligoni. Una rete più dettagliata con un numero maggiore di poligoni fornisce un modello più realistico. Tuttavia, sono necessarie più risorse da parte del computer per calcolare un modello di questo tipo e per renderizzare la grafica 3D.

Modello di teiera come rete poligonale

Modello di teiera come rete poligonale.

La divisione dei poligoni in triangoli è comparsa molto tempo fa, quando la grafica dei primi computer doveva funzionare con schede grafiche scarse. Il triangolo consente di descrivere con precisione la posizione di una piccola parte di superficie e di calcolare i parametri correlati, come le luci e i riflessi della luce. L'insieme di questi piccoli triangoli permette di creare un'immagine tridimensionale realistica dell'oggetto. Di seguito, il poligono e il triangolo saranno usati come sinonimi, poiché è molto più facile immaginare un triangolo che un poligono con N vertici.


Cubo composto da triangoli.

Un modello tridimensionale di un oggetto può essere creato descrivendo le coordinate di ogni vertice del triangolo, il che consente di calcolare ulteriormente le coordinate per ogni punto dell'oggetto, anche se l'oggetto si muove o la posizione di osservazione cambia. Così, abbiamo a che fare con i vertici, gli spigoli che li collegano, e la faccia che è formata dagli spigoli. Se si conosce la posizione di un triangolo, possiamo creare una normale per la faccia utilizzando le leggi dell'algebra lineare (una normale è un vettore che è perpendicolare alla superficie). Questo permette di calcolare come il volto verrà illuminato e come la luce verrà riflessa da esso.


Esempi di oggetti semplici con vertici, spigoli, facce e normali. Una normale è una freccia rossa.

Un oggetto modello può essere creato in diversi modi. La topologia descrive come i poligoni formano la maglia 3D. Una buona topologia consente di utilizzare il numero minimo di poligoni per descrivere un oggetto e può facilitare lo spostamento e la rotazione dell'oggetto.

Modello a sfera in due topologie

Modello a sfera in due topologie.

L'effetto volume viene creato utilizzando luci e ombre sui poligoni dell'oggetto. Perciò, lo scopo della grafica computerizzata 3D è quello di calcolare la posizione di ogni punto di un oggetto, calcolare le luci e le ombre e visualizzarlo sullo schermo.

Creare una forma

Scriviamo un semplice programma che crea un cubo. Utilizzare la classe CCanvas3D della libreria 3D graphics.

La classe CCanvas3DWindow, che esegue il rendering di una finestra 3D, ha un numero minimo di membri e metodi. Aggiungeremo gradualmente nuovi metodi con una spiegazione dei concetti di grafica 3D implementati in funzioni per lavorare con DirectX.

//+------------------------------------------------------------------+
//| Application window                                               |
//+------------------------------------------------------------------+
class CCanvas3DWindow
  {
protected:
   CCanvas3D         m_canvas;
   //--- canvas size
   int               m_width;
   int               m_height;
   //--- the Cube object
   CDXBox            m_box;

public:
                     CCanvas3DWindow(void) {}
                    ~CCanvas3DWindow(void) {m_box.Shutdown();}
   //-- create a scene
   virtual bool      Create(const int width,const int height){}
   //--- calculate the scene
   void              Redraw(){}
   //--- handle chart events
   void              OnChartChange(void) {}
  };

La creazione di una scena inizia con la creazione dell’area di lavoro. Quindi si impostano i seguenti parametri per la matrice di proiezione:

  1. Un angolo di visuale di 30 gradi (M_PI/6), da cui si guarda la scena 3D
  2. Rapporto di aspetto come rapporto tra larghezza e altezza
  3. Distanza dal piano di ritaglio vicino (0,1f) e lontano (100,f)

Ciò significa che solo gli oggetti compresi tra queste due pareti virtuali (0,1f e 100,f) saranno renderizzati nella matrice di proiezione. Inoltre, l'oggetto deve rientrare nell'angolo di visuale orizzontale di 30 gradi. Notare che le distanze così come tutte le coordinate nella grafica computerizzata sono virtuali. Ciò che conta sono le relazioni tra le distanze e le dimensioni, ma non i valori assoluti.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
      //--- save canvas dimensions
      m_width=width;
      m_height=height;
      //--- create a canvas to render a 3D scene
      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);
         }
      //--- set projection matrix parameters - angle of view, aspect ratio, distance to the near and far clip planes
      m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      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);
         }
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

Dopo aver creato la matrice di proiezione, possiamo procedere alla costruzione dell'oggetto 3D — un cubo basato sulla classe CDXBox. Per creare un cubo, è sufficiente indicare due vettori che puntano agli angoli opposti del cubo. Osservando la creazione del cubo in modalità di debug, è possibile vedere cosa sta succedendo in DXComputeBox(): la creazione di tutti i vertici del cubo (le loro coordinate vengono scritte nell'array 'vertices') e la suddivisione degli spigoli del cubo in triangoli che vengono enumerati e salvati nell'array 'indiсes'. In totale, il cubo ha 8 vertici, 6 facce le quali si dividono in 12 triangoli e 36 indici che enumerano i vertici di questi triangoli.

Sebbene il cubo abbia solo 8 vertici, vengono creati 24 vettori per descriverli, in quanto è necessario specificare un insieme separato di vertici che hanno una normale per ciascuna delle 6 facce. La direzione della normale influisce sul calcolo dell'illuminazione di ciascuna faccia. L'ordine in cui i vertici di un triangolo sono elencati nell’indice determina quale dei suoi lati sarà visibile. L'ordine in cui vengono riempiti i vertici e gli indici viene mostrato nel codice DXUtils.mqh:

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

Le coordinate delle texture per la mappatura delle texture per ogni faccia sono descritte nello stesso codice:

//--- texture coordinates
   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);
      }

Ciascuno dei 4 vettori per le facce imposta uno dei 4 angoli per la mappatura delle texture. Questo significa che una struttura di gruppo sarà mappata su ogni faccia del cubo per renderizzare la texture. Naturalmente, questo è necessario solo se la texture è impostata.


Calcolo della scena e Rendering

Tutti i calcoli devono essere eseguiti nuovamente ogni volta che la scena 3D viene modificata. Ecco l'ordine dei calcoli richiesti:

  • Calcolo del centro di ogni oggetto in coordinate mondiali
  • Calcolare la posizione di ogni elemento dell'oggetto, cioè di ogni vertice
  • Determinare la profondità dei pixel e la loro visibilità per l'osservatore
  • Calcolare la posizione di ogni pixel sul poligono specificato dai suoi vertici
  • Impostare il colore di ogni pixel del poligono in base alla texture specificata.
  • Calcolare la direzione del pixel di luce e della sua riflessione
  • Applicare una luce diffusa a ciascun pixel
  • Convertire tutte le coordinate mondiali in coordinate della telecamera
  • Convertire le coordinate della telecamera nelle coordinate della matrice di proiezione
Tutte queste operazioni vengono eseguite nel metodo Render dell'oggetto CCanvas3D. Dopo il rendering, l'immagine calcolata viene trasferita dalla matrice di proiezione all'area di disegno, richiamando il metodo Update.
   //+------------------------------------------------------------------+
   //| Update the scene                                                 |
   //+------------------------------------------------------------------+
   void              Redraw()
     {
      //--- calculate the 3D scene
      m_canvas.Render(DX_CLEAR_COLOR|DX_CLEAR_DEPTH,ColorToARGB(clrBlack));
      //--- update the picture on the canvas in accordance with the current scene
      m_canvas.Update();
      }

Nel nostro esempio, il cubo viene creato una sola volta e non cambia più. Pertanto, il riquadro sull'area di disegno dovrà essere modificato solo se ci sono cambiamenti nel grafico, come ad esempio il ridimensionamento del grafico. In questo caso, le dimensioni dell'area di disegno vengono adattate alle dimensioni correnti del grafico, la matrice di proiezione viene reimpostata e l'immagine sull’area di disegno viene aggiornata.

   //+------------------------------------------------------------------+
   //| Process chart change event                                       |
   //+------------------------------------------------------------------+
   void              OnChartChange(void)
     {
      //--- get current chart sizes
      int w=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
      int h=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
      //--- update canvas dimensions in accordance with the chart size
      if(w!=m_width || h!=m_height)
        {
         m_width =w;
         m_height=h;
         //--- resize canvas
         m_canvas.Resize(w,h);
         DXContextSetSize(m_canvas.DXContext(),w,h);
         //--- update projection matrix in accordance with the canvas sizes
         m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
         //--- recalculate 3D scene and render it onto the canvas
         Redraw();
         }
      }

Avviare l'EA "Step1 Create Box.mq5". Si vedrà un quadrato bianco su sfondo nero. Per impostazione predefinita, è impostato il colore bianco per gli oggetti al momento della creazione. L'illuminazione non è ancora stata impostata.

Un cubo bianco e la sua disposizione nello spazio

Un cubo bianco e la sua disposizione nello spazio

L'asse X è diretto verso destra, Y verso l'alto e Z verso l'interno della scena 3D. Un sistema di coordinate di questo tipo è detto mancino.

Il centro del cubo si trova nel punto con le seguenti coordinate X=0, Y=0 e Z=6. La posizione dalla quale guardiamo il cubo è al centro delle coordinate,che è il valore predefinito. Se si vuole cambiare la posizione da cui viene visualizzata la scena 3D, è necessario impostare esplicitamente le coordinate appropriate utilizzando la funzione ViewPositionSet() .

Per completare il programma, premere "Escape".


Rotazione dell'oggetto intorno all'asse Z e al punto di vista

Per animare la scena, attiviamo la rotazione del cubo intorno all'asse Z. Per farlo, aggiungete un timer — in base ai suoi eventi, il cubo verrà ruotato in senso antiorario.

Creare una matrice di rotazione per consentire la rotazione intorno all'asse Z con un determinato angolo, utilizzando il metodo DXMatrixRotationZ(). Quindi passarlo come parametro al metodo TransformMatrixSet(). Questo cambierà la posizione del cubo nello spazio 3D. Chiamare, ancora Redraw() per aggiornare l'immagine sull’area di disegno.

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- variables for calculating the rotation angle
      static ulong last_time=0;
      static float angle=0;
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the angle of rotation of the cube around the Z axis
      angle+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- set the angle of rotation of the cube around the Z axis
      DXMatrix rotation;
      DXMatrixRotationZ(rotation,angle);
      m_box.TransformMatrixSet(rotation);
      //--- recalculate 3D scene and render it onto the canvas
      Redraw();
      }

Dopo il lancio, si vedrà un quadrato bianco rotante.

Il cubo ruota intorno all'asse Z in senso antiorario

Il codice sorgente di questo esempio è disponibile nel file "Step2 Rotation Z.mq5". Notare che l'angolo M_PI/5 viene ora specificato durante la creazione della scena, che è maggiore dell'angolo M_PI/6 dell'esempio precedente. 

      //--- set projection matrix parameters - angle of view, aspect ratio, distance to the near and far clip planes
      m_matrix_view_angle=(float)M_PI/5;
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube

Tuttavia, le dimensioni del cubo nella schermata sono visivamente più piccole. Più piccolo è l'angolo di visualizzazione specificato durante l'impostazione della matrice di proiezione, più grande è la parte di fotogramma occupata dall'oggetto. Questo può essere paragonato alla visione di un oggetto con un telescopio: l'oggetto è più grande, anche se l'angolo di visualizzazione è più piccolo.


Gestione della posizione della telecamera

La classe CCanvas3D ha tre metodi per l'impostazione di importanti parametri della scena 3D, che sono interconnessi:

Tutti questi parametri sono utilizzati in combinazione — ciò significa che se si desidera impostare uno di questi parametri nella scena 3D, devono essere inizializzati anche gli altri due parametri. Questo dovrebbe essere fatto almeno nella fase di generazione della scena. Questo è mostrato nell'esempio seguente, in cui il bordo superiore della struttura oscilla a sinistra e a destra. Lo swing viene implementato aggiungendo le seguenti tre righe di codice nel metodo Create() :

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
....       
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- set the scene parameters
      m_canvas.ViewUpDirectionSet(DXVector3(0,1,0));  // set the direction vector up, along the Y axis  
      m_canvas.ViewPositionSet(DXVector3(0,0,0));     // set the viewpoint from the center of coordinates
      m_canvas.ViewTargetSet(DXVector3(0,0,6));       // set the gaze point at center of the cube      
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

Modificare il metodo OnTimer() per far oscillare il vettore orizzonte a destra e a sinistra.

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- variables for calculating the rotation angle
      static ulong last_time=0;
      static float max_angle=(float)M_PI/30;
      static float time=0;
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the angle of rotation of the cube around the Z axis
      time+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- set the rotation angle around the Z axis
      DXVector3 direction=DXVector3(0,1,0);     // initial direction of the top
      DXMatrix rotation;                        // rotation vector      
      //--- calculate the rotation matrix 
      DXMatrixRotationZ(rotation,float(MathSin(time)*max_angle));
      DXVec3TransformCoord(direction,direction,rotation);
      m_canvas.ViewUpDirectionSet(direction);   // set the new direction of the top
      //--- recalculate 3D scene and render it onto the canvas
      Redraw();
      }

Salvate l'esempio come "Step3 ViewUpDirectionSet.mq5" ed eseguitelo. Vedrete l'immagine di un cubo che oscilla, anche se in realtà è immobile. Questo effetto si ottiene quando la telecamera stessa oscilla a destra e a sinistra.

La direzione della parte superiore oscilla a sinistra e a destra

La direzione della parte superiore oscilla a sinistra e a destra

Ricordate che esiste una connessione tra le coordinate dell’obiettivo, della telecamera e della direzione della parte superiore. Pertanto, per controllare la posizione della telecamera, è necessario specificare anche la direzione della parte superiore e le coordinate dell’obiettivo, cioè del punto di osservazione.


Gestione del Colore degli Oggetti

Modifichiamo il nostro codice e mettiamo il cubo al centro della coordinata, mentre muoviamo la telecamera.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      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);
         }
      //--- set the color 
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- set positions for camera, gaze and direction of the top
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));  // set the direction vector up, along the Y axis
      m_canvas.ViewPositionSet(DXVector3(3.0,2.0,-5.0));    // set camera on the right, on top and in front of the cube
      m_canvas.ViewTargetSet(DXVector3(0,0,0));             // set the gaze direction at center of the cube
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

Inoltre, coloriamo il cubo di blu. Il colore è impostato nel formato RGB con un canale alfa (il canale alfa è indicato per ultimo), anche se i valori sono normalizzati a uno. Pertanto, un valore di 1 significa 255 e 0,5 significa 127.

Aggiungiamo la rotazione intorno all'asse X e salvariamo le modifiche come "Step4 Box Color.mq5".

Vista in alto a destra di un cubo rotante.

Vista in alto a destra di un cubo rotante.


Rotazione e Movimento

Gli oggetti possono essere spostati e ruotati in tre direzioni alla volta. Tutte le modifiche agli oggetti sono implementate utilizzando le matrici. Ognuno di essi, ossia rotazione, movimento e trasformazione, possono essere calcolati separatamente. Cambiamo l'esempio: la visuale della telecamera è ora dall'alto e dal davanti.

   //+------------------------------------------------------------------+
   //| 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);
      //--- position the camera in top and in front of the center of coordinates
      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));      
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      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);
         }
      //--- set the cube color
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- calculate the cube position and the transfer matrix
      DXMatrix rotation,translation;
      //--- rotate the cube sequentially along the X, Y and Z axes
      DXMatrixRotationYawPitchRoll(rotation,(float)M_PI/4,(float)M_PI/3,(float)M_PI/6);
      //-- move the cube to the right/downward/inward
      DXMatrixTranslation(translation,1.0,-2.0,5.0);
      //--- get the transformation matrix as a product of rotation and transfer
      DXMatrix transform;
      DXMatrixMultiply(transform,rotation,translation);
      //--- set the transformation matrix 
      m_box.TransformMatrixSet(transform);      
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);    
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

Creare in sequenza le matrici di rotazione e di trasferimento, applicare il risultato della matrice di trasformazione e renderizzare il cubo. Salvare le modifiche in "Step5Translation.mq5” ed eseguirlo.

Rotazione e movimento di un cubo

Rotazione e movimento di un cubo

La telecamera è ferma ed è puntata al centro delle coordinate leggermente dall'alto. Il cubo è stato ruotato in tre direzioni ed è stato spostato in basso a destra e verso l'interno della scena.


Lavorare con l'Illuminazione

Per ottenere un'immagine tridimensionale realistica, è necessario calcolare l'illuminazione di ogni punto sulla superficie dell'oggetto. Questo viene fatto utilizzando il modello Phong shading, che calcola l'intensità del colore dei seguenti tre componenti dell'illuminazione: ambientale, diffusa e speculare. Qui vengono utilizzati i seguenti parametri:

  • DirectionLight — la direzione dell'illuminazione direzionale è impostata in CCanvas3D
  • AmbientLight — il colore e l'intensità dell'illuminazione ambientale sono impostati in CCanvas3D
  • DiffuseColor — la componente di illuminazione diffusa calcolata è impostata in CDXMesh e nelle sue classi figlie
  • EmissionColor — il componente di illuminazione dello sfondo è impostato in CDXMesh e nelle sue classi figlie
  • SpecularColor — il componente speculare è impostato in CDXMesh e nelle sue classi figlie

Modello di Phong shading
Modello di Phong shading


Il modello di illuminazione è implementato nell’ombreggiatore standard, i parametri del modello sono impostati in CCanvas3D e i parametri dell'oggetto sono impostati in CDXMesh e nelle sue classi figlie. Modificare l'esempio come segue:

  1. Riportare il cubo al centro delle coordinate.
  2. Impostarlo su bianco.
  3. Aggiungere una sorgente direzionale di colore giallo che illumini la scena dall'alto verso il basso.
  4. Impostare il colore blu per l'illuminazione non direzionale.
      //--- set yellow color for the source and direct it from above downwards
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- set the blue color for the ambient light 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));          
      //--- create cube - pass to it the resource manager, scene parameters and coordinates of two opposite corners of the cube
      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);
         }
      //--- set the white color for the cube
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0)); 
      //--- add green glow for the cube (emission)
      m_box.EmissionColorSet(DXColor(0.0,1.0,0.0,0.2f)); 

Notare che la posizione della sorgente di luce diretta non viene impostata in Canvas3D, mentre viene indicata solo la direzione in cui la luce si diffonde. La sorgente di luce direzionale è considerata a distanza infinita e un flusso di luce strettamente parallelo illumina la scena.

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

In questo caso, il vettore di diffusione della luce è puntato lungo l'asse Y in direzione negativa, cioè dall'alto verso il basso. Inoltre, se si impostano i parametri per la sorgente di luce diretta (LightColorSet e LightDirectionSet), è necessario specificare anche il colore della luce ambientale (AmbientColorSet). Per impostazione predefinita, il colore della luce ambientale è impostato su bianco con intensità massima e quindi tutte le ombre saranno bianche. Ciò significa che gli oggetti della scena saranno illuminati di bianco dall'illuminazione ambientale, mentre la sorgente luminosa direzionale sarà interrotta dalla luce bianca.

      //--- set yellow color for the source and direct it from above downwards
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- set the blue color for the ambient light 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));  // must be specified

La seguente gif animata mostra come cambia l'immagine quando si aggiunge l'illuminazione. Il codice sorgente dell'esempio è disponibile nel file "Step6AddLight.mq5".

Il cubo bianco con emissione verde sotto una sorgente di luce gialla, con luce ambientale blu

Il cubo bianco con emissione verde sotto una sorgente di luce gialla, con luce ambientale blu

Provate a disattivare i metodi del colore nel codice qui sopra per vedere come funziona.


Animazione

L'animazione implica un cambiamento dei parametri della scena e degli oggetti nel tempo. Tutte le proprietà disponibili possono essere modificate in base al tempo o agli eventi. Impostare il timer su 10 millisecondi — questo evento influenzerà l'aggiornamento della scena:

int OnInit()
  {
...
//--- create canvas
   ExtAppWindow=new CCanvas3DWindow();
   if(!ExtAppWindow.Create(width,height))
      return(INIT_FAILED);
//--- set timer
   EventSetMillisecondTimer(10);
//---
   return(INIT_SUCCEEDED);
   }

Aggiungere il gestore di eventi appropriato a CCanvas3DWindow. È necessario modificare i parametri dell'oggetto (come la rotazione, il movimento e lo zoom) e la direzione dell'illuminazione:

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {    
      static ulong last_time=0;
      static float time=0;       
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the elapsed time value
      time+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- calculate the cube position and the rotation matrix
      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);
      //--- calculate the cube compression/extension along the axes
      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));
      //--- multiply the matrices to obtain the final transformation
      DXMatrix transform;
      DXMatrixMultiply(transform,scale,rotation);
      DXMatrixMultiply(transform,transform,translation);
      //--- set the transformation matrix
      m_box.TransformMatrixSet(transform);
      //--- calculate the rotation of the light source around the Z axis
      DXMatrixRotationZ(rotation,deltatime);
      DXVector3 light_direction;
      //--- get the current direction of the light source
      m_canvas.LightDirectionGet(light_direction);
      //--- calculate the new direction of the light source and set it
      DXVec3TransformCoord(light_direction,light_direction,rotation);
      m_canvas.LightDirectionSet(light_direction);
      //--- recalculate the 3D scene and draw it in the canvas
      Redraw();
      }

Notare che le modifiche agli oggetti vengono applicate sui valori iniziali, come se si trattasse sempre dello stato iniziale del cubo e si applicassero tutte le operazioni relative a rotazione/movimento/compressione da zero, il che significa che lo stato attuale del cubo non viene salvato. Tuttavia, la direzione della sorgente luminosa viene modificata con incrementi del deltatime rispetto al valore corrente.

Un cubo rotante con illuminazione dinamica

Un cubo rotante con la direzione della sorgente luminosa che cambia dinamicamente.

Il risultato è un'animazione 3D molto complessa. Il codice di esempio è disponibile nel file "Step7Animation.mq5".


Controllare la Posizione della Telecamera Utilizzando il Mouse

Consideriamo l'ultimo elemento di animazione della grafica 3D, una reazione alle azioni dell'utente. Aggiungere la gestione della telecamera utilizzando il mouse nel nostro esempio. Innanzitutto, sottoscrivere gli eventi del mouse e creare i gestori corrispondenti:

int OnInit()
  {
...
//--- set the timer
   EventSetMillisecondTimer(10);
//--- enable receiving of mouse events: moving and button clicks
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1)
//---
   return(INIT_SUCCEEDED);
   }
void OnDeinit(const int reason)
  {
//--- Deleting the timer
   EventKillTimer();
//--- disable the receiving of mouse events
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,0);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,0);
//--- delete the object
   delete ExtAppWindow;
//--- return chart to the usual display mode with price charts
   ChartSetInteger(0,CHART_SHOW,true);
   }
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- chart change event
   if(id==CHARTEVENT_CHART_CHANGE)
      ExtAppWindow.OnChartChange();
//--- mouse movement event
   if(id==CHARTEVENT_MOUSE_MOVE)
      ExtAppWindow.OnMouseMove((int)lparam,(int)dparam,(uint)sparam);
//--- mouse wheel scroll event
   if(id==CHARTEVENT_MOUSE_WHEEL)
      ExtAppWindow.OnMouseWheel(dparam);

In CCanvas3DWindow, creare il gestore dell'evento di movimento del mouse. Cambierà l'angolo di direzione della telecamera quando il mouse viene spostato con il tasto sinistro premuto:

   //+------------------------------------------------------------------+
   //| Handle mouse movements                                           |
   //+------------------------------------------------------------------+
   void              OnMouseMove(int x,int y,uint flags)
     {
      //--- left mouse button
      if((flags&1)==1)
        {
         //--- there is no information about the previous mouse position
         if(m_mouse_x!=-1)
           {
            //--- update the camera angle upon change of position
            m_camera_angles.y+=(x-m_mouse_x)/300.0f;
            m_camera_angles.x+=(y-m_mouse_y)/300.0f;
            //--- set the vertical angle in the range between (-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;
            //--- update camera position
            UpdateCameraPosition();
            }
         //--- save mouse position
         m_mouse_x=x;
         m_mouse_y=y;
         }
      else
        {
         //--- reset the saved position if the left mouse button is not pressed
         m_mouse_x=-1;
         m_mouse_y=-1;
         }
      }

Qui è il gestore dell'evento della rotellina del mouse, che modifica la distanza tra la telecamera e il centro della scena:

   //+------------------------------------------------------------------+
   //| Handling mouse wheel events                                      |
   //+------------------------------------------------------------------+
   void              OnMouseWheel(double delta)
     {
      //--- update the distance between the camera and the center upon a mouse scroll
      m_camera_distance*=1.0-delta*0.001;
      //--- set the distance in the range between [3,50]
      if(m_camera_distance>50.0)
         m_camera_distance=50.0;
      if(m_camera_distance<3.0)
         m_camera_distance=3.0;
      //--- update camera position
      UpdateCameraPosition();
      }

Entrambi i gestori richiamano il metodo UpdateCameraPosition() per aggiornare la posizione della telecamera in base ai parametri aggiornati:

   //+------------------------------------------------------------------+
   //| Updates the camera position                                      |
   //+------------------------------------------------------------------+
   void              UpdateCameraPosition(void)
     {
      //--- the position of the camera taking into account the distance to the center of coordinates
      DXVector4 camera=DXVector4(0.0f,0.0f,-(float)m_camera_distance,1.0f);
      //--- camera rotation around the X axis
      DXMatrix rotation;
      DXMatrixRotationX(rotation,m_camera_angles.x);
      DXVec4Transform(camera,camera,rotation);
      //--- camera rotation around the Y axis
      DXMatrixRotationY(rotation,m_camera_angles.y);
      DXVec4Transform(camera,camera,rotation);
      //--- set camera to position
      m_canvas.ViewPositionSet(DXVector3(camera));
      }

Il codice sorgente è disponibile nel file "Step8MouseControl.mq5" qui sotto.

Controllo della Posizione della Telecamera Utilizzando il Mouse

Controllo della Posizione della Telecamera Utilizzando il Mouse


Applicazione di Texture

Una texture è un'immagine bitmap che viene applicata alla superficie di un poligono per rappresentare modelli o materiali. L'uso delle texture permette di riprodurre piccoli oggetti sulla superficie, che richiederebbero troppe risorse se li creassimo utilizzando i poligoni. Ad esempio, può essere un'imitazione di una pietra, del legno, del suolo e di altri materiali.

CDXMesh e le sue classi figlie consentono di specificare la texture. Nell’ombreggiatore di pixel standard questa texture viene utilizzata insieme a DiffuseColor. Rimuovere l'animazione dell'oggetto e applicare una texture di pietra. Dovrebbe trovarsi nella cartella MQL5\Files della directory di lavoro del terminale:

   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- set the white color for the non-directional lighting
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0));

      //--- add texture to draw the cube faces
      m_box.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- add the cube to the scene
      m_canvas.ObjectAdd(&m_box);
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

Un cubo con una texture di pietra

Un cubo con una texture di pietra.


Creazione di Oggetti Personalizzati

Tutti gli oggetti sono costituiti da vertici (DXVector3), che sono collegati alle primitive utilizzando gli indici. La primitiva più comune è il triangolo. Un oggetto 3D di base viene creato creando un elenco di vertici che contiene almeno le coordinate (ma può contenere anche molti dati aggiuntivi, come le normali, i colori, ecc.), il tipo di primitive in cui sono combinati e un elenco di indici dei vertici con cui saranno combinati nelle primitive.


La Libreria Standard dispone del tipo di vertice DXVertex, che contiene le sue coordinate, una normale per il calcolo dell'illuminazione, le coordinate della texture e il colore. L’ombreggiatore di vertice standard funziona con questo tipo di vertice.

struct DXVertex
  {
   DXVector4         position;  // vertex coordinates
   DXVector4         normal;    // normal vector
   DXVector2         tcoord;    // face coordinate to apply the texture
   DXColor           vcolor;    // color
  };

Il tipo ausiliario MQL5\Include\Canvas\DXDXUtils.mqh contiene una serie di metodi per generare la geometria (vertici e indici) delle primitive di base e per caricare la geometria 3D dal file .OBJ.

Aggiungiamo la creazione di una sfera e di un toroidale, applicando la stessa texture di pietra:

   virtual bool      Create(const int width,const int height)
     {
 ...     
      // --- vertices and indexes for manually created objects
      DXVertex vertices[];
      uint indices[];
      //--- prepare vertices and indices for the sphere
      if(!DXComputeSphere(0.3f,50,vertices,indices))
         return(false);
      //--- set white color for the vertices
      DXColor white=DXColor(1.0f,1.0f,1.0f,1.0f);
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- create the sphere object
      if(!m_sphere.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set diffuse color for the sphere
      m_sphere.DiffuseColorSet(DXColor(0.0,1.0,0.0,1.0));
      //--- set white specular color
      m_sphere.SpecularColorSet(white);
      m_sphere.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- add the sphere to a scene
      m_canvas.ObjectAdd(&m_sphere);
      //--- prepare vertices and indices for the torus
      if(!DXComputeTorus(0.3f,0.1f,50,vertices,indices))
         return(false);
      //--- set white color for the vertices
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- create the torus object
      if(!m_torus.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
         }
      //--- set diffuse color for the torus
      m_torus.DiffuseColorSet(DXColor(0.0,0.0,1.0,1.0));
      m_torus.SpecularColorSet(white);
      m_torus.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- add the torus to a scene
      m_canvas.ObjectAdd(&m_torus);      
      //--- redraw the scene
      Redraw();
      //--- succeed
      return(true);
      }

Aggiungere l'animazione ai nuovi oggetti:

   void              OnTimer(void)
     {
...
      m_canvas.LightDirectionSet(light_direction);
      //--- sphere orbit
      DXMatrix translation;
      DXMatrixTranslation(translation,1.1f,0,0);
      DXMatrixRotationY(rotation,time);
      DXMatrix transform;
      DXMatrixMultiply(transform,translation,rotation);
      m_sphere.TransformMatrixSet(transform);
      //--- torus orbit with rotation around its axis
      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);           
      //--- recalculate the 3D scene and draw it in the canvas
      Redraw();
      }

Salviamo le modifiche come Threeobjects.mq5 ed eseguiamolo.

Figure rotanti nell'orbita del cubo.

Figure rotanti nell'orbita del cubo.


Superficie 3D Basata sui Dati

Vari grafici sono solitamente utilizzati per la creazione di report e l'analisi dei dati, come grafici lineari, istogrammi, diagrammi a torta, ecc. MQL5 offre una comoda libreria grafica, che però può creare solo grafici 2D.

La classe CDXSurface consente di visualizzare una superficie utilizzando dati personalizzati memorizzati in un array bidimensionale. Vediamo l'esempio della seguente funzione matematica

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

Creiamo un oggetto per disegnare la superficie e un array per memorizzare i dati:

   virtual bool      Create(const int width,const int height)
     {
...
      //--- prepare an array to store data
      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;
      //--- create a surface object
      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);
         }
      //--- create texture and reflection
      m_surface.SpecularColorSet(DXColor(1.0,1.0,1.0,1.0));
      m_surface.TextureSet(m_canvas.DXDispatcher(),"checker.bmp");
      //--- add the surface to the scene
      m_canvas.ObjectAdd(&m_surface);
      //--- succeed
      return(true);
      }

La superficie sarà disegnata all'interno di un riquadro con una base di 4x4 e un'altezza di 1. Le dimensioni della texture sono 0,25x0,25.

  • SF_TWO_SIDED indica che la superficie verrà disegnata sia sopra che sotto, nel caso in cui la telecamera si muova sotto la superficie.
  • SF_USE_NORMALS indica che i calcoli della normale saranno utilizzati per calcolare i riflessi dalla superficie causate dalla sorgente di luce direzionale.
  • CS_COLD_TO_HOT imposta la colorazione della mappa di calore della superficie dal blu al rosso con una transizione attraverso il verde e il giallo.

Per animare la superficie, aggiungere il tempo sotto il segno del seno e aggiornarlo con il timer.

   void              OnTimer(void)
     {
      static ulong last_time=0;
      static float time=0;
      //--- get the current time
      ulong current_time=GetMicrosecondCount();
      //--- calculate the delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- increase the elapsed time value
      time+=deltatime;
      //--- remember the time
      last_time=current_time;
      //--- calculate surface values taking into account time changes
      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);
            }
         }
      //--- update data to draw the surface
      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))
        {
         //--- recalculate the 3D scene and draw it in the canvas
         Redraw();
         }
      }
Il codice sorgente è disponibile in 3DSurface.mq5, mentre l'esempio del programma è mostrato nel video.




In questo articolo abbiamo preso in considerazione le capacità delle funzioni DirectX per creare forme geometriche semplici e grafica animata 3D per l'analisi visiva dei dati. Esempi più complessi possono essere trovati nella directory di installazione del terminale MetaTrader 5: Expert Advisor "Correlation Matrix 3D" e "Math 3D Morpher", come lo script "Remnant 3D". 

MQL5 consente di risolvere importanti operazioni di trading algoritmico senza ricorrere a pacchetti di terze parti:

  • Ottimizzare strategie di trading complesse che contengono molti parametri di input.
  • Ottenere risultati di ottimizzazione
  • Visualizzare i dati nel più comodo archivio tridimensionale
Utilizzate le funzionalità più avanzate per visualizzare i dati azionari e sviluppare strategie di trading in MetaTrader 5 — ora con grafica 3D!


Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/7708

File allegati |
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)
Sviluppare un Expert Advisor per il trading da zero (Parte 10): Accesso agli indicatori personalizzati Sviluppare un Expert Advisor per il trading da zero (Parte 10): Accesso agli indicatori personalizzati
Come accedere agli indicatori personalizzati direttamente in un Expert Advisor? Un EA per il trading può essere veramente utile solo se può utilizzare indicatori personalizzati; altrimenti, è solo un insieme di codici e istruzioni.
Visualizza questo! Libreria grafica di MQL5 simile a 'plot' del linguaggio R Visualizza questo! Libreria grafica di MQL5 simile a 'plot' del linguaggio R
Quando si studia la logica del trading, la rappresentazione visiva sotto forma di grafici è di grande importanza. Alcuni linguaggi di programmazione popolari tra la comunità scientifica (come R e Python) dispongono della speciale funzione "plot" utilizzata per la visualizzazione. Permette di disegnare linee, distribuzioni di punti e istogrammi per visualizzare i modelli. In MQL5, è possibile fare lo stesso utilizzando la classe CGraphics.
Sviluppare un Expert Advisor per il trading da zero (Parte 11): Sistema di ordini incrociati Sviluppare un Expert Advisor per il trading da zero (Parte 11): Sistema di ordini incrociati
In questo articolo creeremo un sistema di ordini incrociati. C'è un tipo di asset che rende la vita dei trader molto difficile — i contratti future. Ma perché rendono la vita difficile?
SQLite: Gestione nativa dei database SQL in MQL5 SQLite: Gestione nativa dei database SQL in MQL5
Lo sviluppo delle strategie di trading è associato alla gestione di grandi quantità di dati. Ora è possibile lavorare con i database utilizzando query SQL basate su SQLite direttamente in MQL5. Una caratteristica importante di questo motore è che l'intero database è collocato in un unico file situato sul PC dell'utente.