如何在 MetaTrader 5 中利用 DirectX 创建 3D 图形·进阶篇
(2/3)· 显卡跑得动 DX11 却画不出立体感?本篇把旋转、着色与动画的底层逻辑一次讲透
很多交易者以为在 MT5 里调出 3D 图形只是换个指标样式,其实从顶点法线到相机矩阵任一步算错,屏幕上的物体就会扭曲或消失。把 DirectX 当成普通画图函数用,往往卡在光照和旋转联动上出不来图。
「相机三参必须一起初始化」
在 CCanvas3D 里操控 3D 场景,视点、凝视点、顶部方向这三个量是绑死的。ViewPositionSet 管相机放哪,ViewTargetSet 管看向哪,ViewUpDirectionSet 管画面顶边朝哪倾;只要动其中一个,另外两个不赋值,场景生成阶段就会缺参数跑偏。 原文示例把文件存成 “Step3 ViewUpDirectionSet.mq5” 运行后,立方体本身没动,但因为相机顶部方向在左右摆,看起来就像静物在转。这效果靠在 Create() 里补三行初始化,再让 OnTimer() 驱动顶点水平摆动实现。 别把三个方法当独立开关 很多新手只设 ViewPositionSet 就等画面出来,结果黑屏或畸变。目标点坐标 (0,0,6) 和顶部 (0,1,0) 不写,相机姿态就是未定义状态。 下面代码里三行高亮的就是最小可用组合:视线从坐标原点出发,盯着 Z=6 的立方体中心,顶边沿 Y 轴。复制进你自己的 Create() 就能直接验证。
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 辅助盯盘面板时,这种轻量动画能避免静态视角带来的空间误判。外汇与贵金属波动剧烈,任何可视化只是辅助,下单前仍以价格行为本身为准。
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 轴转的蓝立方体右上视图。外汇与贵金属图表外做这类图形验证同样涉及软件环境风险,参数不对可能黑屏,先小窗测试。
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 跑一遍,改下那三个角度参数就能直观验证。
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 负载波动可能影响盯盘延迟,属高风险辅助手段。
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) 让立方体偏航、俯仰、翻滚以不同周期耦合,视觉上就是持续变轨的旋转体。外汇与贵金属行情外接此类可视化时须留意,图形端资源占用可能拖累报价响应,属高风险辅助手段。
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(); }