Come creare grafica 3D utilizzando DirectX in MetaTrader 5
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
- Creare una forma
- Calcolo della scena e Rendering
- Rotazione dell'oggetto intorno all'asse Z e al punto di vista
- Gestione della posizione della telecamera
- Gestione del Colore degli Oggetti
- Rotazione e Movimento
- Lavorare con l'Illuminazione
- Animazione
- Controllare la Posizione della Telecamera Utilizzando il Mouse
- Applicazione di Texture
- Creazione di Oggetti Personalizzati
- Superficie 3D Basata sui Dati
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.
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.
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:
- Un angolo di visuale di 30 gradi (M_PI/6), da cui si guarda la scena 3D
- Rapporto di aspetto come rapporto tra larghezza e altezza
- 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
//+------------------------------------------------------------------+ //| 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
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:
- ViewPositionSet imposta il punto di vista della scena 3D
- ViewTargetSet imposta le coordinate del punto di vista
- ViewUpDirectionSet imposta la direzione del bordo superiore della struttura nello spazio 3D
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
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.
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
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
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:
- Riportare il cubo al centro delle coordinate.
- Impostarlo su bianco.
- Aggiungere una sorgente direzionale di colore giallo che illumini la scena dall'alto verso il basso.
- 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
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 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
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.
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.
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
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/7708
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso