【DirectX11】【学习笔记(2)】开始画图

可编程渲染管线

概述:渲染管线就是用来创建虚拟摄像机看到的2d图像的设置步骤。它包含以下几个阶段

1. Input Assembler (IA) Stage

2. Vertex Shader (VS) Stage

3. Hull Shader (HS) Stage

4. Tesselator Shader (TS) Stage

5. Domain Shader (DS) Stage

6. Geometry Shader (GS) Stage

7. Stream Output (SO) Stage

8. Rasterizer (RS) Stage

9. Pixel Shader (PS) Stage

10. Output Merger (OM) Stage

其中7个是从Direc3D 10继承过来的。3个是新增的,

分别是Hull, Tesselator, and Domain shaders。这三个阶段都是用来增加物体对象细节的。是比较高级的功能。打个比方:可以把一个三角形分成多个三角形,然后在新生成的顶点上增加更多细节。

注意:我们必须编译每个着色器,确保它没有错误。我们可以随时启用设置各个着色器。而不是设置效果文件中的代码。

Direct3D Pipeline

圆角阶段是可编程的,我们可以自己创建。方角阶段是不可编程的,不过我们可以通过device context来更改它们的配置

Input Assembler (IA) Stage

这个阶段读取图形数据,用来创建图形,比如三角形,方形和线段。这个图形会被后面的阶段采用。

首先创建顶点结构和顶点布局(布局是用来告诉direct3d我们的顶点结构中的每一个元素分别代表什么)

代码如下

//The vertex Structure
struct Vertex
{
    D3DXVECTOR3 pos;
    D3DXCOLOR   color;
};


//The input-layout description
D3D11_INPUT_ELEMENT_DESC layout[] =
{
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"COLOR",    0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};

创建好结构desc(描述信息)之后,我们就通过device调用创建布局方法

ID3D11Device::CreateInputLayout()

IA阶段需要两个缓存,一个是顶点缓存,一个是索引缓存。我们首先创建顶点缓存描述结构,然后把真正的数据填到顶点结构中。我们就可以开始创建缓存了。

ID3D11Device::CreateBuffer()

最后是设置顶点缓存,和布局

ID3D11DeviceContext::IASetVertexBuffers()
ID3D11DeviceContext::IASetInputLayout()

接着我们设置顶点拓扑,好知道顶点之间是如何连接的(每个顶点之间是哪几个顶点组成一个图形)

最终我们就可以把图形发送给IA了,调用方法:

ID3D11DeviceContext::Draw()

Vertex Shader (VS) Stage(可编程)

每个从IA阶段发送过来的图形顶点都要经过VS Stage,这个阶段主要是用来对顶点进行坐标变换,灯光计算。用HLSL(一种类C++语言)编写。代码如下

float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}

本节涉及到的功能比较简单,只是接受顶点位置做输入,然后输出一个顶点位置信息。

注意:VS Stage必须被实施,即使不需要对顶点进行操作。

Hull Shader (HS) Stage(可编程)

这个阶段是用来计算如何在图形上增加新顶点。

Tessellator (TS) Stage

接收HS传来的数据,然后分割图形。

Domain Shader (DS) Stage

拿到HS阶段的顶点坐标和TS阶段顶点变换的坐标。来创建更多细节。

Geometry Shader (GS) Stage(可选择 && 可编程)

它接收一个图元作为输入,比如接收三个顶点作为三角形输入。

它的优势在于它可以创建或者销毁图元,而VS不可以(它接受一个顶点输入,输出一个顶点)

Stream Output (SO) Stage

这个阶段是把从VS/GS阶段传来的图元数据放进不同的顶点缓存中,然后按照列表输出,比如三角形列表,线段列表。

不完整的图元会被剔除,比如只有两个顶点的三角形,只有一个顶点的线段。这个是为了防止在GS阶段产生一些错的数据。

Rasterization Stage (RS) Stage

这个阶段对每个图元的每一个顶点进行插值,然后把它们放进下一阶段PS,这一阶段同样处理clipping,这个是用于裁剪在画面之外的部分。

RS阶段设置视口的函数如下:

ID3D11DeviceContext::RSSetViewports()

Pixel Shader (PS) Stage

RS会对插值的每一个像素调用一次PS,PS决定了每个像素在屏幕上的最终颜色。当一个像素上有两个前后不同的色彩时,将有OM来决定哪一个被最终展示在屏幕上。

float4 PS() : SV_TARGET
{
    return float4(0.0f, 0.0f, 1.0f, 1.0f);
}

返回一个4D向量为蓝色。

Output Merger (OM) Stage

这个阶段主要是用像素片段和深度/模板缓存来决定哪一个像素被最终写入rendertarget。我们设置rendertarget通过调用

ID3D11DeviceContext::OMSetRenderTargets()

最终所有的阶段执行完成,我们通过交换链函数来交换前后缓冲

IDXGISwapChain::Present();

以下介绍每一个步骤的函数的具体参数。

Vertex Structure & Input Layout

每一个物体都由带属性的顶点组成,属性诸如:位置,颜色。

注意:D3D已经从d3dx数学库转移到了xna数学库。

输入布局结构如下:

typedef struct D3D11_INPUT_ELEMENT_DESC
{
   LPCSTR                         SemanticName;
   UINT                         SemanticIndex;
   DXGI_FORMAT                     Format;
   UINT                         InputSlot;
   UINT                         AlignedByteOffset;
   D3D11_INPUT_CLASSIFICATION     InputSlotClass;
   UINT                         InstanceDataStepRate;
}     D3D11_INPUT_ELEMENT_DESC;

SemanticName - 连接元素的字符串。用来匹配顶点结构中的元素。

SemanticIndex - 名字后的索引号。eg:如果我们顶点结构中有两个贴图,我们不会创建两个不同的名字,我们只需要两个不同的索引。如果一个名字后没有索引,默认为0。

Format - 顶点结构中元素格式,必须为DXGI_FORMAT 枚举值之一,本节我们对于positoin使用DXGI_FORMAT_R32G32B32_FLOAT

InputSlot - d3d最多允许15个元素槽。eg:我们可以把顶点结构中的位置和颜色放入同一个槽里,也可以放进不同的槽。

AlignedByteOffset  - 当前元素的字节偏移。例如第一个元素位置偏移为0,而第二个元素color偏移为12bytes,因为位置占DXGI_FORMAT_R32G32B32_FLOAT,12个字节。

InputSlotClass 0 现在我们只用D3D10_INPUT_PER_VERTEX_DATA。

InstanceDataStepRate  - 用于实例化,现在设置为0.

接下来我们创建一个全局数组来保存多个布局信息。

struct Vertex    //Overloaded Vertex Structure
{
    Vertex(){}
    Vertex(float x, float y, float z)
        : pos(x,y,z){}

    XMFLOAT3 pos;
};

D3D11_INPUT_ELEMENT_DESC layout[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  
};
UINT numElements = ARRAYSIZE(layout);

Cleaning Up

注意一定要释放接口

void CleanUp()
{
    //Release the COM Objects we created
    SwapChain->Release();
    d3d11Device->Release();
    d3d11DevCon->Release();
    renderTargetView->Release();
    triangleVertBuffer->Release();
    VS->Release();
    PS->Release();
    VS_Buffer->Release();
    PS_Buffer->Release();
    vertLayout->Release();
}

Initializing the Scene

bool InitScene()
{
    //Compile Shaders from shader file
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_5_0", 0, 0, 0, &VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_5_0", 0, 0, 0, &PS_Buffer, 0, 0);

    //Create the Shader Objects
    hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
    hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);

    //Set Vertex and Pixel Shaders
    d3d11DevCon->VSSetShader(VS, 0, 0);
    d3d11DevCon->PSSetShader(PS, 0, 0);

    //Create the vertex buffer
    Vertex v[] =
    {
        Vertex( 0.0f, 0.5f, 0.5f ),
        Vertex( 0.5f, -0.5f, 0.5f ),
        Vertex( -0.5f, -0.5f, 0.5f ),
    };

    D3D11_BUFFER_DESC vertexBufferDesc;
    ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

    vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
    vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 3;
    vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    vertexBufferDesc.CPUAccessFlags = 0;
    vertexBufferDesc.MiscFlags = 0;

    D3D11_SUBRESOURCE_DATA vertexBufferData; 

    ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
    vertexBufferData.pSysMem = v;
    hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &triangleVertBuffer);

    //Set the vertex buffer
    UINT stride = sizeof( Vertex );
    UINT offset = 0;
    d3d11DevCon->IASetVertexBuffers( 0, 1, &triangleVertBuffer, &stride, &offset );

    //Create the Input Layout
    hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), 
        VS_Buffer->GetBufferSize(), &vertLayout );

    //Set the Input Layout
    d3d11DevCon->IASetInputLayout( vertLayout );

    //Set Primitive Topology
    d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

    //Create the Viewport
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));

    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = Width;
    viewport.Height = Height;

    //Set the Viewport
    d3d11DevCon->RSSetViewports(1, &viewport);

    return true;
}   

这部分主要对场景的各种变量进行初始化。包括对IA阶段的设置,启用着色器,RS阶段设置视口。

Compiling the Shaders

我们从效果文件中编译着色器。通过调用:

HRESULT WINAPI D3DX11CompileFromFile(
            LPCSTR pSrcFile,
            CONST D3D10_SHADER_MACRO* pDefines, 
            LPD3D10INCLUDE pInclude,
            LPCSTR pFunctionName, 
            LPCSTR pProfile, 
            UINT Flags1, 
            UINT Flags2, 
            ID3DX11ThreadPump* pPump, 
            ID3D10Blob** ppShader, 
            ID3D10Blob** ppErrorMsgs, 
            HRESULT* pHResult);

pSrcFile  - 文件名字

pDefines  - 指向数组宏的指针。设为NULL

pInclude - 指向include接口的指针,如果shader中有include语句,这里不能设为空。

pFunctionName - 文件中的函数名字

pProfile  - shader版本。我们这里用的比较老 "vs_4_0" and "ps_4_0".

Flags1 - 编译标记

Flags2 - 效果标记

pPump  - 与多线程有关

ppShader  - 指向返回的shader对象,这个更像是一个包含shader信息的缓冲区,可以用来创建真正的shader

ppErrorMsgs  - 返回一系列编译警告,错误信息。

pHResult  - 编译结果。(详情见上一章番外篇)

hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_5_0", 0, 0, 0, &VS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_5_0", 0, 0, 0, &PS_Buffer, 0, 0);

Creating the Shaders

HRESULT CreateVertexShader( 
  [in]        const void *pShaderBytecode,
  [in]        SIZE_T BytecodeLength,
  [in]        ID3D11ClassLinkage *pClassLinkage,
  [in, out]   ID3D11VertexShader **ppVertexShader) = 0;
);

HRESULT CreatePixelShader( 
  [in]        const void *pShaderBytecode,
  [in]        SIZE_T BytecodeLength,
  [in]        ID3D11ClassLinkage *pClassLinkage,
  [in, out]   ID3D11PixelShader **ppPixelShader) = 0;
);

pShaderBytecode  - 指向shader缓冲区的指针

BytecodeLength  - 缓冲区大小

pClassLinkage - 指向类接口指针。这里设为空

ppVertexShader  - 返回的顶点着色器

ppPixelShader  - 返回的像素着色器

Setting the Shaders

一个应用可能会需要多个着色器,我们必须在运行时动态设置他们。D3D会保持这次的设置直到下次更改,所以我们必须根据不同的需要实时设置。

void  VSSetShader( 
  [in]   ID3D11VertexShader *pVertexShader,
  [in]   (NumClassInstances)  ID3D11ClassInstance *const *ppClassInstances,
  [in]   UINT NumClassInstances);
);

void PSSetShader( 
  [in]   ID3D11PixelShader *pPixelShader,
  [in]   (NumClassInstances)  ID3D11ClassInstance *const *ppClassInstances,
  [in]   UINT NumClassInstances);
);

pVertexShader - 指向顶点着色器

pPixelShader  - 指向像素着色器

ppClassInstances - 设为NULL

NumClassInstances  - 上一个参数的个数,设为NULL

d3d11DevCon->VSSetShader(VS, 0, 0);
d3d11DevCon->PSSetShader(PS, 0, 0);

创建顶点缓存

typedef struct D3D11_BUFFER_DESC
{
   UINT             ByteWidth;
   D3D11_USAGE         Usage;
   UINT             BindFlags;
   UINT             CPUAccessFlags;
   UINT             MiscFlags;
   UINT             StructureByteStride;
}    D3D11_BUFFER_DESC;

ByteWidth  - 缓冲区字节大小

Usage - 描述我们的缓冲区如何被读写

BindFlags - 设为D3D11_BIND_VERTEX_BUFFER,因为这是一个顶点缓冲

 CPUAccessFlags  - 缓冲区如何被CPU使用,设为NULL

MiscFlags  - 额外标记

StructureByteStride  - 设为NULL

有了描述信息之后,我们需要填满D3D11_SUBRESOURCE_DATA结构,里面的数据是将被放入顶点缓存的数据

typedef struct D3D11_SUBRESOURCE_DATA
{
   const    void *pSysMem;
   UINT     SysMemPitch;
   UINT     SysMemSlicePitch;
}     D3D11_SUBRESOURCE_DATA;

pSysMem  - 要放入的数据

SysMemPitch - 贴图一行的字节数

SysMemSlicePitch - 3d贴图的深度值间距

有了描述信息和数据,我们就可以创建缓冲了

HRESULT CreateBuffer( 
   [in]    const D3D11_BUFFER_DESC *pDesc,
   [in]    const D3D11_SUBRESOURCE_DATA *pInitialData,
   [in]    ID3D11Buffer **ppBuffer
);

pDesc - 指向描述信息

pInitialData - 指向数据

ppBuffer - 返回的缓冲指针

整段代码如下:

Vertex v[] =
{
    Vertex( 0.0f, 0.5f, 0.5f ),
    Vertex( 0.5f, -0.5f, 0.5f ),
    Vertex( -0.5f, -0.5f, 0.5f ),
};

D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );

vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 3;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;

D3D11_SUBRESOURCE_DATA vertexBufferData; 

ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = v;
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &triangleVertBuffer);

Setting the Vertex Buffer

我们需要把顶点缓存绑定到IA阶段

void IASetVertexBuffers(
   [in]   UINT StartSlot,
   [in]   UINT NumBuffers,
   [in]   ID3D11Buffer *const *ppVertexBuffers,
   [in]   const UINT *pStrides,
   [in]   const UINT *pOffsets
);

StartSlot - 输入槽

NumBuffers - 我们要绑定的缓冲数,这里我们只绑定一个

ppVertexBuffers - 指向顶点缓存

pStrides  - 每个顶点大小

pOffsets - 从缓冲的第N个字节开始。偏移量,我们设为0

UINT stride = sizeof( Vertex );
UINT offset = 0;
d3d11DevCon->IASetVertexBuffers( 0, 1, &triangleVertBuffer, &stride, &offset );

Creating the Input (Vertex) Layout

创建输入布局,通过函数ID3D11Device::CreateInputLayout():

HRESULT CreateInputLayout( 
   [in]   const D3D11_INPUT_ELEMENT_DESC *pInputElementDescs,
   [in]   UINT NumElements,
   [in]   const void *pShaderBytecodeWithInputSignature,
   [in]   SIZE_T BytecodeLength,
   [out]  ID3D11InputLayout **ppInputLayout
);

pInputElementDescs  - 指向描述信息数组

NumElements - 数组元素个数

pShaderBytecodeWithInputSignature -指向顶点着色器

BytecodeLength - 顶点着色器大小

ppInputLayout - 返回的输入布局指针

hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(), 
    VS_Buffer->GetBufferSize(), &vertLayout );

Setting the Input (Vertex) Layout

把layout绑定到IA,ID3D11DeviceContext::IASetInputLayout():

void STDMETHODCALLTYPE IASetInputLayout( 
   [in]   ID3D11InputLayout *pInputLayout
);

Setting the Primitive Topology

( ID3D11DeviceContext::IASetPrimitiveTopology() )

我们想告诉IA,我们发送了怎样的图元。

参数是D3D11_PRIMITIVE_TOPOLOGY枚举值

D3D10_PRIMITIVE_TOPOLOGY_POINTLIST - 每个顶点都是一个单独的点

D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP - 连接起来的点

D3D10_PRIMITIVE_TOPOLOGY_LINELIST - 每两个点构成一条线,和上一个不同的是,上一个参数的每一个顶点都会连接到之前的线的顶点上

D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP - 每个三角形都和邻接的三角形分享顶点

D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST - 每三个顶点构成一个三角形。和上一个参数不同的是,STRIP需要四个点构成两个三角形,而LIST需要6个点

D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ - 只用于GS Stage

d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

Creating the Viewport

RS Stage决定绘图在窗口的哪个部分。

D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = Width;
viewport.Height = Height;

因为我们打算绘制在整个屏幕上,所以需要设置为窗口的大小

Setting the Viewport

( ID3D11DeviceContext::RSSetViewports() )

第一个参数是绑定的个数,第二个是视口  这样我们可以拥有多个视口 给多个玩家

Rendering the Primitive

void DrawScene()
{
    float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);

    d3d11DevCon->Draw( 3, 0 );

    SwapChain->Present(0, 0);
}

Draw函数第一个参数是要绘制的顶点数,第二个是绘制顶点的偏移量

Effect File (Shaders)

( Vertex Shader )

我们本节只有一个非常简单的顶点着色器,

float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}

函数接收一个命inPos的参数,POSITION指的是IA阶段发送的顶点结构中的元素位置

我们这里只是把位置信息读入,然后返回一个位置。

( Pixel Shader )

本节的 PS只返回一个固定的颜色给RS。 之后我们会学习更多有趣的功能!

【总结】Direct3D每设置一个功能的时候,基本上都需要先创建对象描述信息,然后利用描述创建对象,最终再把对象绑定到管线上使用!

本节内容很多,我写完博客后还是决定做一个思维导图来加深一下自己对整体的把握。

这节内容其实之前就已经看过几次了,但是今天又重新回头系统的阅读并记录下来,感觉自己对这部分内容掌握又深了一点。

好了本节内容就到这里拉,如果你也喜欢Ditect3D,欢迎评论给出建议,我们可以一起学习,一起进步!

下午还有个报告,要去看论文了~ 溜了溜了

                                                                            ———————— 小明 2018.11.16 13.29

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值