DirectX 教程(第一部分):绘制第一个三角形·综合运用
🔺

DirectX 教程(第一部分):绘制第一个三角形·综合运用

(3/3)·从 API 历史到 MQL 脚本落地,把前两部分的概念一次性跑成屏上图元

含代码示例偏理论 第 3/3 篇
很多交易者以为 MQL 里调个绘图函数就能出图,结果卡在设备关联和输入布局上白白耗掉一下午。DirectX 被 MetaQuotes 封装后看似简单,但不知互换链条和图元拓扑,写出来的脚本大概率黑屏或崩溃。先把初始化仪式摸清,再碰 HLSL 才算踩在地面上。

在 MT5 里把 DirectX 渲染管线跑通

想在 MT5 自定义指标或 EA 里画自己的图形,得先让 DirectX 上下文和着色器就位。下面这段初始化逻辑就是典型做法:先建上下文,再分别编译顶点和像素着色器,任何一步拿到 INVALID_HANDLE 就直接打印错误并退出,避免后续渲染调用全崩。 顶点布局只定义了一个 POSITION 语义、格式为 R32G32B32A32_FLOAT,说明每个顶点带 4 个 32 位浮点(xyzw)。图元拓扑设成 TRIANGLELIST,也就是用三角形列表来拼几何,不是线段或点。 Draw() 里第一件事是用 DXVector{1.0f,0.0f,0.0f,0.5f} 清屏——红色、alpha 0.5 的半透明底。若 DXContextClearColors 返回 false,同样打印错误并 return false,保证帧开头状态干净。 直接把下面代码贴进你的 DXTutorial 类方法里,在 MT5 策略测试器开可视化跑一遍,看终端有没有 'failed to create' 这类红字,就能确认显卡驱动和 DX 封装是否可用。

MQL5 / C++
m_dx_pixel_shader = DXShaderCreate(m_dx_context, DX_SHADER_PIXEL, shader, "PShader", shader_error);
if(m_dx_pixel_shader == INVALID_HANDLE)
  {
    Print("Error: failed to create pixel shader: ", GetLastError());
    Print("Shader compilation error: ", shader_error);
    class="kw">return(false);
  }
m_dx_buffer = DXBufferCreate(m_dx_context, DX_BUFFER_VERTEX, vertex);
if(m_dx_buffer == INVALID_HANDLE)
  {
    Print("Error: failed to create vertex buffer: ", GetLastError());
    class="kw">return(false);
  }
if(!DXShaderSetLayout(m_dx_vertex_shader, layout))
  {
    Print("Error: failed to set vertex layout: ", GetLastError());
    class="kw">return(false);
  }
if(!DXPrimiveTopologySet(m_dx_context, DX_PRIMITIVE_TOPOLOGY_TRIANGLELIST))
  {
    Print("Error: failed to set primitive type: ", GetLastError());
    class="kw">return(false);
  }
if(!DXShaderSet(m_dx_context, m_dx_vertex_shader))
  {
    Print("Error, failed to set vertex shader: ", GetLastError());
    class="kw">return(false);
  }
if(!DXShaderSet(m_dx_context, m_dx_pixel_shader))
  {
    Print("Error: failed to set pixel shader: ", GetLastError());
    class="kw">return(false);
  }
if(!DXBufferSet(m_dx_context, m_dx_buffer))
  {
    Print("Error: failed to set buffer to render: ", GetLastError());
    class="kw">return(false);
  }
class="kw">return(true);
}
DXVertexLayout layout[class="num">1] = {{"POSITION", class="num">0, DX_FORMAT_R32G32B32A32_FLOAT }};
class="type">class="kw">string shader_error = "";
m_dx_context = DXContextCreate(m_width, m_height);
m_dx_vertex_shader = DXShaderCreate(m_dx_context, DX_SHADER_VERTEX, shader, "VShader", shader_error);
m_dx_pixel_shader = DXShaderCreate(m_dx_context, DX_SHADER_PIXEL, shader, "PShader", shader_error);
m_dx_buffer = DXBufferCreate(m_dx_context, DX_BUFFER_VERTEX, vertex);
DXShaderSetLayout(m_dx_vertex_shader, layout)
DXPrimiveTopologySet(m_dx_context, DX_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
DXShaderSet(m_dx_context, m_dx_vertex_shader)
DXShaderSet(m_dx_context, m_dx_pixel_shader)
DXBufferSet(m_dx_context, m_dx_buffer)
class="type">bool DXTutorial::Draw()
  {
    DXVector dx_color{class="num">1.0f, class="num">0.0f, class="num">0.0f, class="num">0.5f};
    if(!DXContextClearColors(m_dx_context, dx_color))
      {
        Print("Error: failed to clear the class="type">color buffer: ", GetLastError());
        class="kw">return(false);
      }

◍ DirectX 离屏渲染与资源回收的调用链

在 MT5 里用 DirectX 做离屏图形,核心是一串不能跳步的上下文调用。先清深度缓冲,再画顶点,接着从上下文抓图像到 m_image,最后用 ResourceCreate 把图像塞进可绘制资源;任何一步返回 false 都直接退出,避免半成品资源污染后续帧。 if(!DXContextClearDepth(m_dx_context)) { Print("Error: failed to clear the depth buffer: ", GetLastError()); return(false); } if(!DXDraw(m_dx_context)) { Print("Error: failed to draw vertices of the vertex buffer: ", GetLastError()); return(false); } if(!DXContextGetColors(m_dx_context, m_image)) { Print("Error: unable to get image from the graphics context: ", GetLastError()); return(false); } if(!ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Error: failed to create a resource to draw"); return(false); } return(true); } 上面这段逐行看:第 1 行清深度缓冲,失败就打印错误码并撤出;第 6 行 DXDraw 负责把顶点缓冲画进上下文,外汇或贵金属图表高频重绘时这里最容易因句柄失效报错;第 11 行取回图像像素,第 16 行用 ARGB 归一化格式建资源。 清屏时可用 DXVector 设半透明红:dx_color{1.0f, 0.0f, 0.0f, 0.5f} 表示 RGBA 中红满、透明 0.5,配合 DXContextClearColors 与 ClearDepth 能做出叠加层效果。 Deinit 里必须反向释放。像素着色器、顶点着色器、顶点缓冲、图形上下文都靠 DXRelease 逐个释放,m_dx_buffer 等句柄大于 0 才调用,否则可能误删系统默认对象;最后 ResourceFree 与 ObjectDelete 清掉自定义资源和画布对象。 void DXTutorial::Deinit() { if(m_dx_pixel_shader > 0 && !DXRelease(m_dx_pixel_shader)) { Print("Error: failed to release the pixel shader handle: ", GetLastError()); } if(m_dx_vertex_shader > 0 && !DXRelease(m_dx_vertex_shader)) { Print("Error: failed to release the vertex shader handle: ", GetLastError()); } if(m_dx_buffer > 0 && !DXRelease(m_dx_buffer)) { Print("Error: failed to release the vertex buffer handle: ", GetLastError()); } if(m_dx_context > 0 && !DXRelease(m_dx_context)) { Print("Error: failed to release the graphics context handle: ", GetLastError()); } if(!ResourceFree(m_resource)) { Print("Error: failed to delete the graphics resource"); } if(!ObjectDelete(0, m_canvas)) { Print("Error: failed to delete graphical object"); } } 着色器源码以字符串拼入。VShader 接收 POSITION 语义的 float4 并原样输出 SV_POSITION,这是最小可用顶点着色器骨架,后续接像素着色器才能出图。外汇贵金属行情波动剧烈,此类自定义图形模块若释放不干净,可能导致终端显存泄漏,实盘前请在模拟环境验证。

MQL5 / C++
if(!DXContextClearDepth(m_dx_context))
  {
  Print("Error: failed to clear the depth buffer: ", GetLastError());
  class="kw">return(false);
  }
if(!DXDraw(m_dx_context))
  {
  Print("Error: failed to draw vertices of the vertex buffer: ", GetLastError());
  class="kw">return(false);
  }
if(!DXContextGetColors(m_dx_context, m_image))
  {
  Print("Error: unable to get image from the graphics context: ", GetLastError());
  class="kw">return(false);
  }
if(!ResourceCreate(m_resource, m_image, m_width, m_height, class="num">0, class="num">0, m_width, COLOR_FORMAT_ARGB_NORMALIZE))
  {
  Print("Error: failed to create a resource to draw");
  class="kw">return(false);
  }
class="kw">return(true);
}
DXVector dx_color{class="num">1.0f, class="num">0.0f, class="num">0.0f, class="num">0.5f};
DXContextClearColors(m_dx_context, dx_color)
DXContextClearDepth(m_dx_context)
DXDraw(m_dx_context)
DXContextGetColors(m_dx_context, m_image)
ResourceCreate(m_resource, m_image, m_width, m_height, class="num">0, class="num">0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)
class="type">void DXTutorial::Deinit()
  {
  if(m_dx_pixel_shader > class="num">0 && !DXRelease(m_dx_pixel_shader))
    {
    Print("Error: failed to release the pixel shader handle: ", GetLastError());
    }
  if(m_dx_vertex_shader > class="num">0 && !DXRelease(m_dx_vertex_shader))
    {
    Print("Error: failed to release the vertex shader handle: ", GetLastError());
    }
  if(m_dx_buffer > class="num">0 && !DXRelease(m_dx_buffer))
    {
    Print("Error: failed to release the vertex buffer handle: ", GetLastError());
    }
  if(m_dx_context > class="num">0 && !DXRelease(m_dx_context))
    {
    Print("Error: failed to release the graphics context handle: ", GetLastError());
    }
  if(!ResourceFree(m_resource))
    {
    Print("Error: failed to class="kw">delete the graphics resource");
    }
  if(!ObjectDelete(class="num">0, m_canvas))
    {
    Print("Error: failed to class="kw">delete graphical object");
    }
  }
class="type">class="kw">string shader = "float4 VShader( float4 Pos : POSITION ) : SV_POSITION \r
"
                "  {                                                                      \r
"

「顶点着色与像素着色的返回逻辑」

这段 HLSL 片段定义了两个着色器入口:VShader 把传入的顶点位置原样返回,PShader 则无视输入,直接输出 rgba=(0.0,1.0,0.0,1.0) 的纯绿不透明像素。也就是说,无论几何体落在屏幕哪个坐标,最终着色结果都是固定绿色,常用于最小可运行渲染管线的基线验证。 OnStart 里先声明一个含 3 个 float4 的顶点数组,分别取 (-0.5,-0.5)、(0.0,0.5)、(0.5,-0.5) 的归一化设备坐标,构成一个居中三角形。DXTutorial 对象初始化后,若失败立即 return,成功则两次 ChartRedraw 之间各 Sleep(1000) 毫秒,给渲染一秒的观察窗口。 在 MT5 里跑这套代码,你能直接看到图表上浮现一个绿色三角形;若把 PShader 的 1.0f 绿通道改成 0.0f、红通道改 1.0f,三角形会立刻变红,这就是着色器热改色的直观验证点。外汇与贵金属图表叠加自定义 DX 绘制属高风险实验,建议先用模拟环境确认资源释放无误。

MQL5 / C++
  class="kw">return Pos;  
  }  
  
float4 PShader( float4 Pos : SV_POSITION ) : SV_TARGET 
  {  
   class="kw">return float4( class="num">0.0f, class="num">1.0f, class="num">0.0f, class="num">1.0f );  
  }  
class="type">void OnStart()
  {
   float4 vertex[class="num">3] = {{-class="num">0.5f, -class="num">0.5f, class="num">0.0f, class="num">1.0f}, {class="num">0.0f, class="num">0.5f, class="num">0.0f, class="num">1.0f}, {class="num">0.5f, -class="num">0.5f, class="num">0.0f, class="num">1.0f}};
   DXTutorial dx;
   if(!dx.Init(vertex)) class="kw">return;
   ChartRedraw();
   Sleep(class="num">1000);
   if(!dx.Draw()) class="kw">return;
   ChartRedraw();
   Sleep(class="num">1000);
  }

三角形之后的 DirectX 进阶路口

第一个三角形跑通只是起点。MQL5 里借助 DirectX 完成顶点到像素的管线映射后,真正能拉开分析界面差距的还在后面。 除了已经用到的顶点缓冲,后续要把索引、常量、纹理等数据分别绑定到不同管线阶段,这一步直接决定你能在 MT5 图表上叠加多复杂的自定义图层。 HLSL 着色器语言是绕不开的下一关。用它能写顶点变换和像素着色逻辑,矩阵运算、法线计算、特殊滤镜效果都靠它实现,外汇和贵金属高频盯盘时自绘热力带会用到这类手段,但图形 API 调试成本高、显存占用波动大,属于高风险定制开发。 建议直接在 MT5 脚本里把本文的三角形示例改成带纹理的四边形,先感受数据绑定差异,再碰着色器。

◍ 最后说句实在话

这套在 MT5 里用 DirectX 接管图表渲染的思路,从背景贴图到独立拖拽窗体对象,实操门槛比普通指标高,但扩展空间也明显更大。俄文原帖下 2023–2024 年的几条留言显示,有读者在强制 WARP 模式下跑通了基础示例,也有人催更纹理与变换矩阵的第二部分,作者回称暂无需求。 外汇和贵金属市场本身高杠杆、高风险,这类图形化方案只解决「看得更清楚」,不解决「看得更准」,用它辅助判别形态可以,别当成信号源。 真要动手,先从文内提到的 DoEasy 图形第九十七部分找鼠标拖拽窗体的最小可用代码,在策略测试器外挂一个背景图验证渲染管线,比直接抄完整库更省时间。

把重复劳动交给小布
这些 DirectX 初始化与资源释放的样板代码,小布盯盘的 AIGC 辅助已内置常用模板,打开对应品种页即可调用,你只需改顶点数组和着色器逻辑。

常见问题

需要至少最简顶点着色器和像素着色器,MQL 侧负责设备、画布与资源,HLSL 决定图元如何映射到屏幕。
目前小布内置的是常见初始化与释放片段,完整三角形示例仍需按本文结构拼装,但重复声明部分可由它代劳。
它管理后台缓冲与前台显示的交替,MQL 中由 CreateCanvas 与图像显示步骤隐式处理,理解原理才能排查闪烁。
输入布局告诉管道每个顶点字段的偏移与格式,错配会导致读取越界,屏幕出现畸变或空图。