DirectX 教程(第一部分):绘制第一个三角形
内容目录
概述
当您搞明白用 DirectX 绘制一个图元三角形时,需要编写大量代码并填充巨大的 C++ 结构,您就会想到初始化仪式。 不用提更复杂的东西了,比如纹理、变换矩阵和阴影。 幸运的是,MetaQuotes 解决了这个问题:它们隐藏了整个例程,而只留下了最必要的函数。 然而,它也有一个副作用:任何之前不熟悉 DirectX 的人都无法看到整个画面,也无法理解这一切发生的原因和方式。 而且依然还要编写很多 MQL 代码。
由于不了解引擎盖下的内容,DirectX 令人感到无从下手:“为什么它如此困难和令人困惑,难道不能让它变得更简单吗?”....然而这只是第一阶段。 此外,您还要学习 HLSL 着色器语言,和视频卡编程的特性。 为了避免所有这些混淆,我建议研究 DirectX 的内部结构,但务须太深入。 然后我们将用 MQL 编写一个小脚本,在屏幕上显示一个三角形。
DirectX API
DirectX 的历史
DirectX 是一套 API(应用程序编程接口),在微软平台上处理多媒体和视频。 它最初是为制作游戏而开发的,但随着时间的推移,开发人员开始在工程和数学软件中使用它。 DirectX 能够操控图形、声音、输入和网络,而无需访问底层函数。 该 API 是作为跨平台 OpenGL 的替代品出现的。 在创建 Windows 95 时,微软实施了一些质的改变,这令开发应用程序和游戏的环境更加困难,因此可能会影响该操作系统的普及。 DirectX 是为了让更多程序员为 Windows 开发游戏而创建的解决方案。 开发 DirectX 是由 Craig Eisler、Alex St.John 和 Eric Engstrom 发起的。
- 1995 年 9 月。 首次发布。 这是一个相当原始的版本,作为 Windows API 的附加组件运行。 它并未引起太多关注。 开发人员大多使用 DOS,相比之下,新操作系统对系统的需求更高。 此外,那时 OpenGL 已经存在。 且尚不清楚微软是否会继续支持 DirectX。
- 1996 年 6 月。 版本 2 发布了。
- 1996 年 9 月。 版本 3。
- 1997 年 8 月。 微软发布了第五版,而不是第四版。 使用这个版本编写代码变得更加容易,程序员开始关注它。
- 1998 年 8 月。 版本 6。 操作进一步简化了。
- 1999 年 9 月。 版本 7。 在显卡内存中可以创建顶点缓冲区,这原本是 OpenGL 的一大优势。
- 2000 年 11 月。 版本 8。 关键时刻。 在此之前,DirectX 一直在努力追赶,但从第八版开始超越了业界。 微软开始与图形卡制造商合作。 顶点和像素着色器出现。 开发只需要一台个人电脑,不像 OpenGL 需要一台工作站。
- 2002 年 12 月。 版本 9。 DirectX 已成为行业标准。 HLSL 着色器语言出现。 这可能是 DirectX 最长寿的版本。 比如 775 插座...
- 2006 年 11 月。 版本 10。 与版本 9 不同,这一版本与 Vista 操作系统绑定,然而后者并不流行。 这些因素直接对版本 10 的成功产生了负面影响。 微软添加了一个几何体着色器。
- 2009 年 10 月。 版本 11。 添加了曲面细分、计算着色器,改进了多核处理器的操作。
- 2015 年 7 月。 版本 12。 底层 API。 该版本提供了与多核处理器更好的兼容性,能够组合来自不同供应商的多个视频卡的资源,光线跟踪。
Direct3D
Direct3D 是庞大的 DirectX API 的许多组件之一;它负责图形,是应用程序和图形卡驱动程序之间的中介。 Direct3D 基于 COM(组件对象模型)。 COM 是微软在 1993 年推出的应用程序二进制接口(ABI)标准。 它在各种编程语言的进程间通信(IPC)中创建对象。 COM 作为一种解决方案出现,旨在提供一种独立于语言的方式来实现可在其创建环境之外使用的对象。 COM 允许对象在不知道其内部实现的情况下被重用,因为它们提供了定义良好的接口,与具体实现分离。 COM 对象使用引用计数来自行负责创建和销毁。
接口
设备
Direct3D 中的一切都是从设备开始的。 它用于创建资源(缓冲区、纹理、着色器、状态对象)和图形适配器列举的功能。 设备是位于用户系统上的虚拟适配器。 适配器可以是真正的视频卡,也可以是其软件仿真。 硬件设备使用最频繁是因为它们能提供最高的性能。 该设备为所有这些适配器提供了一个统一的接口,并使用它们渲染图形,并产生一个或多个输出。
设备
设备关联
设备关联负责与渲染相关的任何内容。 这包括管道配置和渲染命令的创建。 设备关联出现在版本 11 的 DirectX 当中 — 最初渲染是由设备实现的。 关联有两种类型:即时和延迟关联。
即时关联提供显卡数据的访问,以及在设备上立即执行命令列表的能力。 每个设备只有一个即时关联。 一次只能有一个线程访问它。 对于多线程的访问,应启用同步来访问。
延迟关联将命令添加到命令列表中,以便稍后在即时关联中执行。 因此,所有命令最终都会经由直接关联执行。 延迟关联涉及一些开销,因此只有在并行化资源密集型任务时,才能体现出使用它的好处。 您可以创建多个延迟关联,并从单独的线程访问每个关联。 但是,若要从多线程访问相同的延迟关联,就需要同步,就像直接关联一样。
互换链条
互换链条旨在创建一个或多个后台缓冲区。 这些缓冲区存储渲染图像,直到它们显示在屏幕上。 前景、后台缓冲器的工作原理如下。 前景缓冲区就是您在屏幕上看到的。 后台缓冲区是需要渲染的图像。 然后,缓冲区被互换:前景变为后台,后台变为前景。 整个过程重复翻转。 因此,我们一直看到的画面,其实每下一幅画面都是在“幕后”渲染的。
互换链条
设备、设备关联和互换链条是渲染图像所需的主要组件。
输入布局
输入布局通知管道顶点缓冲区的结构。 我们只需要坐标,这就是为什么我们可以简单地传递 float4 类型的顶角数组,而无需使用特殊的结构。 float4 是由四个 float 变量组成的结构。
struct float4 { float x; float y; float z; float w; };
例如,考虑由一个坐标和两种颜色组成的更复杂的顶点结构:
struct Vertex
{
float4 Pos;
float4 Color0;
float4 Color1;
};
该结构的 MQL 输入布局如下所示:
DXVertexLayout layout[3] = {{"POSITION", 0, DX_FORMAT_R32G32B32A32_FLOAT}, {"COLOR", 0, DX_FORMAT_R32G32B32A32_FLOAT}, {"COLOR", 1, DX_FORMAT_R32G32B32A32_FLOAT}};
“layout” 数组的每个元素定义顶点结构的对应元素。
- DXVertexLayout 结构的第一个元素是语义名称。 它将顶点结构的元素映射到顶点着色器中的结构元素。 “POSITION” 表示该值负责坐标,“COLOR” 用于颜色。
- 第二个元素是语义索引。 如果我们需要传递多个相同类型的参数,例如,两个颜色值,第一个对应索引 0 一起传递,第二个对应索引 1 一起传递。
- 数值结构中的最后一个表示顶点的类型。 DX_FORMAT_R32G32B32A32_FLOAT 字面意思它是一种 RGBA 颜色,每个组件由 32 位浮点值表示。 这可能会令人困惑。 这种类型可用来传递坐标 — 它提供关于四个 32 位浮点值的信息,就像顶点结构中的 float4 一样。
图元拓扑
顶点缓冲区存储有关图像点的信息,但我们不知道图元中它们的相对位置。 这就是图元拓扑的用途。 图形点列表(Point List)意味着缓冲区存储独立的图形点。 线条带(Line Strip)将缓冲区表示为形成多段线的连接点。 线条带中的每两个图形点定义在一行里。 三角形带(Triangle Strip)和三角形列表(Triangle List)设置三角形的顶点顺序,类似于直线。
拓扑
HLSL
高级着色语言(HLSL)是一种用于编写着色器的类 C 语言。 着色器,原本是设计用在图形卡上运行的程序。 所有 GPGPU 语言的编程都非常相似,并且拥有与图形卡设计相关的特定功能。 如果您有开发 OpenCL、Cuda 或 OpenGL 的经验,您会很快理解 HLSL。 但如果您只为 CPU 创建程序,那么可能很难切换到新的范例。 通常,用于处理器的传统优化方法不起作用。 例如,处理器使用 “if” 语句来避免不必要的计算、或选择最佳算法,这是正确的。 但在 GPU 上,与此相对,这会增加程序的执行时间。 为了达到最大值,您可能需要计算相关寄存器的数量。 图形卡编程时,提高性能的三个主要原则是:并行性、吞吐量和占用率。
图形管道
管道设计用于将 3D 场景转换为 2D 显示模式。 管道反映了显卡的内部结构。 下图显示了数据流如何贯穿所有阶段从管道输入直至输出流动。 椭圆形展示了使用 HLSL 语言编程的阶段 — 着色器;而矩形展示了固有阶段。 其中一些是可选的,可以轻松跳过。
图形管道
-
输入汇总(Input Assembler)阶段从顶点和索引缓冲区接收数据,并为顶点着色器准备数据。
- 顶点着色器(Vertex Shader)阶段针对顶点执行操作。 可编程(Programmable)阶段。 管道强制性。
- 外壳着色器(Hull Shader)阶段负责曲面细分级别。 可编程(Programmable)阶段。 选项。
- 曲面细分器(Tessellator)阶段创建较小的图元。 固化(Fixed)阶段。 选项。
- 域着色器(Domain Shader)阶段计算曲面细分后的最终顶点值。 可编程(Programmable)阶段。 选项。
- 几何体(Geometry)着色器阶段将各种变换应用于图元(点、线、三角形)。 可编程(Programmable)阶段。 选项。
- 流输出(Stream Output)阶段将数据传输到 GPU 内存,然后将数据发送回管道。 固化(Fixed)阶段。 选项。
- 光栅化(Rasterizer)阶段会切断所有不在范围内的内容,为像素着色器准备数据。 固化(Fixed)阶段。
-
像素着色器(Pixel Shader)阶段执行像素操作。 可编程(Programmable)阶段。 管道强制性。
- 输出合并(Output Merger)阶段形成最终图像。 固化(Fixed)阶段。
另一个值得一提的着色器是计算着色器(DirectCompute),它是一个单独的管道。 该着色器设计用于通用计算,类似于 OpenCL 和 Cuda。 可编程(Programmable)阶段。 选项。
MetaQuotes 真对 DirectX 的实现不包括 DirectCompute 和曲面细分阶段。 因此,我们只有三个着色器:顶点、几何体和像素。
3D 图形
图元
渲染图元是图形 API 的主要目的。 现代显卡适用于快速渲染大量三角形。 实际上,在计算机图形学的当前发展阶段,绘制 3D 对象最有效的方法是以多边形创建曲面。 曲面可由三个指定的点来定义。 3D 建模软件通常使用矩形,但图形卡仍会强制把多边形转换成三角形。
三角形网格
顶点
在 Direct3D 中必须指定三个顶点才能渲染一个三角形。 顶点似乎是一个点在空间中的位置,但在 Direct3D 当中,它远不止于此。 除了顶点位置,我们还可以传递颜色、纹理坐标和法线。 通常,矩阵变换通常用于规范化坐标。 为了避免此刻令其复杂化,请考虑以下事实:在光栅化阶段,沿 X 和 Y 轴的顶点坐标必须在 [-1;1] 范围内;沿 Z 轴 - 从 0 到 1。
颜色
计算机图形学中的颜色有三个组成部分:红色、绿色和蓝色。 这与人眼的结构特征有关。 显示的像素也由这三个颜色的子像素组成。 MQL 有 ColorToARGB 函数,可将 web 颜色转换为 ARGB 格式,该格式除存储颜色之外,还存储了透明度。 当组件在 [0;1] 范围内时,颜色可以规范化,也可以非规范化:例如,32 位颜色的组件可有 0 至 255 (2^8-1) 的值。 大多数现代显示器采用 32 位彩色。
MQL 中的动作序列
为了在 MQL 中使用 DirectX 显示图像,需要执行以下操作:
- 使用 ObjectCreate 创建位图标签或位图对象。
- 使用 ResourceCreate 创建动态图形资源。
- 使用 ObjectSetString,并带 OBJPROP_BMPFILE 参数,将资源与对象链接。
- 为着色器创建文件(或将着色器保存到字符串变量)。
- 在 HLSL 中编写顶点和像素着色器。
- 使用 #resource "FileName.hlsl" 作为字符串 variable_name; 连接着色器文件
- 以 DXVertexLayout 类型数组描述顶点的格式
- 创建关联 — DXContextCreate。
- 创建顶点着色器 — DXShaderCreate 参数是 DX_SHADER_VERTEX。
- 创建像素着色器 — DXShaderCreate 参数是 DX_SHADER_PIXEL。
- 创建顶点缓冲区 — DXBufferCreate 参数是 DX_BUFFER_VERTEX。
- 如果需要,创建索引缓冲区 — DXBufferCreate 参数是 DX_BUFFER_INDEX。
- 传递顶点格式 — DXShaderSetLayout。
- 设置图元的拓扑 — DXPrimiveTopologySet。
- 绑定顶点和像素着色器 — DXShaderSet。
- 绑定顶点(和索引,如果有)缓冲区 — DXBufferSet。
- 清除深度缓冲区 — DXContextClearDepth。
- 如有必要,清除颜色缓冲区 — DXContextClearColors
- 发送渲染命令 — DXDraw (或 DXDrawIndexed,如果指定了索引缓冲区)
- 将结果传递到图形资源 — DXContextGetColors
- 更新图形资源 — ResourceCreate
- 别忘了刷新图表 — ChartRedraw
- 使用后清理干净 — DXRelease
- 删除图形资源 — ResourceFree
- 删除图形对象 — ObjectDelete
您还和我在一起吗? 事实上,一切都比看上去更容易。 您以后会看到的。
实践
类概览
使用 DirectX 的整个过程可以分为几个阶段:创建画布、初始化设备、编写顶点和像素着色器、在屏幕上显示生成的图像、释放资源。 该类将如下所示:
class DXTutorial { private: int m_width; int m_height; uint m_image[]; string m_canvas; string m_resource; int m_dx_context; int m_dx_vertex_shader; int m_dx_pixel_shader; int m_dx_buffer; bool InitCanvas(); bool InitDevice(float4 &vertex[]); void Deinit(); public: void DXTutorial() { m_dx_context = 0; m_dx_vertex_shader = 0; m_dx_pixel_shader = 0; m_dx_buffer = 0; } void ~DXTutorial() { Deinit(); } bool Init(float4 &vertex[], int width, int height); bool Draw(); };
私密成员:
- m_width 和 m_height — 画布的宽度和高度。 这些成员在创建位图标签对象、动态图形资源和图形上下文时会用到。 它们的值是在初始化期间设置的,但也可以手工设置它们的值。
- m_image — 创建图形资源时使用的数组。 DirectX 操作结果会被传递给它。
- m_canvas — 图形对象的名称,m_resource — 图形资源的名称。 在初始化和去初始化期间使用。
- m_dx_context — 最重要的那个,是图形上下文句柄。 参与所有 DirectX 操作。 它在创建图形上下文时被初始化。
- m_dx_vertex_shader — 顶点着色器的句柄。 它在设置顶点标记、绑定图形上下文、逆初始化时均会被用到。 在编译顶点着色器时初始化。
- m_dx_pixel_shader — 像素着色器句柄。 它在绑定图形上下文和逆初始化时会被用到。 在编译像素着色器时初始化。
- m_dx_buffer — 顶点缓冲区句柄。 它在绑定图形上下文和逆初始化时会被用到。 它在创建顶点缓冲区时初始化。
初始化和逆初始化方法:
- InitCanvas() — 创建画布来显示图像。 使用位图标签对象和动态图形资源。 背景以黑色填充。 返回操作进度。
- InitDevice() — 初始化 DirectX。 创建图形上下文、顶点和像素着色器、以及顶点缓冲区。 设置图元的类型,并标记顶点。 取一个顶点数组作为输入。 返回操作进度。
- Deinit() — 释放所占用的资源。 删除图形上下文、顶点和像素着色器、顶点缓冲区、位图标签对象和动态图形资源。
公开成员:
- DXTutorial() — 构造器。 将 DirectX 句柄设置为 0。
- ~DXTutorial() — 析构器。 调用 Deinit() 方法。
- Init() — 准备工作。 将顶点数组、以及可选的高度和宽度作为输入。 验证接收到的数据,调用 InitCanvas() 和 InitDevice()。 返回操作进度。
- Draw() — 在屏幕上显示图像。 清除颜色和深度缓冲区,将图像输出到图形资源。 返回操作进度。
顶点数组
由于顶点只包含有关坐标的信息,为了简单起见,我们将采用包含 4 个浮点变量的结构。 X, Y, Z 是三维空间中的坐标。 W 是一个辅助常数,必须等于 1,用于矩阵运算。
struct float4 { float x; float y; float z; float w; };
一个三角形需要 3 个顶点,所以要用大小为 3 的数组。
float4 vertex[3] = {{-0.5f, -0.5f, 0.0f, 1.0f}, {0.0f, 0.5f, 0.0f, 1.0f}, {0.5f, -0.5f, 0.0f, 1.0f}};
初始化
将顶点数组和画布大小传递给对象。 检查输入数据。 如果传递的宽度或高度小于 1,则把该参数设置为 500 像素。 顶点数组的尺寸必须为 3。 接下来,在循环中检查每个顶点。 X 和 Y 坐标必须在 [-1;1] 范围内,Z 必须等于 0,否则会被强制重置为该值。 W 必须为 1,否则也会强制重置。 调用画布和 DirectX 初始化函数。
bool DXTutorial::Init(float4 &vertex[], int width = 500, int height = 500) { if(width <= 0) { m_width = 500; Print("Warning: width changed to 500"); } else { m_width = width; } if(height <= 0) { m_height = 500; Print("Warning: height changed to 500"); } else { m_height = height; } if(ArraySize(vertex) != 3) { Print("Error: 3 vertex are needed for a triangle"); return(false); } for(int i = 0; i < 3; i++) { if(vertex[i].w != 1) { vertex[i].w = 1.0f; Print("Warning: vertex.w changed to 1"); } if(vertex[i].z != 0) { vertex[i].z = 0.0f; Print("Warning: vertex.z changed to 0"); } if(fabs(vertex[i].x) > 1 || fabs(vertex[i].y) > 1) { Print("Error: vertex coordinates must be in the range [-1;1]"); return(false); } } ResetLastError(); if(!InitCanvas()) { return(false); } if(!InitDevice(vertex)) { return(false); } return(true); }
创建画布
InitCanvas() 函数的作用是:创建一个位图标签对象,其坐标以像素为单位。 然后,一个动态图形资源被绑定到此对象,来自 DirectX 的图像将被输出到其中。
bool DXTutorial::InitCanvas() { m_canvas = "DXTutorialCanvas"; m_resource = "::DXTutorialResource"; int area = m_width * m_height; if(!ObjectCreate(0, m_canvas, OBJ_BITMAP_LABEL, 0, 0, 0)) { Print("Error: failed to create an object to draw"); return(false); } if(!ObjectSetInteger(0, m_canvas, OBJPROP_XDISTANCE, 100)) { Print("Warning: failed to move the object horizontally"); } if(!ObjectSetInteger(0, m_canvas, OBJPROP_YDISTANCE, 100)) { Print("Warning: failed to move the object vertically"); } if(ArrayResize(m_image, area) != area) { Print("Error: failed to resize the array for the graphical resource"); return(false); } if(ArrayInitialize(m_image, ColorToARGB(clrBlack)) != area) { Print("Warning: failed to initialize array for graphical resource"); } 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); } if(!ObjectSetString(0, m_canvas, OBJPROP_BMPFILE, m_resource)) { Print("Error: failed to bind resource to object"); return(false); } return(true); }
我们来更详尽地研究一下该代码。
m_canvas = "DXTutorialCanvas";
指定图形资源 “DXTutorialCanvas” 的名称。
m_resource = "::DXTutorialResource";
指定动态图形资源的名称 “::DXTutorialResource”。
int area = m_width * m_height;
该方法需要若干次宽度和高度的乘积,因此我们提前计算,并保存结果。
ObjectCreate(0, m_canvas, OBJ_BITMAP_LABEL, 0, 0, 0)
创建名为 “DXTutorialCanvas” 的位图标签对象。
ObjectSetInteger(0, m_canvas, OBJPROP_XDISTANCE, 100)
将对象从图表左上角向右移动 100 个像素。
ObjectSetInteger(0, m_canvas, OBJPROP_YDISTANCE, 100)
将对象从图表左上角向下移动 100 像素。
ArrayResize(m_image, area)
调整所要绘制的数组尺寸。
ArrayInitialize(m_image, ColorToARGB(clrBlack))
用黑色填充数组。 数组中的颜色必须以 ARGB 格式存储。 为方便起见,请使用标准的 ColorToARGB 函数将颜色转换为所需的格式。
ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)
创建一个名为 “::DXTutorialResource” 的动态图形资源,其宽度为 m_width,高度为 m_height。 以 COLOR_FORMAT_ARGB_NORMALIZE 值表示用透明颜色。 使用 m_image 数组作为数据源。
ObjectSetString(0, m_canvas, OBJPROP_BMPFILE, m_resource)
将对象和资源关联起来。 之前,我们没有指定对象的大小,因为它会自动调整到资源的大小。
DirectX 初始化
我们转到最有趣的部分。
bool DXTutorial::InitDevice(float4 &vertex[]) { DXVertexLayout layout[1] = {{"POSITION", 0, DX_FORMAT_R32G32B32A32_FLOAT }}; string shader_error = ""; m_dx_context = DXContextCreate(m_width, m_height); if(m_dx_context == INVALID_HANDLE) { Print("Error: failed to create graphics context: ", GetLastError()); return(false); } m_dx_vertex_shader = DXShaderCreate(m_dx_context, DX_SHADER_VERTEX, shader, "VShader", shader_error); if(m_dx_vertex_shader == INVALID_HANDLE) { Print("Error: failed to create vertex shader: ", GetLastError()); Print("Shader compilation error: ", shader_error); return(false); } 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); 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()); return(false); } if(!DXShaderSetLayout(m_dx_vertex_shader, layout)) { Print("Error: failed to set vertex layout: ", GetLastError()); return(false); } if(!DXPrimiveTopologySet(m_dx_context, DX_PRIMITIVE_TOPOLOGY_TRIANGLELIST)) { Print("Error: failed to set primitive type: ", GetLastError()); return(false); } if(!DXShaderSet(m_dx_context, m_dx_vertex_shader)) { Print("Error, failed to set vertex shader: ", GetLastError()); return(false); } if(!DXShaderSet(m_dx_context, m_dx_pixel_shader)) { Print("Error: failed to set pixel shader: ", GetLastError()); return(false); } if(!DXBufferSet(m_dx_context, m_dx_buffer)) { Print("Error: failed to set buffer to render: ", GetLastError()); return(false); } return(true); }
我们来分析一下代码。
DXVertexLayout layout[1] = {{"POSITION", 0, DX_FORMAT_R32G32B32A32_FLOAT }};
这一行描述了顶点的格式。 图形卡需要这些信息才能正确处理顶点的输入数组。 在此情况下,数组大小等于 1,因为顶点只存储位置信息。 但是如果我们添加关于顶点颜色的信息,我们需要另一个数组单元。 "POSITION" 表示该信息与坐标有关。 0 是一个语义索引。 如果我们需要在一个顶点中传递两个不同的坐标,我们可以把第一个顶点对应到索引 0,第二个顶点对应到索引 1。 DX_FORMAT_R32G32B32A32_FLOAT - information representation format. 在本例中,四个 32 位浮点数。
string shader_error = "";
此变量存储着色器编译错误。
m_dx_context = DXContextCreate(m_width, m_height);
创建一个宽度为 m_width ,且高度为 m_height 的图形上下文。 记住句柄。
m_dx_vertex_shader = DXShaderCreate(m_dx_context, DX_SHADER_VERTEX, shader, "VShader", shader_error);
创建顶点着色器,并保存句柄。 DX_SHADER_VERTEX 指示着色器类型 - 顶点。 代码 shader 存储顶点着色器和像素着色器的源代码,但建议将它们存储在单独的文件中,并将其作为资源包含。 "VShader" 是入口点的名称(普通程序中的函数 “main”)。 如果发生着色器编译错误,附加信息将写入 shader_error。 例如,如果指定入口点 “VSha”,变量将包含以下文本: "error X3501: 'VSha': entrypoint not found"。
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)
设置图元的类型为 "list of triangles"。
DXShaderSet(m_dx_context, m_dx_vertex_shader)
传递有关顶点着色器的信息。
DXShaderSet(m_dx_context, m_dx_pixel_shader)
传递有关像素着色器的信息。
DXBufferSet(m_dx_context, m_dx_buffer)
传递有关缓冲区的信息。
图像显示
DirectX 将图像输出到数组。 基于此数组创建图形资源。
bool DXTutorial::Draw() { DXVector dx_color{1.0f, 0.0f, 0.0f, 0.5f}; if(!DXContextClearColors(m_dx_context, dx_color)) { Print("Error: failed to clear the color buffer: ", GetLastError()); return(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); }
我们更详细地分析这个方法。
DXVector dx_color{1.0f, 0.0f, 0.0f, 0.5f};
dx_color 变量保存所创建 DXVector 的类型。 将为其指定具有半透明度的红色。 RGBA格式,值在 0 到 1 间浮动。
DXContextClearColors(m_dx_context, dx_color)
填充缓冲区,颜色为 dx_color
DXContextClearDepth(m_dx_context)
清除深度缓冲区。
DXDraw(m_dx_context)
将渲染任务发送到 DirectX。
DXContextGetColors(m_dx_context, m_image)
将结果返回到 m_image 数组。
ResourceCreate(m_resource, m_image, m_width, m_height, 0, 0, m_width, COLOR_FORMAT_ARGB_NORMALIZE)
更新动态图形资源。
释放资源
DirectX 要求手工释放资源。 此外,还需要删除图形对象和资源。 检查是否需要释放资源,然后调用 DXRelease。 通过 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"); } }
着色器
着色器将存储在 shader 代码中。 但由于其大体积,最好将它们放在单独的外部文件中,并将它们作为资源连接起来。
string shader = "float4 VShader( float4 Pos : POSITION ) : SV_POSITION \r\n" " { \r\n" " return Pos; \r\n" " } \r\n" " \r\n" "float4 PShader( float4 Pos : SV_POSITION ) : SV_TARGET \r\n" " { \r\n" " return float4( 0.0f, 1.0f, 0.0f, 1.0f ); \r\n" " } \r\n";
着色器是用于图形卡的程序。 在 DirectX 中,它是用类 C 的 HLSL 语言编写的。 float4 在着色器中是一个内置的数据类型,与我们的结构相反。 VShader 在本例中是顶点着色器,PShader 是像素着色器。 POSITION - 语义表示输入数据是坐标;其含义与 DXVertexLayout 中的相同。 SV_POSITION - 也是语义,但针对输出值。 SV_ 前缀表示这是一个系统值。 SV_TARGET - 语义,表示该值将写入纹理或像素缓冲区。 那么,此处发生了什么。 坐标被输入到顶点着色器中,顶点着色器会将坐标原封不动地传递到输出。 像素着色器(来自光栅化阶段)接收插值,其颜色设置为绿色。
OnStart
在函数中创建 DXTutorial 类的实例。 调用 Init 函数,将顶点数组传递给该函数。 然后调用 Draw 函数。 之后,脚本执行结束。
void OnStart() { float4 vertex[3] = {{-0.5f, -0.5f, 0.0f, 1.0f}, {0.0f, 0.5f, 0.0f, 1.0f}, {0.5f, -0.5f, 0.0f, 1.0f}}; DXTutorial dx; if(!dx.Init(vertex)) return; ChartRedraw(); Sleep(1000); if(!dx.Draw()) return; ChartRedraw(); Sleep(1000); }
结束语
在本文中,我们研究了 DirectX 的历史。 我们试图理解它是什么及其目的。 我们业已研究了 API 的内部结构。 我们还看到了在现代图形卡上将顶点转换为像素的管道。 此外,本文还提供了使用 DirectX 所需的操作列表,以及一个 MQL 完成的小示例。 最后,我们绘制了第一个三角形! 祝贺! 但对于一个纯属的 DirectX 操作,还有许多其它有趣的新东西需要学习。 这包括除了顶点之外的其它数据传输、HLSL 着色器编程语言、使用矩阵、纹理、法线、和许多特殊效果的各种变换。
参考和链接
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10425
.
Sir:
Your this DX.mq5 issame as DXTutorial.mq5, and received same error : Ошибка, не удалось создать графический контекст: 5151
I checked code and fond that DXContextCreate function fail ! all samples at https://www.mql5.com/en/articles/7708 gave same error.
When I change code as
received that
2023.05.09 18:32:21.723 DX (EURUSD,H1) Before DXContextCreate
2023.05.09 18:32:21.727 DX (EURUSD,H1) Ошибка, не удалось создать графический контекст: 5151
I can not debug it , my OS is windows server 2019 and display adapter is Nivida Quadro FX 1700, and DXdiag told that it support DirectX12
Quadro FX 1700
The Quadro FX 1700 was an enthusiast-class professional graphics card by NVIDIA, launched on September 12th, 2007. Built on the 80 nm process, and based on the G84 graphics processor, in its G84-875-A2 variant, the card supports DirectX 11.1. Even though it supports DirectX 11, the feature level is only 10_0, which can be problematic with many DirectX 11 & DirectX 12 titles.
Try this.
problematic
Thanks!
yes, I find it, FX 1700's DirectX-feature-level only support to 10.0, it is an old diaplay adapter .
DXDiag: DirectX Version: DirectX 12, Chip type: Quadro FX 1700 Driver Date/Size: 2016-10-18 8:00:00, 17559200 bytes
DDI Version: 11.1 Feature Levels: 10_0,9_3,9_2,9_1 Driver Model: WDDM 1.2
I will try it, otherwise I maybe change my diaplay adapter at this server PC.
I test the code at my notepad PC with windows 11 OS, all run OK.
The Quadro FX 1700 was an enthusiast-class professional graphics card by NVIDIA, launched on September 12th, 2007. Built on the 80 nm process, and based on the G84 graphics processor, in its G84-875-A2 variant, the card supports DirectX 11.1. Even though it supports DirectX 11, the feature level is only 10_0, which can be problematic with many DirectX 11 & DirectX 12 titles.
Try this.
Thanks again !
When set Force WARP for MT, run OK !
Thanks again !
When set Force WARP for MT, run OK !