04.开始绘图!

  
在这里,我们将学习绘制一个简单的蓝色三角形!在实际开始之前,我们需要介绍Direct3d 11的实际工作方式。我尝试通过尽可能短的有意义的步骤,使课程尽可能短,同时仍能完成任务。我认为这样会更有效,因为您不会像读一本又一本的书那样无聊。通过不入睡,您将从较短的步骤中获得更多收益。您可能会看到,这比以前的要长一些,它初始化了direct3d 11,但这是采取其余步骤的一大步骤。坦白说,它并不像您通读后所看到的那样复杂。确实没有很多新代码。

可编程图形渲染管线

如果您有使用Direct3D 10的经验,并且了解管道,则可以跳过这些管道部分,因为Direct3D基本上是Direct3D 10的扩展,它使用相同的管道,但有几个额外的阶段。Direct3D 11在可编程图形管线中增加了3个新阶段。不仅如此,它还支持另一个单独的但松散连接的管道,称为计算着色器管道。图形渲染管道(又称为绘制管道)中的三个新阶段是Hull,Tesselator和Domain着色器。它们与镶嵌有关,后者基本上为对象添加了更多细节,而更多地添加了细节。例如,它从模型中提取了一个简单的三角形,可能会添加更多的顶点以创建更多的三角形,然后重新定位顶点,使三角形更加详细。它可以采用简单的低花粉模型,并在屏幕上显示时将其转换为非常详细的高花粉模型。它可以非常快速有效地完成所有这些工作。但是,这是一个高级主题,我们将在本课程中不学习如何实现。 通过使用GPU作为一种并行处理器来扩展CPU的处理能力,可将计算着色器(也称为Dispatch Pipeline)用于进行极快的计算。这与图形没有任何关系。例如,您可以使用计算着色器管道在GPU上执行性能非常昂贵的操作,例如精确的碰撞检测。本课程将不讨论计算着色器。 渲染管道是Direct3d用于基于虚拟相机看到的内容创建2d图像的一组步骤。它由Direct3D 10中使用的7个阶段以及Direct3D 11随附的3个新阶段组成,如下所示: 1.输入汇编器(IA)阶段 2.顶点着色器(VS)阶段 3.船体着色器(HS)阶段 4. Tesselator Shader(TS)阶段 5.域着色器(DS)阶段 6.几何着色器(GS)阶段 7.流输出(SO)阶段 8.光栅化(RS)阶段 9.像素着色器(PS)阶段 10.输出合并(OM)阶段 另一件事是我们现在必须编译每个着色器。这样可以确保着色器没有错误。另外,除了将效果文件中的一种技术设置为活动技术(着色器的顺序)之外,我们还可以随时在代码中设置各个着色器。我认为这是一种更具动态性的方法,因为它为我们提供了更大的自由来更改活动着色器,同时保留其他人作为活动着色器。例如,我们可以将像素着色器从使用照明计算来确定最终像素颜色更改为不使用照明方程式的像素着色器,同时仍保持相同的顶点着色器处于活动状态。
Direct3D管道
舍入阶段是“可编程”阶段,实际上是我们自己创建的阶段。方形阶段是我们不编程的阶段,但是我们可以使用Direct3d 11设备上下文来更改其设置。

输入组装器(IA)阶段

您可以看到的第一阶段是输入汇编器(IA)。IA是固定功能阶段,这意味着我们不做任何编程来暗示它。IA读取几何数据,顶点和索引。然后,它使用数据来创建几何图元,例如梯形图,正方形,直线和点,这些图元将被馈入其他阶段并由其他阶段使用。索引定义了图元应如何通过顶点组合在一起。我们将在以后的课程中讨论索引。 在将所有内容发送给IA之前,我们需要先做几件事,例如创建缓冲区并设置基本拓扑,输入布局和活动缓冲区。 首先,我们首先创建一个缓冲区。IA使用的两个缓冲区是顶点缓冲区和索引缓冲区。在本课程中,我们将不再担心索引缓冲区。要创建缓冲区,我们将填写D3D11_BUFFER_DESC结构。 创建一个或多个缓冲区之后,我们需要创建输入布局对象。这样做是为了告诉Direct3d我们的文本结构由什么组成,以及如何处理我们的顶点结构中的每个组件。我们使用D3D11_INPUT_ELEMENT_DESC元素数组将信息提供给direct3d。D3D11_INPUT_ELEMENT_DESC数组中的每个元素都描述了顶点结构中的一个元素。如果您的顶点结构具有position元素和color元素,则D3D10_INPUT_ELEMENT_DESC数组将具有一个用于位置的元素和一个用于颜色的元素。这是一个例子:
//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}
};


在本课程中,我们的顶点结构如下所示:

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 },  
};


在使用D3D11_INPUT_ELEMENT_DESC结构描述了输入布局之后,我们需要使用以下函数创建它:

ID3D11Device::CreateInputLayout()


我们还需要创建一个顶点缓冲区来保存对象的顶点。要创建顶点缓冲区,首先我们使用D3D11_BUFFER_DESC结构描述缓冲区,然后使用实际的顶点数据填充D3D11_SUBRESOURCE_DATA结构。要实际创建顶点缓冲区,我们可以调用:

ID3D11Device::CreateBuffer()


下一步是将布局说明和顶点缓冲区绑定到IA。我们可以通过调用以下函数将布局和顶点缓冲区绑定到IA:

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


现在,我们需要设置基本拓扑,以便IA知道如何使用顶点并制作诸如三角形或直线之类的基本元素。我们调用该函数:

ID3D11DeviceContext::IASetPrimitiveTopology()


在本课程的后面,我将介绍不同的类型。

管道准备就绪后,我们调用draw方法将原语发送到IA。在本课程中,我们调用的方法是:

ID3D11DeviceContext::Draw()

顶点着色器(VS)阶段

VS是第一个可编程着色器,这意味着我们必须自己对其进行编程。VS阶段是将图元在AI中组装后所有顶点都要经过的阶段。绘制的每个顶点都将通过VS。使用VS,您可以执行诸如变换,缩放,照明,对纹理进行位移贴图之类的操作。即使不需要修改程序中的顶点,也必须始终实施顶点着色器以使管道正常工作。管道中的着色器以HLSL语言编写。该语言类似于C ++语法,因此学习起来并不难。我将在每次更改效果的课程中解释效果文件,稍后我们将专门针对HLSL讲授课程。在本课程中,我们的顶点着色器不执行任何操作,因此我们只返回每个顶点的位置而不修改它。看起来像这样:
float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}


顶点着色器将单个顶点作为输入,并返回单个输入。注意VS参数中Pos之后的POSITION。创建顶点(输入)布局时,我们为顶点的位置值指定POSITION,因此它们将被发送到VS中的此参数。您可以根据需要从POSITION更改名称。

船体着色器(HS)阶段

HS是Direct3d 11图形渲染管道中添加的三个新可选阶段中的第一个。外壳着色器阶段,细化器阶段和域着色器阶段这三个新阶段共同协作以实现称为镶嵌的东西。镶嵌的作用是将一个原始对象(例如三角形或直线)分割成许多较小的部分,从而以极快的速度增加模型的细节。它会在将所有这些新原语放在屏幕上之前在GPU上创建它们,并且不会将它们保存到内存中,因此,这节省了在CPU和内存上创建它们的大量时间。您可以采用简单的低轮询模型,然后使用镶嵌来将其转换为非常详细的轮询。 因此,回到船体着色器。这是另一个可编程阶段。我将不做详细介绍,但此阶段要做的是计算向原始对象添加新顶点的方式和位置,以使其更加详细。然后,它将数据发送到Tessellator阶段和Domain Shader阶段。

镶嵌(TS)阶段

细分阶段是细分过程的第二阶段。这是固定功能阶段。该阶段要做的是从船体着色器获得输入,然后实际进行图元的分割。然后,它将数据传递到Domain Shader。

域着色器(DS)阶段

这是细分过程中三个阶段的第三个阶段。这是一个可编程功能阶段。这个阶段要做的是从Hull Shader阶段获取新顶点的位置,并转换从tessallator阶段获得的顶点以创建更多细节,因为仅在三角形或直线的中心添加更多顶点不会增加任何细节。然后将顶点传递到几何着色器阶段。

几何着色器(GS)阶段

此着色器阶段是可选的。这也是另一个可编程功能阶段。它接受图元作为输入,例如三角形的3个顶点,直线的2个顶点和一个点的3个顶点。它还可以从边缘相邻图元中获取数据作为输入,例如一条线的另外2个顶点,或三角形的另外3个顶点。GS的一个优点是它可以创建或销毁VS无法实现的基元(它可以获取一个顶点并输出一个顶点)。在这一阶段,我们可以将一个点变成四边形或三角形。我们能够将数据从GS传递到光栅化器阶段,和/或通过流输出传递到内存中的顶点缓冲区。我们将在下一课中学习有关此着色器阶段的更多信息。

流输出(SO)阶段

该阶段用于从管线中获取顶点数据,特别是在没有GS的情况下,特别是几何着色器阶段或顶点着色器阶段。从SO发送到内存的顶点数据被放入一个或多个顶点缓冲区。从SO输出的顶点数据始终以列表形式发送,例如线列表或三角形列表。永远不会发出不完整的图元,就像在顶点和几何图形中一样,它们会被悄悄地分解开来。不完整的图元是图元,例如只有两个顶点的三角形或只有一个顶点的线。

栅格化阶段(RS)阶段

RS阶段获取发送给它的矢量信息(形状和图元),并通过在每个图元之间插入每个顶点的值将它们转换为像素。它还处理裁剪,基本上是裁剪屏幕视图之外的图元。我们使用以下方法在RS中设置视口:
ID3D11DeviceContext::RSSetViewports()

像素着色器(PS)阶段

该阶段进行计算并修改将在屏幕上看到的每个像素,例如基于每个像素的照明。它是另一个可编程功能,也是一个可选阶段。RS为基元中的每个像素调用一次像素着色器。就像我们之前说过的那样,图元中每个顶点的值和属性都插在RS中的整个图元中。基本上就像顶点着色器,其中顶点着色器具有1:1映射(它吸收一个顶点并返回一个顶点),而像素着色器也具有1:1映射(它吸收一个像素并返回一个像素) 。 像素着色器的工作是计算每个像素片段的最终颜色。像素片段是将被绘制到屏幕上的每个潜在像素。例如,在实心圆后面有一个实心正方形。正方形中的像素是像素片段,圆形中的像素是像素片段。每个都有写入屏幕的机会,但是一旦进入输出合并阶段,即决定要绘制到屏幕上的最终像素,它将看到圆的深度值小于屏幕的深度值。正方形,因此只会绘制圆形像素。PS输出4D颜色值。 在本课程中,我们将使用像素着色器通过返回等于蓝色的4D浮点值来使三角形变为蓝色。无需进行任何计算,仅返回绿色即可,因此通过着色器运行的每个像素都将是蓝色。
float4 PS() : SV_TARGET
{
    return float4(0.0f, 0.0f, 1.0f, 1.0f);
}

输出合并(OM)阶段

流水线的最后阶段是输出合并阶段。基本上,此阶段获取像素片段和深度/模板缓冲区,并确定实际将哪些像素写入渲染目标。在上一课中,我们通过调用以下命令设置渲染目标:
ID3D11DeviceContext::OMSetRenderTargets()


我们可以稍后再讨论。

在OM阶段之后,剩下要做的就是将我们的backbuffer显示在屏幕上。我们可以通过以下方式实现:

IDXGISwapChain::Present();


在这里,我们创建了几个新的接口对象。请记住,完成接口对象操作后,必须释放它们。第一个是缓冲区,用于保存我们的顶点数据。接下来的两个是我们的顶点和像素着色器。之后,我们有了顶点和像素缓冲区,它将保存有关顶点和像素着色器的信息。最后一个是我们的输入(顶点)布局。

ID3D11Buffer* triangleVertBuffer;
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;

顶点结构和输入布局

(D3D11_INPUT_ELEMENT_DESC) 我们制作的所有3D对象都将由具有诸如顶点之类的属性的点组成,例如颜色。我们必须建立自己的顶点结构。这是一个重载的顶点结构(因此我们可以轻松,动态地创建和编辑顶点),并且只有一个位置。注意XMFLOAT3。正如我在上一课中所提到的,direct3d正在从d3dx数学库转移到更流行的xna数学库中。在使用D3DXVECTOR3之前。 之后,您可以看到我们的输入布局。输入布局是使用D3D11_INPUT_ELEMENT_DESC结构的数组定义的。D3D11_INPUT_ELEMENT_DESC看起来像这样:
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;


每个成员的说明如下:

语义名称-
这只是一个与元素关联的字符串。此字符串将用于将顶点结构中的元素映射到顶点着色器中的元素。

语义索引-
基本上,这只是语义名称后面的一个数字,用作索引。例如,如果我们在顶点结构中有2个纹理元素,而不是创建2个不同的纹理语义名称,我们可以仅使用2个不同的索引。如果顶点着色器代码中的语义名称后面没有索引,则默认为索引0。例如,在我们的着色器代码中,我们的语义名称为“ POSITION”,实际上与“ POSITION0”相同。

格式-
这只是我们顶点结构中组件的格式。它必须是DXGI_FORMAT枚举类型的成员。在本课程中,我们有一个3d矢量来描述位置,因此我们可以使用DXGI_FORMAT:DXGI_FORMAT_R32G32B32_FLOAT。如果需要其他格式,可以在msdn上找到它们。稍后我们将使用其他的。

输入插槽-
Direct3D允许我们使用16个不同的元素插槽(0-15),可以将顶点数据放入其中。如果我们的顶点结构具有位置和颜色,则可以将两个元素都放在同一个输入槽中,或者可以将位置数据穿过第一个槽,将颜色数据穿过第二个槽。我们只需要使用一个,但是您可以根据需要进行实验。

AlignedByteOffset-
这是您描述的元素的字节偏移量。在单个输入插槽中,如果我们有位置和颜色,则位置可能为0,因为它始于顶点构造函数的开头,并且颜色需要是我们顶点位置的大小,即12字节(请记住我们的格式我们的顶点位置是DXGI_FORMAT_R32G32B32_FLOAT,它是96位,该位置的每个分量为32。一个字节中有8位,所以96/8 == 12)。

InputSlotClass-
现在,我们可以只使用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);

打扫干净

接下来的新功能是在CleanUp函数中,当我们完成程序后,我们将在其中释放接口。
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();
}

初始化场景

这是我们初始化场景的地方。在这里我们放置在整个游戏过程中会发生变化但不会在整个场景中发生变化的事物。本课中几乎所有的新内容都在这里。我将一次解释一个部分。
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;
}   

编译着色器

(D3DX11CompileFromFile()) 我们将通过创建着色器开始初始化场景。我们将从名为“ Effects.fx”的效果文件中编译着色器。我们可以通过使用函数D3DX11CompileFromFile()来做到这一点:
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-
一个字符串,其中包含着色器所在的文件名。

p定义-
指向宏数组的指针。我们可以将其设置为NULL

pInclude-
这是指向包含接口的指针。如果我们的着色器在文件中使用#include,则我们不能在此处放置NULL,但是我们的着色器没有包含,因此我们将其设置为NULL。

pFunctionName-
这是该文件名中的着色器函数的名称。

pProfile-
您要使用的着色器的版本。Direct3D 11支持着色器版本5.0。但是,在我的笔记本电脑上,我需要将此设置为“ vs_4_0”和“ ps_4_0”。

标志1-
编译标志,我们将其设置为NULL。

标志2-
效果标志。我们还将其设置为NULL。

pPump-
这与多线程有关。我们将其设置为NULL,以便函数在完成之前不会返回。

ppShader-
这是返回的着色器。它不是实际的着色器,而更像是一个包含着色器和有关该着色器信息的缓冲区。然后,我们将使用此缓冲区创建实际的着色器。

ppErrorMsgs-
这将返回在编译着色器时发生的错误和警告的列表。这些错误和警告与您在调试器底部看到的相同。

pH结果-
这是返回的HRESULT。我们这样做是为了使此函数使用“ hr =”,但是您也可以在此参数中加上“&hr”以完成相同的操作。

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);

创建着色器

(ID3D11Device :: CreateVertexShader()) 首先,我们创建一个名为hr的HRESULT对象,用于进行错误检查。为了使代码更加清晰和简洁,我没有包括错误检查功能,但是在本课程的最后,我将说明如何隐含错误检查功能。
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-
这是指向着色器缓冲区开始的指针。

字节码长度-
这是缓冲区的大小。

pClassLinkage-
指向类链接接口的指针。我们将其设置为NULL。

ppVertexShader-
这是我们返回的顶点着色器。

ppPixelShader-
这是我们返回的像素着色器。

hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);

设置着色器

(ID3D11DeviceContext :: VSSetShader()) 现在,我们已经编译并创建了着色器,我们需要将它们设置为当前的管道着色器。如果要设置顶点着色器,我们可以通过调用ID3D11DeviceContext :: VSSetShader()来实现,如果要设置像素着色器,则可以通过ID3D11DeviceContext :: VSSetShader()来实现(还有其他着色器,我们将在后面学习设置,例如几何着色器)。 在大多数情况下,应用程序将为不同的几何集使用不同的着色器集,例如,稍后在绘制天空盒时将使用单独的像素着色器。因此,您将在运行时而不是仅在场景设置功能中设置着色器。请记住,direct3d是一个“状态机”,它将保持当前状态和设置,直到以后对其进行更改,因此不要期望在代码中设置着色器后,direct3d会将着色器设置回默认值,在渲染材质之前,需要始终设置正确的着色器。渲染状态和其他事物也是如此。我们还将在稍后的章节中讨论渲染状态。
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-
这是ppClassInstances中数组中的类实例的数量。我们将其设置为0,因为没有一个。

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

创建顶点缓冲区

(ID3D11Buffer) 现在我们需要创建顶点缓冲区。我们首先使用“顶点”结构制作一个顶点数组。 拥有一个顶点数组之后,我们将通过填充D3D11_BUFFER_DESC结构并通过调用ZeroMemory()确保其为空来描述我们的顶点缓冲区。D3D11_BUFFER_DESC看起来像这样:
typedef struct D3D11_BUFFER_DESC
{
   UINT             ByteWidth;
   D3D11_USAGE         Usage;
   UINT             BindFlags;
   UINT             CPUAccessFlags;
   UINT             MiscFlags;
   UINT             StructureByteStride;
}    D3D11_BUFFER_DESC;


每个成员的说明如下:

字节宽度-
这是缓冲区的字节大小。

用法-
D3D11_USAGE类型,描述如何读取和写入缓冲区。

BindFlags-
我们指定D3D11_BIND_VERTEX_BUFFER,因为这是一个顶点缓冲区。

CPUAccessFlags-
这表示CPU将如何使用我们的缓冲区。我们可以将其设置为NULL

杂项标志-
我们不会使用的其他标志,也将其设置为NULL

StructureByteStride-
此处不使用,请将其设置为NULL。

现在,我们已经对缓冲区进行了描述,我们需要使用缓冲区中所需的数据填充D3D11_SUBRESOURCE_DATA结构。结构如下:

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


每个成员的说明如下:

pSysMem-
这是我们要放入缓冲区的数据。

SysMemPitch-
这是纹理中从一行到下一行的距离(以字节为单位)。它仅用于2D和3D纹理。

SysMemSlicePitch-
3D纹理中从一个深度级别到下一个深度级别的距离(以字节为单位)。仅用于3D纹理。

现在,我们终于可以使用刚刚创建的缓冲区描述和缓冲区数据创建缓冲区了。要创建缓冲区,我们要做的就是调用ID3D11Device :: CreateBuffer()。该函数如下所示:

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


每个参数的说明如下:

pDesc-
指向我们的缓冲区描述的指针

pInitialData-
指向包含我们要放在此处的数据的子资源数据结构的指针。如果我们以后要添加数据,可以将其设置为NULL。

ppBuffer-
返回的ID3D11Buffer。

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);

设置顶点缓冲区

(ID3D11DeviceContext :: IASetVertexBuffers()) 现在我们有了顶点缓冲区,我们需要将其绑定到IA。我们可以通过调用ID3D11Devicecontext :: IASetVertexBuffers函数来实现:
void IASetVertexBuffers(
   [in]   UINT StartSlot,
   [in]   UINT NumBuffers,
   [in]   ID3D11Buffer *const *ppVertexBuffers,
   [in]   const UINT *pStrides,
   [in]   const UINT *pOffsets
);


每个参数的说明如下:

StartSlot-
这是我们可以绑定到的输入插槽。我们在这里设置0。

NumBuffers-
这是我们绑定的缓冲区数。我们仅绑定1。

ppVertexBuffers-
这是指向我们实际顶点缓冲区的指针。

pStrides-
这是每个顶点的大小。

pOffsets-
从起始位置的缓冲区开始,这是一个字节偏移量。

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

创建输入(顶点)布局

(ID3D11Device :: CreateInputLayout()) 接下来,我们需要创建输入布局。我们可以使用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-
这是包含我们的顶点布局的D3D11_INPUT_ELEMENT_DESC元素的数组。

NumElements-
这是我们顶点布局中元素的数量。

pShaderBytecodeWithInputSignature-
这是我们顶点着色器开始的指针。

字节码长度-
这是我们的顶点着色器的大小。

ppInputLayout-
这是指向我们的输入(顶点)布局的返回指针。

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

设置输入(顶点)布局

(ID3D11DeviceContext :: IASetInputLayout()) 我们已经创建了顶点布局,下一步是将其绑定到IA作为活动输入(顶点)布局。我们可以通过调用函数ID3D11DeviceContext :: IASetInputLayout()来做到这一点:
void STDMETHODCALLTYPE IASetInputLayout( 
   [in]   ID3D11InputLayout *pInputLayout
);


这里唯一的参数是:

pInputLayout-
我们创建的输入布局。

d3d11DevCon->IASetInputLayout( vertLayout );

设置原始拓扑

(ID3D11DeviceContext :: IASetPrimitiveTopology()) 在这里,我们告诉IA我们正在发送什么类型的原语。我们可以通过调用函数ID3D11DeviceContext :: IASetPrimitiveTopology()来设置基本拓扑。这里唯一的参数是D3D11_PRIMITIVE_TOPOLOGY枚举类型。以下是常见类型的列表: 点列表- 我们可以使用D3D10_PRIMITIVE_TOPOLOGY_POINTLIST。通过使用这种拓扑,每个顶点将被绘制为一个单独的点。 线带- 我们可以使用D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP。这基本上就像“连接点”。所有顶点将成为线的一部分。 行列表- 我们可以使用D3D10_PRIMITIVE_TOPOLOGY_LINELIST。每两个顶点将创建一条线。该线段与线段的区别在于,在线段中,所有顶点都将连接以创建线,即连续线。 三角带- 我们可以使用D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP。在这里,我们创建三角形。每个三角形与相邻的三角形共享其顶点。所有三角形将被连接。 三角形清单- 我们可以使用D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST。这就是说,每3个顶点构成一个三角形,因此不必连接所有三角形。它比三角带慢,因为必须使用更多的顶点,这与三角带不同,在三角带中,您可以拥有2个由4个顶点构成的三角形。在三角形列表中,需要具有6个顶点才能创建2个三角形。 具有邻接关系的基元- 一个示例是D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ。这些仅用于几何体着色器。我们暂时不会真正担心它们。
d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );

创建视口

(D3D11_VIEWPORT) 现在剩下要做的就是创建并设置视口。视口将告诉管道的RS阶段绘制什么。我们可以使用D3D11_VIEWPORT结构创建视口。视口将创建一个以像素为单位的正方形,光栅化程序将使用该正方形来找到在窗口的客户区域中显示几何图形的位置。当我们引入深度缓冲区时,您还将使用视口。我们可以设置最小和最大深度值,通常在0到1之间。然后,OM将根据其深度值决定要显示的像素“片段”。我们希望视口覆盖整个窗口客户区域,因此将框的左上角设置为0,0,将框的右下角设置为Width,Height(以像素为单位)。
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = Width;
viewport.Height = Height;

设置视口

(ID3D11DeviceContext :: RSSetViewports()) 创建视口后,需要使用功能ID3D11DeviceContext :: RSSetViewports()将其绑定到管道的RS阶段。第一个参数是要绑定的视口的数量,第二个参数是指向视口数组的指针。在这里您可以拥有多个“窗口”,例如一个用于播放器1,一个用于播放器2。
d3d11DevCon->RSSetViewports(1, &viewport);

渲染图元

(ID3D11DeviceContext :: Draw()) 现在,我们进入DrawScene()函数,使用包含4个值的浮点数组RBGA将背景颜色改回黑色。这里的新行是Draw函数。第一个参数是要绘制的顶点数量,第二个参数是要开始绘制的顶点数组起点的偏移量。
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);
}

效果文件(明暗器)

(顶点着色器) 让我们现在转到效果文件。我们有一个非常非常非常简单的效果文件,其中仅包含一个顶点和像素着色器。并且这些功能中的每一个都发挥了最小作用。在这里,我们将顶点着色器命名为“ VS”,并将其参数设置为接受称为“ Pos”的4d浮点值。IA将在我们的顶点结构中发送位置元素的位置为“ POSITION”,因为我们已在输入布局中告知它发送位置元素。您可以将“ POSITION”更改为其他任何内容,只要您记得在顶点布局中进行更改即可。我们在这里所做的唯一一件事就是将Pos值返回到管道中的下一个活动阶段。
float4 VS(float4 inPos : POSITION) : SV_POSITION
{
    return inPos;
}

效果文件(明暗器)

(像素着色器) 在本课程中,我们要做的最后一件事是创建Pixel Shader函数。该函数唯一要做的就是返回从光栅化器阶段传递给它的每个像素的蓝色。但是,稍后,我们将从顶点着色器返回纹理坐标,法线和/或颜色值,并将其作为此像素着色器的输入。然后,我们可以使用所有这些值来确定像素的最终颜色。这也是我们将隐含基于像素的照明的地方。

行使:

在这一课中,我们画了一个三角形。 1.尝试画一个正方形。提示->>(顶点缓冲区) 2.尝试画线和点。提示->>(原始拓扑) 3.更改基本体的颜色。提示->>(像素着色器) 4.仅在客户区的1/4上显示场景。提示->>(视口) 完整代码如下:

//Include and link appropriate libraries and headers//
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")

#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <D3DX10.h>
#include <xnamath.h>

//Global Declarations - Interfaces//
IDXGISwapChain* SwapChain;
ID3D11Device* d3d11Device;
ID3D11DeviceContext* d3d11DevCon;
ID3D11RenderTargetView* renderTargetView;

///**************new**************
ID3D11Buffer* triangleVertBuffer;
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;
///**************new**************

//Global Declarations - Others//
LPCTSTR WndClassName = L"firstwindow";
HWND hwnd = NULL;
HRESULT hr;

const int Width  = 300;
const int Height = 300;

//Function Prototypes//
bool InitializeDirect3d11App(HINSTANCE hInstance);
void CleanUp();
bool InitScene();
void UpdateScene();
void DrawScene();

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool windowed);
int messageloop();

LRESULT CALLBACK WndProc(HWND hWnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam);

///**************new**************
//Vertex Structure and Vertex Layout (Input Layout)//
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);
///**************new**************

int WINAPI WinMain(HINSTANCE hInstance,    //Main windows function
    HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine,
    int nShowCmd)
{

    if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true))
    {
        MessageBox(0, L"Window Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    if(!InitializeDirect3d11App(hInstance))    //Initialize Direct3D
    {
        MessageBox(0, L"Direct3D Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    if(!InitScene())    //Initialize our scene
    {
        MessageBox(0, L"Scene Initialization - Failed",
            L"Error", MB_OK);
        return 0;
    }

    messageloop();

    CleanUp();    

    return 0;
}

bool InitializeWindow(HINSTANCE hInstance,
    int ShowWnd,
    int width, int height,
    bool windowed)
{
    typedef struct _WNDCLASS {
        UINT cbSize;
        UINT style;
        WNDPROC lpfnWndProc;
        int cbClsExtra;
        int cbWndExtra;
        HANDLE hInstance;
        HICON hIcon;
        HCURSOR hCursor;
        HBRUSH hbrBackground;
        LPCTSTR lpszMenuName;
        LPCTSTR lpszClassName;
    } WNDCLASS;

    WNDCLASSEX wc;

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = NULL;
    wc.cbWndExtra = NULL;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 2);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = WndClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Error registering class",    
            L"Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    hwnd = CreateWindowEx(
        NULL,
        WndClassName,
        L"Lesson 4 - Begin Drawing",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        width, height,
        NULL,
        NULL,
        hInstance,
        NULL
        );

    if (!hwnd)
    {
        MessageBox(NULL, L"Error creating window",
            L"Error", MB_OK | MB_ICONERROR);
        return 1;
    }

    ShowWindow(hwnd, ShowWnd);
    UpdateWindow(hwnd);

    return true;
}

bool InitializeDirect3d11App(HINSTANCE hInstance)
{
    //Describe our Buffer
    DXGI_MODE_DESC bufferDesc;

    ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));

    bufferDesc.Width = Width;
    bufferDesc.Height = Height;
    bufferDesc.RefreshRate.Numerator = 60;
    bufferDesc.RefreshRate.Denominator = 1;
    bufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    //Describe our SwapChain
    DXGI_SWAP_CHAIN_DESC swapChainDesc; 

    ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

    swapChainDesc.BufferDesc = bufferDesc;
    swapChainDesc.SampleDesc.Count = 1;
    swapChainDesc.SampleDesc.Quality = 0;
    swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapChainDesc.BufferCount = 1;
    swapChainDesc.OutputWindow = hwnd; 
    swapChainDesc.Windowed = TRUE; 
    swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;


    //Create our SwapChain
    hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL,
        D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);

    //Create our BackBuffer
    ID3D11Texture2D* BackBuffer;
    hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer );

    //Create our Render Target
    hr = d3d11Device->CreateRenderTargetView( BackBuffer, NULL, &renderTargetView );
    BackBuffer->Release();

    //Set our Render Target
    d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, NULL );

    return true;
}

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

///**************new**************
bool InitScene()
{
    //Compile Shaders from shader file
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_4_0", 0, 0, 0, &VS_Buffer, 0, 0);
    hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_4_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
    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;
}
///**************new**************

void UpdateScene()
{

}

///**************new**************
void DrawScene()
{
    //Clear our backbuffer
    float bgColor[4] = {(0.0f, 0.0f, 0.0f, 0.0f)};
    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);

    //Draw the triangle
    d3d11DevCon->Draw( 3, 0 );

    //Present the backbuffer to the screen
    SwapChain->Present(0, 0);
}
///**************new**************

int messageloop(){
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    while(true)
    {
        BOOL PeekMessageL( 
            LPMSG lpMsg,
            HWND hWnd,
            UINT wMsgFilterMin,
            UINT wMsgFilterMax,
            UINT wRemoveMsg
            );

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;
            TranslateMessage(&msg);    
            DispatchMessage(&msg);
        }
        else{
            // run game code            
            UpdateScene();
            DrawScene();
        }
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam)
{
    switch( msg )
    {
    case WM_KEYDOWN:
        if( wParam == VK_ESCAPE ){
            DestroyWindow(hwnd);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd,
        msg,
        wParam,
        lParam);
}
posted @ 2022-04-06 23:53  szmtjs10  阅读(80)  评论(0编辑  收藏  举报