English Русский 中文 Español Deutsch 日本語 Português Français Italiano Türkçe
preview
MetaTrader 5에서 DirectX를 사용하여 3D 그래픽을 만드는 방법

MetaTrader 5에서 DirectX를 사용하여 3D 그래픽을 만드는 방법

MetaTrader 5통합 | 14 10월 2022, 08:41
639 0
MetaQuotes
MetaQuotes

3차원 컴퓨터 그래픽은 평면 디스플레이에 3차원 물체의 형상이 나타나게 합니다. 이러한 객체와 뷰어의 위치는 시간이 지남에 따라 변경될 수 있습니다. 따라서 2차원 그림도 변경하여 이미지 깊이의 착시를 만들 수 있습니다. 그러기 위해서는 회전, 확대/축소, 조명등이 변경 되도록 지원해야 합니다. MQL5를 사용하면DirectX 함수를 사용하여 MetaTrader 5 터미널에서 컴퓨터 그래픽을 직접 만들고 관리할 수 있습니다. 이들 함수가 작동하려면 비디오 카드가DX 11및 Shader Model 5.0을 지원해야 합니다.


객체 모델링

평면 공간에 3차원 물체를 그리기 위해서는 먼저 이 물체의 X, Y, Z 좌표 모델을 구해야 합니다. 이는 물체 표면의 각 점에 좌표를 지정해 주어야 한다는 것을 의미합니다. 이상적으로는 스케일링 중에 이미지의 품질을 유지하기 위해서는 물체의 표면에 무한한 수의 점을 찍어야 합니다. 보통은 3D 모델에서는 다각형으로 구성된 메쉬를 사용하여 작업합니다. 더 많은 수의 다각형이 있는 더 상세한 메쉬는 더 사실적인 모델을 제공합니다. 그러나 이러한 모델을 계산하고 3D 그래픽을 렌더링하기 위해서는 더 많은 컴퓨터 리소스가 필요합니다.

찻주전자 모델 폴리곤 메쉬로서의 찻주전자 모델

다각형 메쉬로 만든 찻주전자 모델.

다각형을 삼각형으로 나누는 것은 초창기의 컴퓨터와 같이 그래픽이 약한 컴퓨터에서 좋지 않은 그래픽 카드를 통해 실행되어야 했던 오래 전의 일입니다. 삼각형을 사용하면 표면의 작은 면적의 위치를 정확하게 나타낼 수 있을 뿐만 아니라 조명이나 빛 반사와 같은 관련된 매개변수를 계산할 수 있습니다. 이러한 작은 삼각형들이 모이면 물체의 3차원 이미지를 사실적으로 만들 수 있습니다. 이하에서는 꼭짓점이 N인 다각형보다 꼭짓점이 N인 삼각형을 사용하는 것이 훨씬 쉽기 때문에 다각형과 삼각형을 동의어로 사용합니다.


삼각형으로 이루어진 큐브.

물체의 3차원 모델은 삼각형의 각 꼭짓점의 좌표를 기술함으로써 만들 수 있습니다. 이는 물체가 움직이거나 보는 사람의 위치가 변하더라도 물체의 각 점에 대한 좌표를 추가로 계산할 수 있게 합니다. 따라서 우리는 꼭짓점과 꼭짓점을 연결하는 모서리 및 모서리에 의해 형성되는 면을 다룹니다. 삼각형의 위치를 알면 선형 대수학의 법칙을 사용하여 면에 대한 법선을 만들 수 있습니다(법선은 표면에 수직인 벡터입니다). 이렇게 하면 얼굴에 조명이 비춰지는 방식과 얼굴에서 빛이 반사되는 방식을 계산할 수 있습니다.


정점, 가장자리, 면 및 법선이 있는 객체의 예. 법선은 빨간색 화살표입니다.

모델 객체는 다양한 방법으로 만들 수 있습니다. 토폴로지는 다각형이 어떻게 3D 메쉬를 형성하는지를 설명합니다. 좋은 토폴로지는 최소한의 다각형을 사용하여 객체를 설명하고 객체를 더 쉽게 이동하고 회전할 수 있도록 합니다.

구 모델두 토폴로지의 의 구 모델

두 가지 토폴로지의 구형 모델.

볼륨 효과는 객체 다각형에 빛과 그림자를 사용하여 생성됩니다. 따라서 3D 컴퓨터 그래픽의 목적은 물체의 각 점의 위치를 계산하여 빛과 그림자를 계산하여 화면에 표시하는 것입니다.

모양 만들기

큐브를 생성하는 간단한 프로그램을 작성해 보겠습니다. 3D 그래픽 라이브러리의 CCanvas3D 클래스를 사용하세요.

3D 창을 렌더링하는 CCanvas3DWindow 클래스에는 최소한의 멤버와 메서드가 있습니다. DirectX 작업을 위한 함수에 구현된 3D 그래픽 개념에 대한 설명과 함께 새로운 메서드를 점차 추가할 것입니다.

//+------------------------------------------------------------------+
//| 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) {}
  };

장면의 생성은 캔버스를 생성함으로써 시작됩니다. 그런 다음 프로젝션 행렬에 다음과 같은 매개변수가 설정됩니다.

  1. 3D 장면을 보는 방향의 30도 화각(M_PI/6)
  2. 가로 세로 비율
  3. 근거리(0.1f) 및 원거리(100.f) 클리핑 평면까지의 거리

이는 이 두 가상의 벽(0.1f 및 100.f) 사이의 객체만 투영 행렬에서 렌더링 한다는 뜻입니다. 또한 물체는 수평 30도의 화각에 있어야 합니다. 컴퓨터 그래픽의 모든 좌표와 거리가 가상이라는 점에 주목하십시오. 중요한 것은 거리와 크기 간의 관계이지 절대값이 아닙니다.

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

투영 행렬을 만든 후 CDXBox 클래스를 기반으로 하는 큐브인 3D 객체를 만드는 과정으로 이어집니다. 큐브를 만들려면 큐브의 반대쪽 모서리를 가리키는 두 벡터를 지정하면 됩니다. 디버그 모드에서 큐브의 생성을 관찰하면 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);

각 면의 텍스처 매핑을 위한 텍스처 좌표도 동일한 코드에 설명되어 있습니다:

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

각각의 4개의 면 벡터는 텍스처 매핑을 위한 4개의 각도 중 하나를 설정합니다. 이것은 텍스처를 렌더링하기 위해 분대 구조가 각 큐브의 면에 매핑 된다는 것을 의미합니다. 물론 이것은 텍스처가 설정된 경우에만 필요합니다.


장면 계산 및 렌더링

3D 장면이 변경될 때마다 모든 계산을 새로 수행해야 합니다. 필요한 계산 순서는 다음과 같습니다:

  • 월드 좌표에서 각 객체의 중심 계산
  • 객체의 각 요소, 즉 각 정점의 위치를 계산
  • 뷰어에 대한 픽셀의 깊이 및 가시성 결정
  • 꼭짓점으로 지정된 다각형에서 각 픽셀의 위치를 계산
  • 지정된 텍스처에 따라 다각형의 각 픽셀의 색상을 설정
  • 빛 픽셀의 방향과 반사를 계산
  • 각 픽셀에 확산광 적용
  • 모든 월드 좌표를 카메라 좌표로 변환
  • 카메라 좌표를 투영 행렬의 좌표로 변환
이러한 모든 작업은 CCanvas3D 객체의 Render 메서드에서 수행됩니다. 렌더링 후 계산된 이미지는 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();
      }

이 예에서는 큐브는 한 번만 생성되며 더 이상 변경되지 않습니다. 따라서 캔버스의 프레임은 차트 크기 조정과 같이 차트에 변경 사항이 있는 경우에만 변경하면 됩니다. 이 경우 캔버스 차원이 현재 차트의 차원으로 조정되고 투영 행렬이 재설정되며 캔버스의 이미지가 업데이트됩니다.

   //+------------------------------------------------------------------+
   //| 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();
         }
      }

"Step1 Create Box.mq5" EA를 실행합니다. 검정색 배경에 흰색 사각형이 표시되는 것을 볼 수 있습니다. 기본적으로 생성 될 때 객체에 흰색이 설정됩니다. 조명이 아직 설정되지 않았습니다.

흰색 큐브와 공간에서의 레이아웃

흰색 큐브와 공간에서 레이아웃

X축은 오른쪽으로 Y는 위쪽으로 Z는 3D 장면 안쪽으로 향합니다. 이러한 좌표 시스템을 왼손잡이라고 합니다.

큐브의 중심은 좌표 X=0, Y=0, Z=6인 점에 있습니다. 큐브를 보는 위치는 기본값인 좌표의 중심입니다. 3D 장면을 보는 위치를 변경하려면ViewPositionSet()함수를 사용하여 적절한 좌표를 명시적으로 설정합니다.

프로그램 작업을 완료하려면 "Escape"를 누르십시오.


Z축 및 뷰 포인트를 중심으로 한 객체 회전

장면에 애니메이션을 적용하려면 Z축을 중심으로 큐브 회전을 활성화합니다. 이렇게 하려면 타이머를 추가하세요. - 이벤트에 따라 큐브가 시계 반대 방향으로 회전합니다.

DXMatrixRotationZ() 메서드를 사용하여 주어진 각도로 Z축을 중심으로 회전할 수 있도록 회전 행렬을 만듭니다. 그런 다음 회전 행렬을 TransformMatrixSet() 메서드에 매개 변수로 전달합니다. 이렇게 하면 3D 공간에서 큐브의 위치가 변경됩니다. 다시 Redraw()를 호출하여 캔버스의 이미지를 업데이트합니다.

   //+------------------------------------------------------------------+
   //| 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();
      }

실행을 하면 회전하는 흰색 사각형이 표시됩니다.

큐브는 Z축을 중심으로 시계 반대 방향으로 회전합니다.

이 예제의 소스 코드는"Step2 Rotation Z.mq5" 파일에서 찾을 수 있습니다. 이제 장면을 만들 때 각도M_PI/5가 지정됩니다. 이전 예제의 각도 M_PI/6보다 큽니다. 

      //--- 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

그러나 화면의 큐브 차원은 시각적으로는 더 작습니다. 투영 매트릭스를 설정할 때 지정한 화각이 작을수록 물체가 차지하는 프레임 부분이 커집니다. 이것은 망원경으로 물체를 보는 것과 마찬가지입니다. 물체는 더 크지만 화각은 더 작습니다.


카메라 위치 관리

CCanvas3D 클래스에는 상호 연결된 3D 장면 매개변수를 설정하는 세 가지 방법이 있습니다.

이 모든 매개변수는 함께 사용됩니다. 즉 3D 장면에서 이러한 매개변수를 설정하려면 다른 두 매개변수도 초기화해야 합니다. 이것은 적어도 장면 생성 단계에서 수행되어야 합니다. 이것은 프레임의 위쪽 테두리가 왼쪽과 오른쪽으로 흔들리는 다음 예에서 볼 수 있습니다. 스윙은 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);
      }

수평선 벡터가 좌우로 흔들리도록 하기 위해 OnTimer() 메서드를 수정합니다.

   //+------------------------------------------------------------------+
   //| 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();
      }

예제를"Step3 ViewUpDirectionSet.mq5"로 저장하고 실행합니다. 실제로는 움직이지 않지만 흔들리는 큐브의 이미지를 볼 수 있습니다. 이 효과는 카메라 자체가 좌우로 흔들릴 때와 마찬가지입니다.

탑의 탑 방향이 좌우로 흔들린다

상단의 위쪽이 흔들립니다.

타겟 좌표, 카메라 좌표 및 상단 방향 좌표들이 연결되어 있다는 것을 기억하십시오. 따라서 카메라의 위치를 제어하려면 상단의 방향과 대상의 좌표 즉 응시점도 지정해야 합니다.


객체 색상 관리

카메라를 이동하면서 코드를 수정하고 큐브가 좌표의 중심에 놓아지도록 해 봅시다.

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

또한 큐브를 파란색으로 칠해봅니다. 색상은 알파 채널(알파 채널이 마지막에 표시됨)이 있는 RGB 색상 형식으로 설정되지만 값은 1로 정규화 됩니다. 그러므로 값 1은 255를 의미하고 0.5는 127을 의미합니다.

X축을 중심으로 회전을 추가하고 변경 사항을 "Step4 Box Color.mq5"로 저장합니다.

회전하는 큐브의 오른쪽 상단 보기.

회전하는 큐브의 오른쪽 상단 보기.


회전 및 이동

객체는 한 번에 세 방향으로 이동 및 회전할 수 있습니다. 객체 변경은 무엇이든지 행렬을 사용하여 구현됩니다. 즉 회전, 이동 및 변형은 별도로 계산할 수 있습니다. 예를 변경해 보겠습니다: 이제 카메라 뷰가 위쪽과 앞쪽에서 부터입니다.

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

회전 및 전송 행렬을 순차적으로 생성하고 결과 변환 행렬을 적용하고 큐브를 렌더링합니다. "Step5 Translation.mq5"에 변경 사항을 저장하고 실행합니다.

큐브의 회전과 이동

큐브의 회전과 이동

카메라는 정지된 상태로 있고 약간 위에서 좌표의 중심을 가리키도록 합니다. 큐브는 세 방향으로 회전했고 장면의 오른쪽, 아래쪽, 안쪽으로 이동했습니다.


조명 작업

사실적인 3차원 영상을 얻기 위해서는 물체 표면의 각 점의 조명을 계산해야 합니다. 이것은 Phong 셰이딩 모델을 사용하여 수행되며 이는 주변, 확산 및 반사광과 같은 세 가지 조명 구성 요소의 색상 강도를 계산합니다. 여기에서 사용되는 매개변수는 다음과 같습니다.

  • DirectionLight — 방향 조명의 방향은 CCanvas3D에서 설정됩니다.
  • AmbientLight — 앰비언트 조명의 색상과 강도는 CCanvas3D에서 설정됩니다.
  • DiffuseColor — 계산된 확산 조명 구성 요소는 CDXMesh 및 해당 자식 클래스에서 설정됩니다.
  • EmissionColor — 배경 조명 구성 요소는 CDXMesh 및 해당 자식 클래스에서 설정됩니다.
  • SpecularColor — 반사 구성 요소는 CDXMesh 및 해당 자식 클래스에 설정됩니다.

퐁 셰이딩 모델
퐁 음영 모델


조명 모델은 표준 셰이더에서 구현되고 모델 매개변수는 CCanvas3D로 설정되며 객체 매개변수는 CDXMesh 및 해당 자식 클래스에서 설정됩니다. 다음과 같이 예제를 수정합니다.

  1. 큐브를 좌표의 중심으로 되돌립니다.
  2. 큐브를 흰색으로 설정합니다.
  3. 위에서 아래로 장면을 비추는 노란색의 방향성 소스를 추가합니다.
  4. 무지향성 조명의 경우 파란색을 설정합니다.
      //--- 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)); 

Canvas3D에서는 광원의 위치가 설정되지 않고 빛이 퍼지는 방향만 알려주니 주의하시기 바랍니다. 지향성 광원은 무한한 거리에 있는 것으로 간주되며 언제나 평행한 광원 스트림으로 장면을 비춥니다.

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

여기에서 광 확산 벡터는 Y축을 따라 음의 방향, 즉 위에서 아래로 가리킵니다. 또한, 직접 광원(LightColorSet 및 LightDirectionSet)에 대한 매개변수를 설정하는 경우 주변 조명(AmbientColorSet)의 색상도 지정해야 합니다. 기본적으로 주변 조명의 색상은 최대 강도의 흰색으로 설정되므로 모든 그림자가 흰색이 됩니다. 즉 장면의 객체는 주변 조명의 흰색으로 넘쳐나고 방향 소스 라이트는 흰색 조명에 의해 간섭을 받습니다.

      //--- 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

아래 gif 애니메이션은 조명을 추가할 때 이미지가 어떻게 변하는지 보여줍니다. 예제의 소스 코드는"Step6 Add Light.mq5"파일에서 사용할 수 있습니다.

이 있는 노란색 광원 아래에서 녹색 방출이 있는 흰색 큐브파란색 주변 조명이 있는 노란색 광원 아래에서 녹색 방출이 있는 흰색 큐브

파란색 주변 조명이 있는 노란색 광원 아래에서 녹색의 방출이 있는 흰색 큐브

작동 방식을 보려면 위의 코드에서 색상 메서드를 해제해 보세요.


애니메이션

애니메이션은 시간이 지남에 따른 장면 매개변수와 객체의 변화를 의미합니다. 사용 가능한 모든 속성은 시간 또는 이벤트에 따라 변경될 수 있습니다. 타이머를 10밀리초로 설정합니다. 이 이벤트는 장면 업데이트에 영향을 미칩니다.

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

CCanvas3DWindow에 적절한 이벤트 핸들러를 추가합니다. 객체 매개변수(예: 회전, 이동 및 확대/축소)와 조명 방향을 변경해야 합니다:

   //+------------------------------------------------------------------+
   //| 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();
      }

객체의 변경 사항은 초기 값에서 적용된다는 점에 유의하십시오. 우리는 언제나 초기의 큐브의 상태를 다루면서 회전/이동/압축과 관련된 모든 작업을 처음부터 적용하는 것입니다. 즉 큐브의 현재 상태는 저장되지 않는다는 의미입니다. 그러나 광원의 방향은 현재 값에서deltatime단위로 변경됩니다.

회전하는 큐브 동적 조명이 있는 회전하는 큐브

동적으로 변화하는 광원의 방향으로 회전하는 큐브.

결과는 매우 복잡한 3D 애니메이션입니다. 예제 코드는"Step7 Animation.mq5" 파일에 있습니다.


마우스를 사용하여 카메라 위치 제어

3D 그래픽의 마지막 애니메이션 요소인 사용자의 동작에 대한 반응을 살펴보겠습니다. 이 예에서는 마우스를 사용하여 카메라 관리를 추가합니다. 먼저 마우스 이벤트를 받고 해당 핸들러를 만듭니다.

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);

CCanvas3DWindow에서 마우스 이동 이벤트 핸들러를 만듭니다. 왼쪽 버튼을 누른 상태에서 마우스를 움직이면 카메라 방향의 각도가 변경됩니다.

   //+------------------------------------------------------------------+
   //| 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;
         }
      }

다음은 카메라와 장면 중심의 간의 거리를 변경하는 마우스 휠 이벤트 핸들러입니다.

   //+------------------------------------------------------------------+
   //| 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();
      }

두 핸들러 모두 UpdateCameraPosition() 메서드를 호출하여 업데이트된 매개변수에 따라 카메라 위치를 업데이트합니다.

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

소스 코드는 아래의 "Step8 Mouse Control.mq5" 파일에 있습니다.

하여 카메라 위치 제어마우스를 사용

마우스를 사용하여 카메라 위치를 제어


텍스처 적용

텍스처는 패턴이나 재료를 나타내기 위해 다각형 표면에 적용되는 비트맵 이미지입니다. 텍스처를 사용하면 표면에 작은 객체를 재현할 수 있으나 다각형을 사용해서 만들면 너무 많은 리소스가 필요합니다. 예를 들어 돌, 나무, 흙과 같은 여러 재료를 모방할 수 있습니다.

CDXMesh와 그 자식 클래스를 사용해서 텍스처를 지정할 수 있습니다. 표준 픽셀 셰이더에서 이 텍스처는 DiffuseColor와 함께 사용됩니다. 객체 애니메이션을 제거하고 돌의 질감을 적용합니다. 터미널 작업 디렉토리의MQL5\Files 폴더에 있어야 합니다.

   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);
      }

의 큐브

돌 질감의 큐브


사용자 정의 객체 만들기

모든 객체는 인덱스를 사용하여 기본 요소에 연결된 꼭짓점(DXVector3)으로 구성됩니다. 가장 일반적인 기본 요소는 삼각형입니다. 기본 3D 객체는 결합되는 기본 도형 및 기본 도형들이 결합될 정점 인덱스의 목록과 좌표를 포함하는 정점 목록을 생성하여 생성됩니다(그러나 법선, 색상 등과 같은 많은 추가 데이터도 포함할 수 있음).


표준 라이브러리에는 좌표, 조명 계산을 위한 법선, 텍스처 좌표 및 색상이 포함된 DXVertex 정점 유형이 있습니다. 표준 정점 셰이더는 이 정점 유형과 함께 작동합니다.

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

MQL5\Include\Canvas\DXDXUtils.mqh 보조 유형에는 기본 도형의 지오메트리(꼭짓점 및 인덱스)를 생성하고 OBJ 파일에서 3D 지오메트리를 로드하기 위한 메서드 세트가 포함되어 있습니다.

구와 토러스의 생성을 추가하고 동일한 돌의 질감을 적용합니다.

   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);
      }

새 객체에 대한 애니메이션 추가:

   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();
      }

변경 사항을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)
     {
...
      //--- 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);
      }

표면은 밑면이 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;
      //--- 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();
         }
      }
소스 코드는3D Surface.mq5에서 사용할 수 있으며 프로그램의 예제는 비디오에 나와 있습니다.




이 기사에서는 시각적 데이터 분석을 위한 간단한 기하학적 모양과 애니메이션 3D 그래픽을 만드는 DirectX 함수의 기능에 대해 알아봤습니다. 더 복잡한 예는 MetaTrader 5 터미널 설치 디렉토리에서 찾을 수 있습니다: Expert Advisors "Correlation Matrix 3D" 및 "Math 3D Morpher" 및 "Remnant 3D" 스크립트. 

MQL5를 사용하면 타사의 패키지를 사용하지 않고도 알고리즘 거래와 관련한 작업을 해결할 수 있습니다.

  • 많은 수의 입력 매개변수를 가진 복잡한 거래 전략의 최적화
  • 최적화 결과 얻기
  • 편리한 3차원 스토어에서 데이터 시각화
최신의 기능을 사용하여 주식 데이터를 시각화하고 MetaTrader 5에서 거래 전략을 개발하십시오 — 이제 3D 그래픽으로!


MetaQuotes 소프트웨어 사를 통해 러시아어가 번역됨.
원본 기고글: https://www.mql5.com/ru/articles/7708

Expert Advisor 개발 기초부터 (파트 10): 맞춤형 지표 액세스하기 Expert Advisor 개발 기초부터 (파트 10): 맞춤형 지표 액세스하기
Expert Advisor에서 어떻게 맞춤형 지표에 바로 액세스 할 수 있을까요? 트레이딩 EA는 맞춤형 지표를 사용할 수 있는 경우에 더욱 유용할 수 있습니다; 그렇지 않으면 코드와 명령의 집합일 뿐입니다.
시각화! R 언어의 'plot'과 유사한 MQL5 그래픽 라이브러리 시각화! R 언어의 'plot'과 유사한 MQL5 그래픽 라이브러리
트레이딩의 로직을 연구할 때 그래프의 형태로 표시되는 시각적 표현은 매우 중요합니다. 과학 관련 커뮤니티에서 널리 사용되는 여러 프로그래밍 언어(예: R 및 Python)에는 시각화에 사용되는 특수한 '플롯' 함수가 있습니다. 이 함수들이 선, 점 분포 및 히스토그램을 그려서 패턴을 시각화 할 수 있습니다. MQL5에서는 CGraphics 클래스를 사용하여 동일한 작업을 수행할 수 있습니다.
Expert Advisor 개발 기초부터 (파트 11): 교차 주문 시스템(Cross order system) Expert Advisor 개발 기초부터 (파트 11): 교차 주문 시스템(Cross order system)
이 기사에서는 교차 주문 시스템을 만들 것입니다. 트레이더의 삶을 매우 어렵게 만드는 한 가지 자산이 있습니다. - 바로 선물 계약입니다. 왜 선물이 어렵게 만드는 것일까요?
SQLite: MQL5로 SQL 데이터베이스의 처리 SQLite: MQL5로 SQL 데이터베이스의 처리
트레이딩 전략을 개발하는 일은 많은 양의 데이터를 처리하는 것과 관련이 있습니다. 이제 MQL5에서 SQLite를 기반으로 하는 SQL 쿼리를 사용하여 데이터베이스로 작업할 수 있습니다. 이 엔진에서 중요한 점 전체 데이터베이스가 사용자의 PC에 있는 단일 파일에 저장된다는 것입니다.