上一篇文章中我们介绍了 交换链,设备,设备上下文 等等名词,并成功创建了他们,这篇中我们将和学习 OpenGL 时一样去使用三个顶点来绘制一个三角形。
这篇中将要介绍的则是 ID3D11Buffer(缓冲)
,ID3D11VertexShader(顶点着色器)
和 ID3D11PixelShader(像素着色器)
这几个东西,我们将顶点数组构图,使用着色器填色。(事实上和我们的 OpenGL 基本一样)
在 OpenGL 中我们使用 glm 数学库(OpenGL Mathematics),而在 DirectX 中则是使用 XM 库(DirectXMath)。首先定义一个三角形的顶点:
1 | struct Vertex { // 定义一个顶点结构体,暂时就一个坐标属性 |
现在使用 D3D11_BUFFER_DESC
创建定点缓冲描述,
1 | // 缓存信息描述 |
创建 D3D11_SUBRESOURCE_DATA
:
1 | D3D11_SUBRESOURCE_DATA verticesSourceData; |
最后定义 ID3D11Buffer *pVertexBufferObject = nullptr;
将他初始化,接下来使用我们的设备对象来创建一个缓冲:
1 | pDevice->CreateBuffer(&vertexBufferDesc, &verticesSourceData, &pVertexBufferObject); |
这几段代码就是为了创建缓冲而来。
在 OpenGL 中,我们现在应该去使用一个 VertexArraysObject(VAO)来描述这个缓冲,但是在 DirectX 中右上下文设备的存在,我们直接使用上下文:
1 | UINT stride = sizeof(Vertex); |
我们使用立即上下文对象 pImmediateContext
来接手这一段顶点数据缓冲。现在你如果直接在主循环里绘制的话,会得到一个消息:A Vertex Shader is always required when drawing, but none is currently bound.
因此我们在绘制的时候必须为其配置着色器程序。
与 DirectX 配套的着色器程序语言叫做 HLSL(High Level Shading Language),它也是使用文本文件来写,然后在 DirectX 代码中编译。首先我们来放两个最简单的着色器代码,顶点着色器和像素着色器(对应 OpenGL 中的片段着色器):
1 | //triangle.vs 顶点着色器 |
相比而言,HLSL 的代码看起来更简单一点。我们直接定义了 Main 方法(需要注意的是 hlsl 的入口方法并非固定,当你启动编译的时候需要指定入口方法,也就是说是由你决定命名的)。在这里需要看一下 SV_POSITION
和 POSITION
以及 SV_TARGET
这三个变量。SV_POSITION
和 POSITION
无疑都是用来描述位置坐标的标识,而区别是 SV_POSITION
用来代表经过顶点着色器变换后的坐标。SV是Systems Value的简写,在SV_POSITION
的情况下,如果它是绑定在一个从顶点着色器输出的数据结构上的话,意味着这个输出的数据结构包含了最终的转换过的,将用于光栅器的顶点坐标。
也就是说,顶点着色器接收未经处理的原始坐标,在顶点着色器中处理后送给像素着色器(虽然这个例子上我们并未处理)。然后 SV_TARGET
标识则是这个函数返回一个传递给下一阶段的值(也就是最终的OutPut Merger 的颜色值)。
如果需要了解更多的 HLSL 知识,可以去 MSDN 看一下(MSDN 是所有 Windows 程序员要翻烂的地方)。
接下来,我们对这两个着色器进行编译:
1 | ID3D10Blob* pErrorMessage = nullptr; |
首先定义顶点着色器 Blob 对象和像素着色器 Blob 对象以及用来查错的对象。之后调用 D3DX11CompileFromFile
来进行编译,先来看看这个方法的参数:
1 | HRESULT D3DX11CompileFromFile( |
具体使用如下:
1 | // 编译顶点着色器 |
在编译成功后,我们使用设备对象来创建顶点着色器和像素着色器:
1 | hr = pDevice->CreateVertexShader(pVertexShaderBlob->GetBufferPointer(), pVertexShaderBlob->GetBufferSize(), nullptr, &pVertexShader); |
在顶点着色器中,我们使用了 POSITION
,在 DirectX 代码中创建一个 InputLayout
来描述 input-assembler
阶段的数据。
1 | D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 } }; |
DX 中多数 API 都有着及其繁杂的参数,限于篇幅在这里写者并没有全部列出来,最详细的信息还是首推 MSDN (话说我推荐过多少次了)。
这些都结束后,我们在主循环里绘制它:
1 | MSG msg; |
如果代码没有错的话应该可以看到这样一个效果: