如何在 MetaTrader 5 中利用 DirectX 创建 3D 图形·进阶篇
🧊

如何在 MetaTrader 5 中利用 DirectX 创建 3D 图形·进阶篇

(2/3)· 显卡跑得动 DX11 却画不出立体感?本篇把旋转、着色与动画的底层逻辑一次讲透

含代码示例偏理论 第 2/3 篇

很多交易者以为在 MT5 里调出 3D 图形只是换个指标样式,其实从顶点法线到相机矩阵任一步算错,屏幕上的物体就会扭曲或消失。把 DirectX 当成普通画图函数用,往往卡在光照和旋转联动上出不来图。

「相机三参必须一起初始化」

在 CCanvas3D 里操控 3D 场景,视点、凝视点、顶部方向这三个量是绑死的。ViewPositionSet 管相机放哪,ViewTargetSet 管看向哪,ViewUpDirectionSet 管画面顶边朝哪倾;只要动其中一个,另外两个不赋值,场景生成阶段就会缺参数跑偏。 原文示例把文件存成 “Step3 ViewUpDirectionSet.mq5” 运行后,立方体本身没动,但因为相机顶部方向在左右摆,看起来就像静物在转。这效果靠在 Create() 里补三行初始化,再让 OnTimer() 驱动顶点水平摆动实现。 别把三个方法当独立开关 很多新手只设 ViewPositionSet 就等画面出来,结果黑屏或畸变。目标点坐标 (0,0,6) 和顶部 (0,1,0) 不写,相机姿态就是未定义状态。 下面代码里三行高亮的就是最小可用组合:视线从坐标原点出发,盯着 Z=6 的立方体中心,顶边沿 Y 轴。复制进你自己的 Create() 就能直接验证。

MQL5 / C++
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 创建                                                                |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="kw">virtual class="type">bool     Create(class="kw">const class="type">int width,class="kw">const class="type">int height)
   {
....    
    class=class="str">"cmt">//--- 在场景里加入立方体
    m_canvas.ObjectAdd(&m_box);
    class=class="str">"cmt">//--- 设置场景参数
    m_canvas.ViewUpDirectionSet(DXVector3(class="num">0,class="num">1,class="num">0));    class=class="str">"cmt">// set the direction vector up, along the Y axis
    m_canvas.ViewPositionSet(DXVector3(class="num">0,class="num">0,class="num">0));       class=class="str">"cmt">// set the viewpoint from the center of coordinates
    m_canvas.ViewTargetSet(DXVector3(class="num">0,class="num">0,class="num">6));         class=class="str">"cmt">// set the gaze point at center of the cube
    class=class="str">"cmt">//--- 刷新场景
    Redraw();
    class=class="str">"cmt">//--- 成功
    class="kw">return(true);
   }
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 计时器响应                                                          |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="type">void              OnTimer(class="type">void)
   {
    class=class="str">"cmt">//--- 计算旋转角度的变量
    class="kw">static class="type">class="kw">ulong last_time=class="num">0;
    class="kw">static class="type">float max_angle=(class="type">float)M_PI/class="num">30;
    class="kw">static class="type">float time=class="num">0;
    class=class="str">"cmt">//--- 获取当前时间
    class="type">class="kw">ulong current_time=GetMicrosecondCount();
    class=class="str">"cmt">//--- 计算增量
    class="type">float deltatime=(current_time-last_time)/class="num">1000000.0f;
    if(deltatime>class="num">0.1f)
       deltatime=class="num">0.1f;
    class=class="str">"cmt">//--- 立方体绕 Z 轴的旋转角度递增
    time+=deltatime;
    class=class="str">"cmt">//--- 记住时间
    last_time=current_time;
    class=class="str">"cmt">//--- 设置绕 Z 轴的旋转角度
    DXVector3 direction=DXVector3(class="num">0,class="num">1,class="num">0);       class=class="str">"cmt">// initial direction of the top
    DXMatrix rotation;                           class=class="str">"cmt">// rotation vector
    class=class="str">"cmt">//--- 计算旋转矩阵 

用旋转矩阵让视角顶部方向随时间摆动

在 MT5 的 DirectX 画布里,想让 3D 场景的“上方向”动起来,核心就是每秒根据时间算一个绕 Z 轴的旋转矩阵,再把方向向量变换后塞回 ViewUpDirectionSet。下面这段代码把摆动幅度限制在 max_angle 内,用 MathSin(time) 驱动,属于典型的周期性视角微动。 DXMatrixRotationZ(rotation, float(MathSin(time)*max_angle)); 先按正弦值生成 Z 轴旋转矩阵;DXVec3TransformCoord(direction, direction, rotation); 把原有方向向量按该矩阵做坐标变换;m_canvas.ViewUpDirectionSet(direction); 将变换后的向量设为画布顶部新方向;随后 Redraw() 重算 3D 场景并渲染。 实盘意义不大,但在做自定义指标可视化或 AIGC 辅助盯盘面板时,这种轻量动画能避免静态视角带来的空间误判。外汇与贵金属波动剧烈,任何可视化只是辅助,下单前仍以价格行为本身为准。

MQL5 / C++
  DXMatrixRotationZ(rotation,class="type">float(MathSin(time)*max_angle));
  DXVec3TransformCoord(direction,direction,rotation);
  m_canvas.ViewUpDirectionSet(direction);   class=class="str">"cmt">// set the new direction of the top
  class=class="str">"cmt">//--- 重新计算 3D 场景,并在画布上渲染
  Redraw();
  }

◍ 给立方体上色并摆好相机位

在 MT5 的 DX 场景里给几何体染色,颜色不是用 0-255 的字节,而是归一化到 0.0–1.0 的浮点。数值 1.0 对应 255,0.5 对应 127,末尾还要带 Alpha 通道,写法就是 RGBA 顺序。 把立方体染成蓝色,只要调 DiffuseColorSet 传 DXColor(0.0, 0.5, 1.0, 1.0) 即可——红 0、绿 0.5、蓝 1.0、Alpha 满。顺手把相机挪到右侧上方前方 (3.0, 2.0, -5.0),凝视点锁在原点,物体就稳居画面中心。 代码里 ViewUpDirectionSet 用 (0,1,0) 定 Y 轴向上,避免画面歪斜;改完存成 “Step4 Box Color.mq5”,开 MT5 跑一下能看到绕 X 轴转的蓝立方体右上视图。外汇与贵金属图表外做这类图形验证同样涉及软件环境风险,参数不对可能黑屏,先小窗测试。

MQL5 / C++
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 创建                                                                 |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="kw">virtual class="type">bool      Create(class="kw">const class="type">int width,class="kw">const class="type">int height)
   {
  ...
     class=class="str">"cmt">//--- 创建立方体 - 将立方体的两个对角坐标、资源管理器、场景参数传递给它
     if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-class="num">1.0,-class="num">1.0,-class="num">1.0),DXVector3(class="num">1.0,class="num">1.0,class="num">1.0)))
      {
       m_canvas.Destroy();
       class="kw">return(false);
      }
     class=class="str">"cmt">//--- 设置颜色 
     m_box.DiffuseColorSet(DXColor(class="num">0.0,class="num">0.5,class="num">1.0,class="num">1.0));        
     class=class="str">"cmt">//--- 在场景里加入立方体
     m_canvas.ObjectAdd(&m_box);
     class=class="str">"cmt">//--- 设置相机的位置,凝视点和顶部的方向
     m_canvas.ViewUpDirectionSet(DXVector3(class="num">0.0,class="num">1.0,class="num">0.0)); class=class="str">"cmt">// 沿 Y 轴方向设置向上顶点
     m_canvas.ViewPositionSet(DXVector3(class="num">3.0,class="num">2.0,-class="num">5.0));    class=class="str">"cmt">// 将相机设置在立方体的右侧、顶部和前面
     m_canvas.ViewTargetSet(DXVector3(class="num">0,class="num">0,class="num">0));             class=class="str">"cmt">// 将凝视点方向设置在立方体的中心
     class=class="str">"cmt">//--- 刷新场景
     Redraw();
     class=class="str">"cmt">//--- 成功
     class="kw">return(true);
   }

「用矩阵同时转体和位移」

在 MT5 的 DirectX 封装里,物体的旋转与平移都走矩阵运算,而且两者可以分开算再乘到一起。上面这段把相机摆到坐标中心上方偏前(Y=2.0,Z=-5.0),看向原点,上方向锁死 Y 轴,避免画面歪掉。 立方体先沿 Yaw/Pitch/Roll 三个轴依次转:分别是 π/4、π/3、π/6,相当于 45°、60°、30°。之后用 DXMatrixTranslation 把它向右 1.0、向下 -2.0、向场景内 5.0 偏移。注意平移量是世界坐标,不是相对旋转后的本地轴。 旋转矩阵和位移矩阵通过 DXMatrixMultiply 求出最终 transform,再塞给 m_box.TransformMatrixSet。渲染出来你会看到蓝绿色方块既歪着身子又挪了位,相机视角不变。 别把正态当圣经:矩阵相乘顺序很关键,代码里是 rotation 乘 translation,意味着先转再移;反过来写,偏移方向会跟着旋转走,立方体可能飞出视野。开 MT5 把 Step5 跑一遍,改下那三个角度参数就能直观验证。

MQL5 / C++
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class=class="str">"cmt">//| 创建                                                                 |
  class=class="str">"cmt">//+------------------------------------------------------------------+
  class="kw">virtual class="type">bool      Create(class="kw">const class="type">int width,class="kw">const class="type">int height)
   {
  ...
     m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(class="type">float)m_width/m_height,class="num">0.1f,class="num">100.0f);
   class=class="str">"cmt">//--- 将相机放置在坐标中心的顶部和前面
   m_canvas.ViewPositionSet(DXVector3(class="num">0.0,class="num">2.0,-class="num">5.0));
   m_canvas.ViewTargetSet(DXVector3(class="num">0.0,class="num">0.0,class="num">0.0));
   m_canvas.ViewUpDirectionSet(DXVector3(class="num">0.0,class="num">1.0,class="num">0.0));    
   class=class="str">"cmt">//--- 创建立方体 - 将立方体的两个对角坐标、资源管理器、场景参数传递给它
   if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-class="num">1.0,-class="num">1.0,-class="num">1.0),DXVector3(class="num">1.0,class="num">1.0,class="num">1.0)))
     {
      m_canvas.Destroy();
      class="kw">return(false);
     }
   class=class="str">"cmt">//--- 设置立方体颜色
   m_box.DiffuseColorSet(DXColor(class="num">0.0,class="num">0.5,class="num">1.0,class="num">1.0));        
   class=class="str">"cmt">//--- 计算立方体位置和变换矩阵
   DXMatrix rotation,translation;
   class=class="str">"cmt">//--- 沿 X、Y和 Z 轴顺序旋转立方体
   DXMatrixRotationYawPitchRoll(rotation,(class="type">float)M_PI/class="num">4,(class="type">float)M_PI/class="num">3,(class="type">float)M_PI/class="num">6);
   class=class="str">"cmt">//-- 向右/向下/向内移动立方体
   DXMatrixTranslation(translation,class="num">1.0,-class="num">2.0,class="num">5.0);
   class=class="str">"cmt">//--- 取旋转和变化的乘积作为变换矩阵
   DXMatrix transform;
   DXMatrixMultiply(transform,rotation,translation);
   class=class="str">"cmt">//--- 设置变换矩阵
   m_box.TransformMatrixSet(transform);    
   class=class="str">"cmt">//--- 在场景里加入立方体
   m_canvas.ObjectAdd(&m_box);    
   class=class="str">"cmt">//--- 刷新场景
   Redraw();
   class=class="str">"cmt">//--- 成功
   class="kw">return(true);
   }

给三维场景打光:Phong 模型落地

要在 MT5 的 Canvas3D 里画出有体积感的物体,不能只给网格上色,还得算每个面的光照。Phong 着色把光拆成环境、漫反射、镜面三部分,场景级参数在 CCanvas3D 设,物体级参数在 CDXMesh 及其子类设。 定向光在 Canvas3D 里没有「位置」概念,只给传播方向,系统当它是无限远的平行光。上面代码里方向矢量是 (0.0, -1.0, 0.0),即沿 Y 轴负方向从上往下打。 一个容易踩的坑:只要调了 LightColorSet 和 LightDirectionSet,就必须显式写 AmbientColorSet。默认环境光是强度拉满的白色,不覆盖的话整个物体会被白光糊住,定向光的效果基本看不见。 示例里把立方体漫反射设白 (1,1,1,1)、自发光设淡绿 (0,1,0,0.2),黄光自上而下、蓝环境光强度 0.4,最终看到白立方体泛绿。代码在「Step6 Add Light.mq5」,屏蔽其中颜色方法逐个跑,能直接观察各分量权重。 外汇与贵金属行情可视化若套用此类 3D 渲染,需注意终端 GPU 负载波动可能影响盯盘延迟,属高风险辅助手段。

MQL5 / C++
    class=class="str">"cmt">//--- 将光源设置为黄色,并从上方照向下方定向
    m_canvas.LightColorSet(DXColor(class="num">1.0,class="num">1.0,class="num">0.0,class="num">0.8f));
    m_canvas.LightDirectionSet(DXVector3(class="num">0.0,-class="num">1.0,class="num">0.0));
    class=class="str">"cmt">//--- 设置环境光为蓝色 
    m_canvas.AmbientColorSet(DXColor(class="num">0.0,class="num">0.0,class="num">1.0,class="num">0.4f));    
    class=class="str">"cmt">//--- 创建立方体 - 将立方体的两个对角坐标、资源管理器、场景参数传递给它
    if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-class="num">1.0,-class="num">1.0,-class="num">1.0),DXVector3(class="num">1.0,class="num">1.0,class="num">1.0)))
      {
       m_canvas.Destroy();
       class="kw">return(false);
      }
    class=class="str">"cmt">//--- 设置立方体为白色
    m_box.DiffuseColorSet(DXColor(class="num">1.0,class="num">1.0,class="num">1.0,class="num">1.0)); 
    class=class="str">"cmt">//--- 为立方体添加绿色光辉(散射光)
    m_box.EmissionColorSet(DXColor(class="num">0.0,class="num">1.0,class="num">0.0,class="num">0.2f)); 
m_canvas.LightDirectionSet(DXVector3(class="num">0.0,-class="num">1.0,class="num">0.0));
    class=class="str">"cmt">//--- 将光源设置为黄色,并从上方照向下方定向
    m_canvas.LightColorSet(DXColor(class="num">1.0,class="num">1.0,class="num">0.0,class="num">0.8f));
    m_canvas.LightDirectionSet(DXVector3(class="num">0.0,-class="num">1.0,class="num">0.0));
    class=class="str">"cmt">//--- 设置环境光为蓝色 
    m_canvas.AmbientColorSet(DXColor(class="num">0.0,class="num">0.0,class="num">1.0,class="num">0.4f));  class=class="str">"cmt">// must be specified

◍ 用 10 毫秒计时器驱动 3D 场景刷新

在 MT5 里做 3D 动画,核心是把场景参数和物体属性挂到时间流上。把计时器设成 10 毫秒一跳,OnTimer 就成了场景刷新的心跳,任何可改属性——旋转、位移、缩放、光照方向——都能按帧重算。 物体变换每次都从初始态重算,不会保留上一帧的累积状态。代码里 m_box.TransformMatrixSet 接收的 transform,是由 scale、rotation、translation 三个矩阵当场相乘得到的,相当于每一帧都把立方体拉回草创状态再施加全部操作。 光源方向则是另一套逻辑:从当前值起步,按 deltatime 增量旋转。OnTimer 里用 GetMicrosecondCount 算微秒差再除 1000000.0 得到秒级 deltatime,且把单帧增量钳在 0.1 秒上限,避免卡顿后跳变。 下面这段初始化与计时器逻辑可直接拷进 EA 验证:EventSetMillisecondTimer(10) 拉起心跳,OnTimer 内用 DXMatrixRotationYawPitchRoll(rotation,time/11.0f,time/7.0f,time/5.0f) 让立方体偏航、俯仰、翻滚以不同周期耦合,视觉上就是持续变轨的旋转体。外汇与贵金属行情外接此类可视化时须留意,图形端资源占用可能拖累报价响应,属高风险辅助手段。

MQL5 / C++
class="type">int OnInit()
  {
...
class=class="str">"cmt">//--- 创建画布
   ExtAppWindow=new CCanvas3DWindow();
   if(!ExtAppWindow.Create(width,height))
      class="kw">return(INIT_FAILED);
class=class="str">"cmt">//--- 设置计时器
   EventSetMillisecondTimer(class="num">10);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
   }
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class=class="str">"cmt">//| 计时器响应                                                            |
   class=class="str">"cmt">//+------------------------------------------------------------------+
   class="type">void         OnTimer(class="type">void)
    {    
      class="kw">static class="type">class="kw">ulong last_time=class="num">0;
      class="kw">static class="type">float time=class="num">0;      
      class=class="str">"cmt">//--- 获取当前时间
      class="type">class="kw">ulong current_time=GetMicrosecondCount();
      class=class="str">"cmt">//--- 计算增量
      class="type">float deltatime=(current_time-last_time)/class="num">1000000.0f;
      if(deltatime>class="num">0.1f)
         deltatime=class="num">0.1f;
      class=class="str">"cmt">//--- 流逝时间值递增
      time+=deltime;
      class=class="str">"cmt">//--- 记住时间
      last_time=current_time;
      class=class="str">"cmt">//--- 计算立方体位置和旋转矩阵
      DXMatrix rotation,translation,scale;
      DXMatrixRotationYawPitchRoll(rotation,time/class="num">11.0f,time/class="num">7.0f,time/class="num">5.0f);
      DXMatrixTranslation(translation,(class="type">float)sin(time/class="num">3),class="num">0.0,class="num">0.0);
      class=class="str">"cmt">//--- 沿坐标轴计算立方体的压缩/扩展
      DXMatrixScaling(scale,class="num">1.0f+class="num">0.5f*(class="type">float)sin(time/class="num">1.3f),class="num">1.0f+class="num">0.5f*(class="type">float)sin(time/class="num">1.7f),class="num">1.0f+class="num">0.5f*(class="type">float)sin(time/class="num">1.9f));
      class=class="str">"cmt">//--- 将矩阵相乘从而获得最终变换
      DXMatrix transform;
      DXMatrixMultiply(transform,scale,rotation);
      DXMatrixMultiply(transform,transform,translation);
      class=class="str">"cmt">//--- 设置变换矩阵
      m_box.TransformMatrixSet(transform);
      class=class="str">"cmt">//--- 计算光源绕 Z 轴的旋转
      DXMatrixRotationZ(rotation,deltatime);
      DXVector3 light_direction;
      class=class="str">"cmt">//--- 获取光源的当前方向
      m_canvas.LightDirectionGet(light_direction);
      class=class="str">"cmt">//--- 计算并设置光源的新方向
      DXVec3TransformCoord(light_direction,light_direction,rotation);
      m_canvas.LightDirectionSet(light_direction);
      class=class="str">"cmt">//--- 重新计算 3D 场景,并将其绘制在画布中
      Redraw();
    }
让小布替你验证渲染环境
小布盯盘已内置对终端 DX11 与 Shader Model 5.0 的兼容性诊断,打开对应品种页即可看到当前环境能否承载这类 3D 绘制,省去你手动查显卡参数的功夫。

常见问题

通常包含眼睛位置、观察目标点和上方向向量,三者构成视图矩阵,决定场景如何从特定视角投影到二维屏幕。
绕 Z 轴是固定世界坐标变换,绕视角则依赖当前相机矩阵,需要把旋转叠加在视图变换之后,否则会出现轴向漂移。
目前小布提供环境检测与诊断,自定义指标的编译和运行仍需在 MT5 内完成,但可借小布快速确认客户端是否满足图形接口前提。
因为建模时三角形数量固定,缩放后单个切面被拉伸,拓扑密度不足就会丢失细节,只能靠提高细分或法线贴图缓解。
主要看顶点数量、光照计算复杂度和显卡吞吐,FX 贵金属类实时行情叠加绘制时更需注意终端资源占用,高波动时段可能掉帧。