精华内容
下载资源
问答
  • DirectX 11

    2018-08-28 17:09:19
    Rendering techniques about DirectX 11. To Learn or study.
  • DirectX11

    2018-10-24 14:44:46
    DirectX11 With Windows SDK--09 纹理映射与采样器状态 阅读目录 前言 纹理坐标系 纹理读取 DDS位图和WIC位图 DDSTextureLoader和WICTextureLoader库 CreateDDSTextureFromFile函数--从文件读取DD...

    https://www.cnblogs.com/X-Jun/p/9297810.html

    DirectX11 With Windows SDK--09 纹理映射与采样器状态

    阅读目录

    回到顶部

    前言

    在之前的DirectX SDK中,纹理的读取使用的是D3DX11CreateShaderResourceViewFromFile函数,现在在Windows SDK中已经没有这些函数,我们需要找到DDSTextureLoaderWICTextureLoader这两个库来读取DDS位图和WIC位图

    DirectX11 With Windows SDK完整目录

    Github项目源码

    欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

    回到顶部

    纹理坐标系

    纹理坐标系和屏幕、图片坐标系的有些相似,它们的U轴都是水平朝右,V轴竖直向下。但是纹理的X和Y的取值范围都为[0.0, 1.0],分别映射到[0, Width][0, Height]

    对于一个3D的三角形,通过给这三个顶点额外的纹理坐标信息,那么三个纹理坐标就可以映射到纹理指定的某片三角形区域。

    这样的话已知三个顶点的坐标p0,p1p2以及三个纹理坐标q0,q1q2,就可以求出顶点坐标映射与纹理坐标的对应关系:
    (x,y,z) = p0 + s(p1 - p0) + t(p2 - p0)

    (u,v) = q0 + s(q1 - q0) + t(q2 - q0)

    其中需要满足s >= 0,t >= 0s + t <= 1

    所以顶点结构体的内容会有所变化:

    struct VertexPosNormalTex
    {
        DirectX::XMFLOAT3 pos;
        DirectX::XMFLOAT3 normal;
        DirectX::XMFLOAT2 tex;
        static const D3D11_INPUT_ELEMENT_DESC inputLayout[3];
    };

    对应的每个输入元素的描述为:

    const D3D11_INPUT_ELEMENT_DESC VertexPosNormalTex::inputLayout[3] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0}
    };

    回到顶部

    纹理读取

    DDS位图和WIC位图

    DDS是一种图片格式,是DirectDraw Surface的缩写,它是DirectX纹理压缩(DirectX Texture Compression,简称DXTC)的产物。由NVIDIA公司开发。大部分3D游戏引擎都可以使用DDS格式的图片用作贴图,也可以制作法线贴图。

    WIC(Windows Imaging Component)是一个可以扩展的平台,为数字图像提供底层API,它可以支持bmp、dng、ico、jpeg、png、tiff等格式的位图。

    DDSTextureLoader和WICTextureLoader库

    要使用这两个库,有两种方案。

    第一种:在DirectXTex中找到DDSTextureLoader文件夹和WICTextureLoader文件夹中分别找到对应的头文件和源文件(不带12的),并加入到你的项目中

    第二种:将DirectXTK库添加到你的项目中,这里不再赘述

    这之后就可以包含DDSTextureLoader.hWICTextureLoader.h进项目中了。

    CreateDDSTextureFromFile函数--从文件读取DDS纹理

    现在读取DDS纹理的操作变得更简单了:

    HRESULT CreateDDSTextureFromFile(
        ID3D11Device* d3dDevice,                // [In]D3D设备
        const wchar_t* szFileName,              // [In]dds图片文件名
        ID3D11Resource** texture,               // [Out]输出一个指向资源接口类的指针,也可以填nullptr
        ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptr
        size_t maxsize = 0,                     // [In]忽略
        DDS_ALPHA_MODE* alphaMode = nullptr);  // [In]忽略

    下面是一个调用的例子:

    // 初始化木箱纹理
    HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, mWoodCrate.GetAddressOf()));

    CreateWICTextureFromFile函数--从文件读取WIC纹理

    函数原型如下:

    HRESULT CreateWICTextureFromFile(
        ID3D11Device* d3dDevice,                // [In]D3D设备
        const wchar_t* szFileName,              // [In]wic所支持格式的图片文件名
        ID3D11Resource** texture,               // [Out]输出一个指向资源接口类的指针,也可以填nullptr
        ID3D11ShaderResourceView** textureView, // [Out]输出一个指向着色器资源视图的指针,也可以填nullptr
        size_t maxsize = 0);                     // [In]忽略

    下面是一个调用的例子:

    // 初始化火焰纹理
    WCHAR strFile[40];
    mFireAnim.resize(120);
    for (int i = 1; i <= 120; ++i)
    {
        wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i);
        HR(CreateWICTextureFromFile(md3dDevice.Get(), strFile, nullptr, mFireAnim[i - 1].GetAddressOf()));
    }

    回到顶部

    过滤器

    图片的放大

    图片在经过放大操作后,除了图片原有的像素被拉伸,还需要对其余空缺的像素位置选用合适的方式来进行填充。比如一个2x2位图被拉伸成8x8的,除了角上4个像素,还需要对其余60个像素进行填充。下面介绍几种方法

    常量插值法

    对于2x2位图,它的宽高表示范围都为[0,1],而8x8位图的都为[0,7],且只允许取整数。那么对于放大后的像素点(1, 4)就会映射到(1/7, 4/7)上。

    常量插值法的做法十分简单粗暴,就是对X和Y值都进行四舍五入操作,然后取邻近像素点的颜色。比如对于映射后的值如果落在[20.5, 21.5)的范围,最终都会取21。根据上面的例子,最终会落入到像素点(0, 1)上,然后取该像素点的颜色。

    线性插值法

    现在只讨论一维情况,已知第20个像素点的颜色p0和第21个像素点的颜色p1,并且经过拉伸放大后,有一个像素点落在范围(20, 21)之间,我们就可以使用线性插值法求出最终的颜色(t取(0,1)):

    p = t*p1 + (1 - t)*p0

    对于二维情况,会有三种使用线性插值法的情况:

    1. X方向使用常量插值法,Y方向使用线性插值法
    2. X方向使用线性插值法,Y方向使用常量插值法
    3. X和Y方向均使用线性插值法

    左图使用了常量插值法,右图使用了二维线性插值法

    图片的缩小

    图片在经过缩小操作后,需要抛弃掉一些像素。这里我们可以使用mipmapping技术,以额外牺牲一些内存代价的方式来获得高效的拟合效果,这里估计使用的是金字塔下采样的原理。一张512x512的纹理,通过不断的向下采样,可以获得256x256、128x128、64x64...一直到1x1的一系列位图,这些位图构建了一条mipmap链,并且不同的纹理标注有不同的mipmap等级

    接下来会有两种情况:

    1. 选取mipmap等级对应图片和缩小后的图片大小最接近的一张,然后进行线性插值法或者常量插值法,这种方式叫做点过滤(point filtering)
    2. 选取两张mipmap等级相邻的图片,使得缩小后的图片大小在那两张位图之间,然后对这两张位图进行常量插值法或者线性插值法分别取得颜色结果,最后对两个颜色结果进行线性插值,这种方式叫做线性过滤(linear filtering)。

    各向异性过滤

    Anisotropic Filtering可以帮助我们处理那些不与屏幕平行的平面,需要额外使用平面的法向量和摄像机的观察方向向量。虽然使用该种过滤器会有比较大的性能损耗,但是能诞生出比较理想的效果。

    下面左图使用了线性过滤法,右边使用的是各向异性过滤,可以看到顶面纹理比左边的更加清晰

    回到顶部

    对纹理进行采样

    HLSL代码的变动

    Basic.hlsli代码如下:

    #include "LightHelper.hlsli"
    
    Texture2D tex : register(t0);
    SamplerState samLinear : register(s0);
    
    
    cbuffer VSConstantBuffer : register(b0)
    {
        matrix gWorld; 
        matrix gView;  
        matrix gProj;  
        matrix gWorldInvTranspose;
    }
    
    cbuffer PSConstantBuffer : register(b1)
    {
        DirectionalLight gDirLight[10];
        PointLight gPointLight[10];
        SpotLight gSpotLight[10];
        Material gMaterial;
        int gNumDirLight;
        int gNumPointLight;
        int gNumSpotLight;
        float3 gEyePosW;
    }
    
    
    struct VertexIn
    {
        float3 PosL : POSITION;
        float3 NormalL : NORMAL;
        float2 Tex : TEXCOORD;
    };
    
    struct VertexOut
    {
        float4 PosH : SV_POSITION;
        float3 PosW : POSITION;     // 在世界中的位置
        float3 NormalW : NORMAL;    // 法向量在世界中的方向
        float2 Tex : TEXCOORD;
    };
    

    Basic_VS_2D.hlsl的代码:

    // Basic_VS_2D.hlsl
    #include "Basic.hlsli"
    
    // 顶点着色器(2D)
    VertexOut VS_2D(VertexIn pIn)
    {
        VertexOut pOut;
        pOut.PosH = float4(pIn.Pos, 1.0f);
        pOut.PosW = float3(0.0f, 0.0f, 0.0f);
        pOut.NormalW = pIn.Normal;
        pOut.Tex = pIn.Tex;
        return pOut;
    }

    Basic_PS_2D.hlsl的代码:

    // Basic_PS_2D.hlsl
    #include "Basic.hlsli"
    
    // 像素着色器(2D)
    float4 PS_2D(VertexOut pIn) : SV_Target
    {
        return tex.Sample(samLinear, pIn.Tex);
    }

    Basic_VS_3D.hlsl的代码:

    // Basic_VS_3D.hlsl
    #include "Basic.hlsli"
    
    // 顶点着色器(3D)
    VertexOut VS_3D(VertexIn pIn)
    {
        VertexOut pOut;
        matrix viewProj = mul(gView, gProj);
        float4 posW = mul(float4(pIn.PosL, 1.0f), gWorld);
    
        pOut.PosH = mul(posW, viewProj);
        pOut.PosW = posW.xyz;
        pOut.NormalW = mul(pIn.NormalL, (float3x3) gWorldInvTranspose);
        pOut.Tex = pIn.Tex;
        return pOut;
    }
    

    Basic_PS_3D.hlsl的代码:

    // Basic_PS_3D.hlsl
    #include "Basic.hlsli"
    
    // 像素着色器(3D)
    float4 PS_3D(VertexOut pIn) : SV_Target
    {
        // 标准化法向量
        pIn.NormalW = normalize(pIn.NormalW);
    
        // 顶点指向眼睛的向量
        float3 toEyeW = normalize(gEyePosW - pIn.PosW);
    
        // 初始化为0 
        float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
        float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
        float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);
        float4 A = float4(0.0f, 0.0f, 0.0f, 0.0f);
        float4 D = float4(0.0f, 0.0f, 0.0f, 0.0f);
        float4 S = float4(0.0f, 0.0f, 0.0f, 0.0f);
        int i;
        // 强制展开循环以减少指令数
        [unroll]
        for (i = 0; i < gNumDirLight; ++i)
        {
            ComputeDirectionalLight(gMaterial, gDirLight[i], pIn.NormalW, toEyeW, A, D, S);
            ambient += A;
            diffuse += D;
            spec += S;
        }
        
        [unroll]
        for (i = 0; i < gNumPointLight; ++i)
        {
            ComputePointLight(gMaterial, gPointLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
            ambient += A;
            diffuse += D;
            spec += S;
        }
        
        [unroll]
        for (i = 0; i < gNumSpotLight; ++i)
        {
            ComputeSpotLight(gMaterial, gSpotLight[i], pIn.PosW, pIn.NormalW, toEyeW, A, D, S);
            ambient += A;
            diffuse += D;
            spec += S;
        }
        
    
        float4 texColor = tex.Sample(samLinear, pIn.Tex);
        float4 litColor = texColor * (ambient + diffuse) + spec;
        litColor.a = texColor.a * gMaterial.Diffuse.a;
        
        return litColor;
    }

    其中Texture2D类型保存了2D纹理的信息,在这是全局变量。而register(t0)对应起始槽索引0.

    SamplerState类型确定采样器应如何进行采样,同样也是全局变量,register(s0)对应起始槽索引0.

    上述两种变量都需要在C++应用层中初始化和绑定后才能使用。

    Texture2D类型拥有Sample方法,需要提供采样器状态和2D纹理坐标方可使用,然后返回一个包含RGBA信息的float4向量。

    [unroll]属性用于展开循环,避免不必要的跳转,但可能会产生大量的指令

    除此之外,上面的HLSL代码允许每种灯光最多10盏,然后还提供了2D和3D版本的顶点/像素着色器供使用。

    注意Basic.fxLightHelper.hlsli是不参与生成的。其余着色器文件需要按照第2章的方式去设置好。

    ID3D11Device::CreateSamplerState方法--创建采样器状态

    在C++代码层中,我们需要通过D3D设备创建采样器状态,然后绑定到渲染管线中,使得在HLSL中可以根据过滤器、寻址模式等进行采样。

    在创建采样器状态之前,需要先填充结构体D3D11_SAMPLER_DESC来描述采样器状态:

    typedef struct D3D11_SAMPLER_DESC
    {
        D3D11_FILTER Filter;                    // 所选过滤器
        D3D11_TEXTURE_ADDRESS_MODE AddressU;    // U方向寻址模式
        D3D11_TEXTURE_ADDRESS_MODE AddressV;    // V方向寻址模式
        D3D11_TEXTURE_ADDRESS_MODE AddressW;    // W方向寻址模式
        FLOAT MipLODBias;   // mipmap等级偏移值,最终算出的mipmap等级会加上该偏移值
        UINT MaxAnisotropy;                     // 最大各向异性等级(1-16)
        D3D11_COMPARISON_FUNC ComparisonFunc;   // 这节不讨论
        FLOAT BorderColor[ 4 ];     // 边界外的颜色,使用D3D11_TEXTURE_BORDER_COLOR时需要指定
        FLOAT MinLOD;   // 若mipmap等级低于MinLOD,则使用等级MinLOD。最小允许设为0
        FLOAT MaxLOD;   // 若mipmap等级高于MaxLOD,则使用等级MaxLOD。必须比MinLOD大        
    }   D3D11_SAMPLER_DESC;

    D3D11_FILTER部分枚举含义如下:

    枚举值 缩小 放大 mipmap
    D3D11_FILTER_MIN_MAG_MIP_POINT 点采样 点采样 点采样
    D3D11_FILTER_MIN_MAG_POINT_MIP_LINEAR 点采样 点采样 线性采样
    D3D11_FILTER_MIN_POINT_MAG_LINEAR_MIP_POINT 点采样 线性采样 点采样
    D3D11_FILTER_MIN_MAG_MIP_LINEAR 线性采样 线性采样 线性采样
    D3D11_FILTER_ANISOTROPIC 各向异性 各向异性 各向异性

    D3D11_TEXTURE_ADDRESS_MODE是单个方向的寻址模式,有时候纹理坐标会超过1.0或者小于0.0,这时候寻址模式可以解释边界外的情况,含义如下:

    D3D11_TEXTURE_ADDRESS_WRAP使用重复的纹理去覆盖整个实数域,可以当做fmod(X, 1.0f)

    D3D11_TEXTURE_ADDRESS_MIRROR用重复的纹理覆盖整个实数域,只不过相邻的两个纹理满足按交线对称

    D3D11_TEXTURE_ADDRESS_CLAMP对U轴和V轴,小于0的值都取作0,大于1的值都取作1

    D3D11_TEXTURE_BORDER_COLOR对于纹理范围外的区域都使用BorderColor进行填充

    D3D11_TEXTURE_ADDRESS_MIRROR_ONCE相当于MIRROR和CLAMP的结合,仅[-1,1]的范围内镜像有效,其余范围都会取到-1或者1

    最后就是ID3D11Device::CreateSamplerState方法:

    HRESULT ID3D11Device::CreateSamplerState( 
        const D3D11_SAMPLER_DESC *pSamplerDesc, // [In]采样器状态描述
        ID3D11SamplerState **ppSamplerState);   // [Out]输出的采样器

    接下来演示了如何创建采样器状态;

    // 初始化采样器状态描述
    D3D11_SAMPLER_DESC sampDesc;
    ZeroMemory(&sampDesc, sizeof(sampDesc));
    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
    sampDesc.MinLOD = 0;
    sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
    HR(md3dDevice->CreateSamplerState(&sampDesc, mSamplerState.GetAddressOf()));

    ID3D11DeviceContext::PSSetSamplers方法--像素着色阶段设置采样器状态

    void ID3D11DeviceContext::PSSetSamplers(
        UINT StartSlot,     // [In]起始槽索引
        UINT NumSamplers,   // [In]采样器状态数目
        ID3D11SamplerState * const * ppSamplers);   // [In]采样器数组  

    根据前面的HLSL代码,samLinear使用了索引为0起始槽,所以需要这样调用:

    // 像素着色阶段设置好采样器
    md3dImmediateContext->PSSetSamplers(0, 1, mSamplerState.GetAddressOf());

    这样HLSL中对应的采样器状态就可以使用了。

    回到顶部

    GameApp类的变动

    GameApp::InitEffect的变动

    现在我们需要编译出4个着色器,2个顶点布局,以区分2D和3D的部分。

    bool GameApp::InitEffect()
    {
        ComPtr<ID3DBlob> blob;
    
        // 创建顶点着色器(2D)
        HR(CreateShaderFromFile(L"HLSL\\Basic_VS_2D.vso", L"HLSL\\Basic_VS_2D.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
        HR(md3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mVertexShader2D.GetAddressOf()));
        // 创建顶点布局(2D)
        HR(md3dDevice->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout),
            blob->GetBufferPointer(), blob->GetBufferSize(), mVertexLayout2D.GetAddressOf()));
    
        // 创建像素着色器(2D)
        HR(CreateShaderFromFile(L"HLSL\\Basic_PS_2D.pso", L"HLSL\\Basic_PS_2D.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
        HR(md3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mPixelShader2D.GetAddressOf()));
    
        // 创建顶点着色器(3D)
        HR(CreateShaderFromFile(L"HLSL\\Basic_VS_3D.vso", L"HLSL\\Basic_VS_3D.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
        HR(md3dDevice->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mVertexShader3D.GetAddressOf()));
        // 创建顶点布局(3D)
        HR(md3dDevice->CreateInputLayout(VertexPosNormalTex::inputLayout, ARRAYSIZE(VertexPosNormalTex::inputLayout),
            blob->GetBufferPointer(), blob->GetBufferSize(), mVertexLayout3D.GetAddressOf()));
    
        // 创建像素着色器(3D)
        HR(CreateShaderFromFile(L"HLSL\\Basic_PS_3D.pso", L"HLSL\\Basic_PS_3D.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
        HR(md3dDevice->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mPixelShader3D.GetAddressOf()));
    
        return true;
    }

    GameApp::InitResource的变化

    虽然现在允许同时放入多盏灯光了,但在该项目我们只使用一盏点光灯,并且仅用于3D木盒的显示。对于2D火焰动画,实质上是由120张bmp位图构成,我们需要按顺序在每一帧切换下一张位图来达到火焰在动的效果。

    bool GameApp::InitResource()
    {
        // 初始化网格模型并设置到输入装配阶段
        Geometry::MeshData meshData = Geometry::CreateBox();
        ResetMesh(meshData);
    
        // ******************
        // 设置常量缓冲区描述
        D3D11_BUFFER_DESC cbd;
        ZeroMemory(&cbd, sizeof(cbd));
        cbd.Usage = D3D11_USAGE_DEFAULT;
        cbd.ByteWidth = sizeof(VSConstantBuffer);
        cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        cbd.CPUAccessFlags = 0;
        // 新建用于VS和PS的常量缓冲区
        HR(md3dDevice->CreateBuffer(&cbd, nullptr, mConstantBuffers[0].GetAddressOf()));
        cbd.ByteWidth = sizeof(PSConstantBuffer);
        HR(md3dDevice->CreateBuffer(&cbd, nullptr, mConstantBuffers[1].GetAddressOf()));
    
        // ******************
        // 初始化纹理和采样器状态
        
        // 初始化木箱纹理
        HR(CreateDDSTextureFromFile(md3dDevice.Get(), L"Texture\\WoodCrate.dds", nullptr, mWoodCrate.GetAddressOf()));
        // 初始化火焰纹理
        WCHAR strFile[40];
        mFireAnim.resize(120);
        for (int i = 1; i <= 120; ++i)
        {
            wsprintf(strFile, L"Texture\\FireAnim\\Fire%03d.bmp", i);
            HR(CreateWICTextureFromFile(md3dDevice.Get(), strFile, nullptr, mFireAnim[i - 1].GetAddressOf()));
        }
            
        // 初始化采样器状态
        D3D11_SAMPLER_DESC sampDesc;
        ZeroMemory(&sampDesc, sizeof(sampDesc));
        sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
        sampDesc.MinLOD = 0;
        sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
        HR(md3dDevice->CreateSamplerState(&sampDesc, mSamplerState.GetAddressOf()));
    
        
        // ******************
        // 初始化常量缓冲区的值
    
        // 初始化用于VS的常量缓冲区的值
        mVSConstantBuffer.world = XMMatrixIdentity();           
        mVSConstantBuffer.view = XMMatrixTranspose(XMMatrixLookAtLH(
            XMVectorSet(0.0f, 0.0f, -5.0f, 0.0f),
            XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f),
            XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f)
        ));
        mVSConstantBuffer.proj = XMMatrixTranspose(XMMatrixPerspectiveFovLH(XM_PIDIV2, AspectRatio(), 1.0f, 1000.0f));
        mVSConstantBuffer.worldInvTranspose = XMMatrixIdentity();
        
        // 初始化用于PS的常量缓冲区的值
        // 这里只使用一盏点光来演示
        mPSConstantBuffer.pointLight[0].Position = XMFLOAT3(0.0f, 0.0f, -10.0f);
        mPSConstantBuffer.pointLight[0].Ambient = XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f);
        mPSConstantBuffer.pointLight[0].Diffuse = XMFLOAT4(0.7f, 0.7f, 0.7f, 1.0f);
        mPSConstantBuffer.pointLight[0].Specular = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
        mPSConstantBuffer.pointLight[0].Att = XMFLOAT3(0.0f, 0.1f, 0.0f);
        mPSConstantBuffer.pointLight[0].Range = 25.0f;
        mPSConstantBuffer.numDirLight = 0;
        mPSConstantBuffer.numPointLight = 1;
        mPSConstantBuffer.numSpotLight = 0;
        mPSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f);   // 这里容易遗漏,已补上
        // 初始化材质
        mPSConstantBuffer.material.Ambient = XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f);
        mPSConstantBuffer.material.Diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
        mPSConstantBuffer.material.Specular = XMFLOAT4(0.1f, 0.1f, 0.1f, 5.0f);
        // 注意不要忘记设置此处的观察位置,否则高亮部分会有问题
        mPSConstantBuffer.eyePos = XMFLOAT4(0.0f, 0.0f, -5.0f, 0.0f);
    
        // ******************
        // 给渲染管线各个阶段绑定好所需资源
        // 设置图元类型,设定输入布局
        md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
        // 默认绑定3D着色器
        md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
        // VS常量缓冲区对应HLSL寄存于b0的常量缓冲区
        md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
        // PS常量缓冲区对应HLSL寄存于b1的常量缓冲区
        md3dImmediateContext->PSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
        // 像素着色阶段设置好采样器
        md3dImmediateContext->PSSetSamplers(0, 1, mSamplerState.GetAddressOf());
        md3dImmediateContext->PSSetShaderResources(0, 1, mWoodCrate.GetAddressOf());
        md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
    
    
        return true;
    }

    GameApp::UpdateScene的变化

    该项目可以选择播放3D木箱或2D火焰动画,则需要有个当前正在播放内容的状态值,并随之进行更新。

    其中ShowMode是一个枚举类,可以选择WoodCrate或者FireAnim

    void GameApp::UpdateScene(float dt)
    {
    
        // 键盘切换灯光类型
        Keyboard::State state = mKeyboard->GetState();
        mKeyboardTracker.Update(state); 
    
        // 键盘切换模式
        if (mKeyboardTracker.IsKeyPressed(Keyboard::D1))
        {
            // 播放木箱动画
            mCurrMode = ShowMode::WoodCrate;
            md3dImmediateContext->IASetInputLayout(mVertexLayout3D.Get());
            Geometry::MeshData meshData = Geometry::CreateBox();
            ResetMesh(meshData);
            // 输入装配阶段的索引缓冲区设置
            md3dImmediateContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
            md3dImmediateContext->VSSetShader(mVertexShader3D.Get(), nullptr, 0);
            md3dImmediateContext->PSSetShader(mPixelShader3D.Get(), nullptr, 0);
            md3dImmediateContext->PSSetShaderResources(0, 1, mWoodCrate.GetAddressOf());
        }
        else if (mKeyboardTracker.IsKeyPressed(Keyboard::D2))
        {
            mCurrMode = ShowMode::FireAnim;
            mCurrFrame = 0;
            md3dImmediateContext->IASetInputLayout(mVertexLayout2D.Get());
            Geometry::MeshData meshData = Geometry::Create2DShow();
            ResetMesh(meshData);
            // 输入装配阶段的索引缓冲区设置
            md3dImmediateContext->IASetIndexBuffer(mIndexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
            md3dImmediateContext->VSSetShader(mVertexShader2D.Get(), nullptr, 0);
            md3dImmediateContext->PSSetShader(mPixelShader2D.Get(), nullptr, 0);
            md3dImmediateContext->PSSetShaderResources(0, 1, mFireAnim[0].GetAddressOf());
        }
    
        if (mCurrMode == ShowMode::WoodCrate)
        {
            // 更新常量缓冲区,让立方体转起来
            static float phi = 0.0f, theta = 0.0f;
            phi += 0.00003f, theta += 0.00005f;
            XMMATRIX W = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
            mVSConstantBuffer.world = XMMatrixTranspose(W);
            mVSConstantBuffer.worldInvTranspose = XMMatrixInverse(nullptr, W);  // 两次转置抵消
            md3dImmediateContext->UpdateSubresource(mConstantBuffers[0].Get(), 0, nullptr, &mVSConstantBuffer, 0, 0);
        }
        else if (mCurrMode == ShowMode::FireAnim)
        {
            // 用于限制在1秒60帧
            static float totDeltaTime = 0;
    
            totDeltaTime += dt;
            if (totDeltaTime > 1.0f / 60)
            {
                totDeltaTime -= 1.0f / 60;
                mCurrFrame = (mCurrFrame + 1) % 120;
                md3dImmediateContext->PSSetShaderResources(0, 1, mFireAnim[mCurrFrame].GetAddressOf());
            }       
        }
    }

    GameApp::DrawScene的变化

    在这里只有引导内容的变化:

    void GameApp::DrawScene()
    {
        assert(md3dImmediateContext);
        assert(mSwapChain);
    
        md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
        md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
        
        // 绘制几何模型
        md3dImmediateContext->DrawIndexed(mIndexCount, 0, 0);
    
        // 绘制Direct2D部分
        md2dRenderTarget->BeginDraw();
        static const WCHAR* textStr = L"切换显示: 1-木箱(3D) 2-火焰(2D)\n";
        md2dRenderTarget->DrawTextW(textStr, (UINT32)wcslen(textStr), mTextFormat.Get(),
            D2D1_RECT_F{ 0.0f, 0.0f, 600.0f, 200.0f }, mColorBrush.Get());
        HR(md2dRenderTarget->EndDraw());
    
        HR(mSwapChain->Present(0, 0));
    }

    最终的显示效果如下:

    DirectX11 With Windows SDK完整目录

    Github项目源码

    欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

    分类: DirectX11 With Windows SDK

    展开全文
  • DirectX11-源码

    2021-02-08 06:52:49
    DirectX11
  • DirectX11 实例

    2016-04-14 10:17:56
    DirectX11 实例。
  • DirectX 11?

    2020-12-30 07:31:11
    1. Are you planning to add DirectX 11 to otclient? 2. Is there a way to compile it with DirectX 11? <p>and final question.. Is (will?) DX11 faster then DX9 for so simple game?</p><p>该提问来源于开源...
  • DirectX11教程

    2012-08-22 17:19:25
    DirectX11教程,最新最全的DirectX11技术,喜欢DirectX的同学收藏吧。
  • DirectX11-Portfolio-源码

    2021-03-27 10:10:46
    DirectX11-Portfolio
  • 微软DirectX11教程

    2018-01-18 20:35:40
    微软directx11教程可以帮助初学者学习如何掌握directx进行图像学的开发!
  • cmakewin32directx11 执照:麻省理工学院 创建于:Lightnet 包装方式: imgui 1.82 Directx SDK 2010年6月(可选) cmake 3.20 Visual Studio 2019(需要编译器工具生成器) C / C ++ cmake DirectX SDK 窗位...
  • directx11Last month we threw out the challenge to create something intensely-crazy-amazing in Unity 4 with DirectX 11. We put forward $10,000 and a trip to the greatest conference ever (Unite 2013 in ...

    directx11

    Last month we threw out the challenge to create something intensely-crazy-amazing in Unity 4 with DirectX 11. We put forward $10,000 and a trip to the greatest conference ever (Unite 2013 in Vancouver of course) as a reward to capture the awesome.

    上个月,我们提出了挑战,要求使用DirectX 11在Unity 4中创建一个让人疯狂的东西。我们提出了10,000美元的奖励,并参加了有史以来最伟大的会议(当然是温哥华的Unite 2013),以表彰他们表现出色。

    We had a great turnout of submissions which made judging one tough cookie, so as well as presenting the Grand Prize Winner and runners up, we have a little showcase of everything that made us giddy. A massive THANK YOU to everyone who entered, we hope you all had fun and learned something new.

    我们收到了大量提交意见,这使得他们很难判断一个饼干,因此除了展示大奖获得者和亚军之外,我们还展示了一些让我们头晕的东西。 衷心感谢所有参加的人,我们希望大家都玩得开心,学到了一些新东西。

    So without further ado, I present to you, the Winner, Runners Up and Honorable Mentions.

    因此,事不宜迟,我向您介绍获胜者,亚军和荣誉奖。

    Check out official page with more judges notes here!

    在这里查看官方页面,其中有更多评委笔记

    WINNER! The Museum of the Microstar.

    优胜者! 微星博物馆。

    演示地址

    RUST LTD and co did a marvelous job of putting together this interactive demo, it not only showcases impressive technical ability and great visuals, it also encompses a narrative, making it an all round great project. Check out more of their stuff at http://www.rustltd.com/

    RUST LTD和公司在组合此交互式演示方面做了出色的工作,它不仅展示了令人印象深刻的技术能力和出色的视觉效果,还囊括了叙述,使其成为一个全方位的出色项目。 在http://www.rustltd.com/上查看更多他们的东西

    The Team.

    团队。

    RUST LTD: Anton Hand‐ Game Direction, 3D Art, Visual Effects, Adam Liszkiewicz ‐ Narrative Design, Writing, Lucas Miller ‐ Graphic Design, Writing, Luke Noonan ‐ Development, Sound Design, Writing, Narration RUST AFFILIATE: Joshua Ols ‐ Shader Development TINYCUBE STUDIO: Arthur Brussee ‐ Particle System Development

    防锈LTD:安东-游戏方向,3D艺术,视觉效果, 亚当Liszkiewicz -叙事设计,写作, 卢卡斯·米勒 -平面设计,写作, 卢克·努南 -发展,完善的设计,写作,叙述 锈附属公司: 约书亚OLS -着色器开发 TINYCUBE STUDIO: Arthur Brussee- 粒子系统开发

    1st Runner Up – Voxel Cone Tracing.

    第一名-体素锥追踪。

    Kurt Loeffer

    库尔特·洛夫(Kurt Loeffer)

    演示地址

    2nd Runner Up – DirectX 11 Demo

    亚军-DirectX 11演示

    Davit Naskidashvili

    戴维特·纳斯基达什维利

    演示地址

    Honorable Mentions.

    荣誉奖。

    And now for a showcase of great projects that were entered.

    现在,展示已进入的重大项目。

    Light submitted by Sergey Noscow

    Sergey Noscow提交的光

    Deserted building scene. Really great environment modeling, lighting and use of HDR rendering with postprocessing effects.

    废弃的建筑场景。 真正出色的环境建模,照明以及具有后处理效果的HDR渲染的使用。

    Sail Ship submitted by George Notyag

    乔治·Notyag提交的帆船

    The most tessellated ropes and guns you’ve ever seen on a ship! But seriously this looks great.

    您在船上见过的最精致的绳索和枪支! 但是说真的,这看起来很棒。

    Happiness submitted by Maciej Gornicki

    Maciej Gornicki提交的幸福

    A sweet and artistically driven demo about happiness.

    一个关于幸福的甜美且艺术驱动的演示。

    Interactive Water Simulation submitted by Laurens Mathot

    Laurens Mathot提交的交互式水模拟

    Nice use of compute shaders for simulating water flow and buoyancy for objects.

    可以很好地使用计算着色器来模拟对象的水流和浮力。

    翻译自: https://blogs.unity3d.com/2013/02/01/directx-11-competition/

    directx11

    展开全文
  • DirectX11 With Windows SDK--01 DirectX11初始化 原文:DirectX11 With Windows SDK--01 DirectX11初始化前言 由于个人觉得龙书里面第4章提供的Direct3D 初始化项目封装得比较好,而且DirectX SDK ...
    原文:DirectX11 With Windows SDK--01 DirectX11初始化

    前言

    由于个人觉得龙书里面第4章提供的Direct3D 初始化项目封装得比较好,而且DirectX SDK Samples里面的初始化程序过于精简,不适合后续使用,故选择了以Init Direct3D项目作为框架,然后还使用了微软提供的示例项目,两者结合到一起。建议下载项目配合阅读。

    这一章内容大部分属于龙书的内容,但仍有一些不同的地方。因为后续的所有项目都使用该基础框架,你也可以直接使用第一章的项目源码,然后需要了解以下差异部分:

    1. ComPtr智能指针
    2. 新的HR宏
    3. D3D11.1设备的创建

    其中前面两个部分在下面的链接可以看到:

    章节
    ComPtr智能指针
    HR宏关于dxerr库的替代方案

    DirectX11 With Windows SDK完整目录

    Github项目源码

    欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

    如何开启新项目

    安装准备

    如果你打算从VS2015开始,则在安装的时候需要勾选下列选项:

    1172605-20190412090511814-1568191909.png

    编程语言那一项会自动被勾选。

    而如果是使用VS2017或19的话,则在安装的时候需要勾选下列选项:

    1172605-20190412090732766-1578829189.png

    注意:VS2015使用的Windows SDK版本为10.0.14393.0,而VS2017与VS2019使用的Windows SDK版本为10.0.17763.0

    安装完成后,新建项目需要从空项目开始。

    1172605-20190412165100574-1059040710.png

    移除你的项目中有关DX SDK的库路径和包含路径

    如果你曾经用过DX SDK来编写DX项目,务必要把你之前配置的DX SDK库路径和包含路径给清理掉,使用项目默认的库路径和包含路径!

    链接静态库

    这里的每一个项目都需要包含静态库:d3d11.lib,dxgi.lib,dxguid.lib,D3DCompiler.libwinmm.lib。可以在d3dApp.h添加下面的语句:

    #pragma comment(lib, "d3d11.lib")
    #pragma comment(lib, "dxgi.lib")
    #pragma comment(lib, "dxguid.lib")
    #pragma comment(lib, "D3DCompiler.lib")
    #pragma comment(lib, "winmm.lib")

    也可以在项目属性-链接器-输入-附加依赖项 添加上面的库。

    1172605-20181222222131075-159253660.png

    字符集设置为Unicode

    在项目属性页中可以直接进行修改。

    1172605-20181222222206381-1198412375.png

    Win7系统下的额外配置

    由于Win10 SDK中的某些函数在Win7是不支持的,我们还需要在属性页-配置属性-C/C++ -预处理器中,添加预处理器定义以限制API集合:_WIN32_WINNT=0x601

    1172605-20181228110808689-1958314464.png

    现在最基本的配置已经完成,你可以尝试将本教程用到的项目01中所有的头文件和源文件添加进你的项目,然后生成项目并运行以检测。

    项目结构

    现在把目光拉回到我们的教程项目。目前项目中包含头文件的具体功能如下:

    头文件 功能
    d3dApp.h Direct3D应用程序框架类
    d3dUtil.h 包含一些常用头文件及自己编写的函数
    DXTrace.h 包含了HR宏与DXTraceW函数
    GameApp.h 游戏应用程序扩展类,游戏逻辑在这里实现,继承自D3DApp类
    GameTimer.h 游戏计时器类

    其中d3dApp类和GameTimer类是龙书源码提供的,我们可以搬运过来,但是对d3dApp框架类我们还需要进行大幅度修改,毕竟我们的最终目的就是要完全脱离旧的DirectX SDK,使用Windows SDK来实现DX11。修改完成后,d3dApp就几乎已经定型而不需要我们操心了。

    GameApp类则是我们编写游戏逻辑的地方,这里需要进行逐帧的更新及绘制。

    D3DApp框架类

    D3DApp.h展示了框架类的声明,这里的接口类指针全部换上了ComPtr智能指针:

    class D3DApp
    {
    public:
        D3DApp(HINSTANCE hInstance);              // 在构造函数的初始化列表应当设置好初始参数
        virtual ~D3DApp();
    
        HINSTANCE AppInst()const;                 // 获取应用实例的句柄
        HWND      MainWnd()const;                 // 获取主窗口句柄
        float     AspectRatio()const;             // 获取屏幕宽高比
    
        int Run();                                // 运行程序,进行游戏主循环
    
                                                  // 框架方法。客户派生类需要重载这些方法以实现特定的应用需求
        virtual bool Init();                      // 该父类方法需要初始化窗口和Direct3D部分
        virtual void OnResize();                  // 该父类方法需要在窗口大小变动的时候调用
        virtual void UpdateScene(float dt) = 0;   // 子类需要实现该方法,完成每一帧的更新
        virtual void DrawScene() = 0;             // 子类需要实现该方法,完成每一帧的绘制
        virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
        // 窗口的消息回调函数
    protected:
        bool InitMainWindow();      // 窗口初始化
        bool InitDirect3D();        // Direct3D初始化
    
        void CalculateFrameStats(); // 计算每秒帧数并在窗口显示
    
    protected:
    
        HINSTANCE m_hAppInst;        // 应用实例句柄
        HWND      m_hMainWnd;        // 主窗口句柄
        bool      m_AppPaused;       // 应用是否暂停
        bool      m_Minimized;       // 应用是否最小化
        bool      m_Maximized;       // 应用是否最大化
        bool      m_Resizing;        // 窗口大小是否变化
        bool      m_Enable4xMsaa;    // 是否开启4倍多重采样
        UINT      m_4xMsaaQuality;   // MSAA支持的质量等级
    
    
        GameTimer m_Timer;           // 计时器
    
        // 使用模板别名(C++11)简化类型名
        template <class T>
        using ComPtr = Microsoft::WRL::ComPtr<T>;
        // Direct3D 11
        ComPtr<ID3D11Device> m_pd3dDevice;                    // D3D11设备
        ComPtr<ID3D11DeviceContext> m_pd3dImmediateContext;   // D3D11设备上下文
        ComPtr<IDXGISwapChain> m_pSwapChain;                  // D3D11交换链
        // Direct3D 11.1
        ComPtr<ID3D11Device1> m_pd3dDevice1;                  // D3D11.1设备
        ComPtr<ID3D11DeviceContext1> m_pd3dImmediateContext1; // D3D11.1设备上下文
        ComPtr<IDXGISwapChain1> m_pSwapChain1;                // D3D11.1交换链
        // 常用资源
        ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer;        // 深度模板缓冲区
        ComPtr<ID3D11RenderTargetView> m_pRenderTargetView;   // 渲染目标视图
        ComPtr<ID3D11DepthStencilView> m_pDepthStencilView;   // 深度模板视图
        D3D11_VIEWPORT m_ScreenViewport;                      // 视口
    
        // 派生类应该在构造函数设置好这些自定义的初始参数
        std::wstring m_MainWndCaption;                       // 主窗口标题
        int m_ClientWidth;                                   // 视口宽度
        int m_ClientHeight;                                  // 视口高度
    };
    

    而在d3dApp.cpp中,可以看到有一个全局变量g_pd3dApp

    namespace
    {
        // This is just used to forward Windows messages from a global window
        // procedure to our member function window procedure because we cannot
        // assign a member function to WNDCLASS::lpfnWndProc.
        D3DApp* g_pd3dApp = 0;
    }

    设置该全局变量是因为在窗口创建的时候需要绑定一个回调函数,受到回调函数指针类型的限制,我们不可以绑定d3dApp::MainWndProc的成员方法,所以还需要实现一个全局函数用于回调函数的绑定:

    LRESULT CALLBACK
    MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        // Forward hwnd on because we can get messages (e.g., WM_CREATE)
        // before CreateWindow returns, and thus before m_hMainWnd is valid.
        return g_pd3dApp->MsgProc(hwnd, msg, wParam, lParam);
    }

    D3DApp::InitWindowD3DApp::MsgProc方法目前在这里不做过多描述,因为这不是教程的重点部分,但后续可能还要回头修改这两个方法。有兴趣的可以去MSDN查阅这些函数和结构体的信息。

    Direct3D初始化

    注意当前项目使用的是d3d11_1.h头文件

    Direct3D初始化阶段首先需要创建D3D设备D3D设备上下文

    D3D设备(ID3D11Device)包含了创建各种所需资源的方法,最常用的有:资源类(ID3D11Resource, 包含纹理和缓冲区)视图类以及着色器

    D3D设备上下文(ID3D11DeviceContext)可以看做是一个渲染管线,负责渲染工作,它需要绑定来自D3D设备创建的各种资源、视图和着色器才能正常运转,除此之外,它还能够负责对资源的直接读写操作。

    而如果支持Direct3D 11.1的话,则对应的接口类为:ID3D11Device1ID3D11DeviceContext1,它们分别继承自上面的两个接口类,区别在于额外提供了少数新的接口,并且接口方法的实现可能会有所区别。

    现在,我们从D3DApp::InitDirect3D方法开始,一步步进行分析。

    D3D设备与D3D设备上下文的创建

    D3D11CreateDevice函数--创建D3D设备与D3D设备上下文

    创建D3D设备、D3D设备上下文使用如下函数:

    HRESULT WINAPI D3D11CreateDevice(
        IDXGIAdapter* pAdapter,         // [In_Opt]适配器
        D3D_DRIVER_TYPE DriverType,     // [In]驱动类型
        HMODULE Software,               // [In_Opt]若上面为D3D_DRIVER_TYPE_SOFTWARE则这里需要提供程序模块
        UINT Flags,                     // [In]使用D3D11_CREATE_DEVICE_FLAG枚举类型
        D3D_FEATURE_LEVEL* pFeatureLevels,  // [In_Opt]若为nullptr则为默认特性等级,否则需要提供特性等级数组
        UINT FeatureLevels,             // [In]特性等级数组的元素数目
        UINT SDKVersion,                // [In]SDK版本,默认D3D11_SDK_VERSION
        ID3D11Device** ppDevice,        // [Out_Opt]输出D3D设备
        D3D_FEATURE_LEVEL* pFeatureLevel,   // [Out_Opt]输出当前应用D3D特性等级
        ID3D11DeviceContext** ppImmediateContext ); //[Out_Opt]输出D3D设备上下文
    1. 关于pAdapter(适配器),我们可以将它看做是对显示卡设备的一层封装,通过该参数,我们可以指定需要使用哪个显示卡设备。通常该参数我们设为nullptr,这样就可以交由上层驱动来帮我们决定使用哪个显卡,或者在NVIDIA控制面板来设置当前程序要使用哪个显卡。如果想要在应用层决定,使用IDXGIFactory::EnumAdapters方法可以枚举当前可用的显示卡设备。在最底下的练习题你将学会如何指定显示卡设备来创建Direct3D 11.x设备。
    2. DriverType则指定了驱动类型,不过通常大多数情况都会支持D3D_DRIVER_TYPE_HARDWARE,以享受硬件加速带来的效益。现在我们建立一个驱动数组,然后自己通过for循环的方式进行轮询:
    // 驱动类型数组
    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,       // 硬件驱动
        D3D_DRIVER_TYPE_WARP,           // WARP驱动
        D3D_DRIVER_TYPE_REFERENCE,      // 软件驱动
    };
    UINT numDriverTypes = ARRAYSIZE(driverTypes);

    如果D3D_DRIVER_TYPE_HARDWARE不支持,则需要自己通过循环的形式再检查D3D_DRIVER_TYPE_WARP是否支持。

    关于D3D_DRIVER_TYPE的详细描述,可以去查阅MSDN官方文档详细了解一下。

    1. Flags对应的是D3D11_CREATE_DEVICE_FLAG枚举值,如果需要D3D设备调试的话(在Debug模式下),可以指定D3D11_CREATE_DEVICE_DEBUG枚举值。指定该值后,可以在出现程序异常的时候观察调试输出窗口的信息。
    2. pFeatureLevels是一个特性等级数组,通过函数内部进行轮询以检测所支持的特性等级:
    // 特性等级数组
    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
    };
    UINT numFeatureLevels = ARRAYSIZE(featureLevels);

    注意:如果你的系统支持Direct3D 11.1的API,却把pFeatureLevels设置为nullptrD3D11CreateDevice将创建出特性等级为D3D_FEATURE_LEVEL_11_0的设备。而如果你的系统不支持Direct3D 11.1的API,D3D11CreateDevice会立即停止特性数组的轮询并返回E_INVALIDARG。为此,你必须要从D3D_FEATURE_LEVEL_11_0或更低特性等级开始轮询。

    在Win10, Win8.x 或 Win7 SP1且安装了KB2670838补丁的系统都支持Direct3D 11.1的API,而纯Win7系统仅支持Direct3D 11的API

    从上面的描述我们可以得知,特性等级D3D设备的版本并不是互相对应的:

    1. 特性等级的支持情况取决于当前使用的显示适配器

    2. D3D设备的版本取决于所处的系统

    由于该函数可以创建Direct3D 11.1(或者Direct3D 11.0)的设备与设备上下文,但都统一输出ID3D11DeviceID3D11DeviceContext。如果想要查看是否支持Direct3D 11.1的API,可以使用下面的方式:

    ComPtr<ID3D11Device1> md3dDevice1;
    HRESULT hr = md3dDevice.As(&md3dDevice1);

    同理,想要查看是否支持Direct3D 11.2的API,则可以这样:

    ComPtr<ID3D11Device2> md3dDevice2;
    HRESULT hr = md3dDevice.As(&md3dDevice2);

    由于每个电脑的显示卡设备情况有所差异,该教程采用的是默认显示卡(有可能会用到集成显卡),而不是指定显示卡:

    HRESULT hr = S_OK;
    
    // 创建D3D设备 和 D3D设备上下文
    UINT createDeviceFlags = 0;
    #if defined(DEBUG) || defined(_DEBUG)  
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
    #endif
    // 驱动类型数组
    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = ARRAYSIZE(driverTypes);
    
    // 特性等级数组
    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
    };
    UINT numFeatureLevels = ARRAYSIZE(featureLevels);
    
    D3D_FEATURE_LEVEL featureLevel;
    D3D_DRIVER_TYPE d3dDriverType;
    for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
    {
        d3dDriverType = driverTypes[driverTypeIndex];
        hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
            D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContext.GetAddressOf());
    
        if (hr == E_INVALIDARG)
        {
            // Direct3D 11.0 的API不承认D3D_FEATURE_LEVEL_11_1,所以我们需要尝试特性等级11.0以及以下的版本
            hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
                D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContext.GetAddressOf());
        }
    
        if (SUCCEEDED(hr))
            break;
    }
    
    if (FAILED(hr))
    {
        MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
        return false;
    }
    
    // 检测是否支持特性等级11.0或11.1
    if (featureLevel != D3D_FEATURE_LEVEL_11_0 && featureLevel != D3D_FEATURE_LEVEL_11_1)
    {
        MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);
        return false;
    }
    
    // 检测 MSAA支持的质量等级
    md3dDevice->CheckMultisampleQualityLevels(
        DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m_4xMsaaQuality);
    assert(m_4xMsaaQuality > 0);

    注意:

    1. 支持特性等级11_0的显示适配器必然支持所有渲染目标纹理格式的4倍多重采样
    2. 即便m_4xMsaaQuality的返回值为1,也不代表没法启动4倍多重采样,该成员只是代表模式的种类数目

    DXGI初始化

    DXGI交换链

    DXGI交换链(IDXGISwapChain)缓存了一个或多个表面(2D纹理),它们都可以称作后备缓冲区(backbuffer)。后备缓冲区则是我们主要进行渲染的场所,我们可以将这些缓冲区通过合适的手段成为渲染管线的输出对象。在进行呈现(Present)的时候有两种方法:

    1. BitBlt Model(位块传输模型):将后备缓冲区的数据进行BitBlt(位块传输),传入到DX应用中的桌面窗口管理器(DWM)表面,然后进行翻转以交给前台显示。使用这种模型至少需要一个后备缓冲区。事实上,这也是Win32应用程序最常使用的方式,在进行呈现后,渲染管线仍然是对同一个后备缓冲区进行输出。(支持Windows 7及更高版本)
    2. Flip Model(翻转模型):该模型可以避免上一种方式多余的复制,后备缓冲区表面可以直接与桌面窗口管理器(DWM)内的表面进行翻转。但是需要创建至少两个后备缓冲区,并且在每次完成呈现后切换到另一个后备缓冲区进行渲染。该模型可以用于Win32应用程序以及UWP应用程序(需要DXGI1.2,支持Windows 8及更高版本)

    1172605-20190412094917001-570041129.png

    注意:考虑到要兼容Win7系统,而且由于我们编写的是Win32应用程序,因此这里使用的是第一种模型。同时这也是绝大多数教程所使用的。对第二种感兴趣的可以了解下面的链接:

    DXGI翻转模型

    接下来我们需要了解D3D与DXGI各版本的对应关系,这十分重要:

    Direct3D API支持版本 对应包含DXGI版本 对应DXGI接口 可枚举的显示适配器 可创建的交换链
    Direct3D 11.1 DXGI 1.2 IDXGIFactory2 IDXGIAdaptor2 IDXGISwapChain1
    Direct3D 11.0/10.1 DXGI 1.1 IDXGIFactory1 IDXGIAdaptor1 IDXGISwapChain
    Direct3D 10.0 DXGI 1.0 IDXGIFactory IDXGIAdaptor IDXGISwapChain

    d3d与dxgi版本的对应关系你可以通过观察这些d3d头文件所包含的dxgi头文件来了解。

    DXGI交换链的创建需要通过IDXGIFactory::CreateSwapChain方法进行。但是,如果是要创建Direct3D 11.1对应的交换链,则需要通过IDXGIFactory2::CreateSwapChainForHwnd方法进行。

    获取IDXGIFactory1或IDXGIFactory2接口类

    现在我们需要先拿到包含IDXGIFactory1接口的对象,但是为了拿到该对象还需要经历一些磨难。

    之前在创建D3D设备时使用的是默认的显卡适配器IDXGIAdapter(对于双显卡的笔记本大概率使用的是集成显卡),而创建出来的D3D设备本身实现了IDXGIDevice接口,通过该对象,我们可以获取到当前所用的显卡适配器IDXGIAdapter对象,这样我们再通过查询它的父级找到是哪个IDXGIFactory枚举出来的适配器。

    ComPtr<IDXGIDevice> dxgiDevice = nullptr;
    ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
    ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr;   // D3D11.0(包含DXGI1.1)的接口类
    ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr;   // D3D11.1(包含DXGI1.2)特有的接口类
    
    // 为了正确创建 DXGI交换链,首先我们需要获取创建 D3D设备 的 DXGI工厂,否则会引发报错:
    // "IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory."
    HR(m_pd3dDevice.As(&dxgiDevice));
    HR(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf()));
    HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf())));
    
    // 查看该对象是否包含IDXGIFactory2接口
    hr = dxgiFactory1.As(&dxgiFactory2);
    // 如果包含,则说明支持D3D11.1
    if (dxgiFactory2 != nullptr)
    {
        HR(m_pd3dDevice.As(&m_pd3dDevice1));
        HR(m_pd3dImmediateContext.As(&m_pd3dImmediateContext1));
        // ... 省略交换链IDXGISwapChain1的创建
    }
    else
    {
        // ... 省略交换链IDXGISwapChain的创建
    }

    同时之前也提到,如果支持Direct3D 11.1的话,我们就可以拿到DXGI 1.2的相关对象(如IDXGIFactory2)。

    这时m_pd3dDevicem_pd3dDevice1其实都指向同一个对象,m_pd3dImmediateContextm_pd3dImmediateContext1m_pSwapChainm_pSwapChain1也是一样的,区别仅仅在于后者实现了额外的一些接口,问题不大。因此不管是Direct3D 11.1还是Direct3D 11.0,后续都主要使用m_pd3dDevicem_pd3dImmediateContextm_pSwapChain来进行操作。

    IDXGIFactory2::CreateSwapChainForHwnd方法--Direct3D 11.1创建交换链

    如果是Direct3D 11.1的话,需要先填充DXGI_SWAP_CHAIN_DESC1DXGI_SWAP_CHAIN_FULLSCREEN_DESC这两个结构体:

    typedef struct DXGI_SWAP_CHAIN_DESC1
    {
        UINT Width;                     // 缓冲区宽度
        UINT Height;                    // 缓冲区高度
        DXGI_FORMAT Format;             // 缓冲区数据格式
        BOOL Stereo;                    // 忽略   
        DXGI_SAMPLE_DESC SampleDesc;    // 采样描述
        DXGI_USAGE BufferUsage;         // 缓冲区用途
        UINT BufferCount;               // 缓冲区数目
        DXGI_SCALING Scaling;           // 忽略
        DXGI_SWAP_EFFECT SwapEffect;    // 交换效果
        DXGI_ALPHA_MODE AlphaMode;      // 忽略
        UINT Flags;                     // 使用DXGI_SWAP_CHAIN_FLAG枚举类型
    } DXGI_SWAP_CHAIN_DESC1;
    
    typedef struct DXGI_SAMPLE_DESC
    {
        UINT Count;                     // MSAA采样数
        UINT Quality;                   // MSAA质量等级
    } DXGI_SAMPLE_DESC;
    
    typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC
    {
        DXGI_RATIONAL RefreshRate;                  // 刷新率
        DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  // 忽略
        DXGI_MODE_SCALING Scaling;                  // 忽略
        BOOL Windowed;                              // 是否窗口化
    } DXGI_SWAP_CHAIN_FULLSCREEN_DESC;
    
    typedef struct DXGI_RATIONAL
    {
        UINT Numerator;                 // 刷新率分子
        UINT Denominator;               // 刷新率分母
    } DXGI_RATIONAL;

    填充好后,Direct3D 11.1使用的创建方法为IDXGIFactory2::CreateSwapChainForHwnd

    HRESULT IDXGIFactory2::CreateSwapChainForHwnd(
        IUnknown *pDevice,                      // [In]D3D设备
        HWND hWnd,                              // [In]窗口句柄
        const DXGI_SWAP_CHAIN_DESC1 *pDesc,     // [In]交换链描述1
        const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, // [In]交换链全屏描述,可选
        IDXGIOutput *pRestrictToOutput,         // [In]忽略
        IDXGISwapChain1 **ppSwapChain);         // [Out]输出交换链对象

    上面第一个省略的部分代码如下:

    // 填充各种结构体用以描述交换链
    DXGI_SWAP_CHAIN_DESC1 sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.Width = m_ClientWidth;
    sd.Height = m_ClientHeight;
    sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    // 是否开启4倍多重采样?
    if (m_Enable4xMsaa)
    {
        sd.SampleDesc.Count = 4;
        sd.SampleDesc.Quality = m_4xMsaaQuality - 1;
    }
    else
    {
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
    }
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.BufferCount = 1;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    sd.Flags = 0;
    
    DXGI_SWAP_CHAIN_FULLSCREEN_DESC fd;
    fd.RefreshRate.Numerator = 60;
    fd.RefreshRate.Denominator = 1;
    fd.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    fd.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    fd.Windowed = TRUE;
    // 为当前窗口创建交换链
    HR(dxgiFactory2->CreateSwapChainForHwnd(m_pd3dDevice.Get(), m_hMainWnd, &sd, &fd, nullptr, m_pSwapChain1.GetAddressOf()));
    HR(m_pSwapChain1.As(&m_pSwapChain));

    后续我们还可以通过该交换链来手动指定是否需要全屏

    IDXGIFactory::CreateSwapChain方法--Direct3D 11创建交换链

    如果是Direct3D 11.0的话,需要先填充DXGI_SWAP_CHAIN_DESC结构体:

    typedef struct DXGI_SWAP_CHAIN_DESC
    {
        DXGI_MODE_DESC BufferDesc;      // 缓冲区描述
        DXGI_SAMPLE_DESC SampleDesc;    // 采样描述
        DXGI_USAGE BufferUsage;         // 缓冲区用途
        UINT BufferCount;               // 后备缓冲区数目
        HWND OutputWindow;              // 输出窗口句柄
        BOOL Windowed;                  // 窗口化?
        DXGI_SWAP_EFFECT SwapEffect;    // 交换效果
        UINT Flags;                     // 使用DXGI_SWAP_CHAIN_FLAG枚举类型
    }   DXGI_SWAP_CHAIN_DESC;
    
    typedef struct DXGI_SAMPLE_DESC
    {
        UINT Count;                     // MSAA采样数
        UINT Quality;                   // MSAA质量等级
    } DXGI_SAMPLE_DESC;
    
    typedef struct DXGI_MODE_DESC
    {
        UINT Width;                     // 缓冲区宽度
        UINT Height;                    // 缓冲区高度
        DXGI_RATIONAL RefreshRate;      // 刷新率分数表示法
        DXGI_FORMAT Format;             // 缓冲区数据格式
        DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  // 忽略
        DXGI_MODE_SCALING Scaling;      // 忽略
    } DXGI_MODE_DESC;
    
    typedef struct DXGI_RATIONAL
    {
        UINT Numerator;                 // 刷新率分子
        UINT Denominator;               // 刷新率分母
    } DXGI_RATIONAL;

    Direct3D 11.0下使用的创建方法为IDXGIFactory::CreateSwapChain

    HRESULT IDXGIFactory::CreateSwapChain(
        IUnknown *pDevice,                  // [In]D3D设备
        DXGI_SWAP_CHAIN_DESC *pDesc,        // [In]交换链描述
        IDXGISwapChain **ppSwapChain);      // [Out]输出交换链对象

    第二个省略的部分代码如下:

    // 填充DXGI_SWAP_CHAIN_DESC用以描述交换链
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory(&sd, sizeof(sd));
    sd.BufferDesc.Width = m_ClientWidth;
    sd.BufferDesc.Height = m_ClientHeight;
    sd.BufferDesc.RefreshRate.Numerator = 60;
    sd.BufferDesc.RefreshRate.Denominator = 1;
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    // 是否开启4倍多重采样?
    if (m_Enable4xMsaa)
    {
        sd.SampleDesc.Count = 4;
        sd.SampleDesc.Quality = m_4xMsaaQuality - 1;
    }
    else
    {
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
    }
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    sd.BufferCount = 1;
    sd.OutputWindow = m_hMainWnd;
    sd.Windowed = TRUE;
    sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
    sd.Flags = 0;
    HR(dxgiFactory1->CreateSwapChain(m_pd3dDevice.Get(), &sd, m_pSwapChain.GetAddressOf()));

    禁用ALT+ENTER与全屏的关联

    默认情况下按ALT+ENTER可以切换成全屏,如果不想要这种操作,可以使用刚才创建的dxgiFactory1,按照下面的方式来调用即可:

    dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);

    这样DXGI就不会监听Windows消息队列,并且屏蔽掉了对接收到ALT+ENTER消息的处理。

    DXGI交换链与Direct3D设备的交互

    在创建好上述对象后,如果窗口的大小是固定的,则需要经历下面的步骤:

    1. 获取交换链后备缓冲区的ID3D11Texture2D接口对象
    2. 为后备缓冲区创建渲染目标视图ID3D11RenderTargetView
    3. 通过D3D设备创建一个ID3D11Texture2D用作深度/模板缓冲区,要求与后备缓冲区等宽高
    4. 创建深度/模板视图ID3D11DepthStrenilView,绑定刚才创建的2D纹理
    5. 通过D3D设备上下文,在渲染管线的输出合并阶段设置渲染目标
    6. 在渲染管线的光栅化阶段设置好渲染的视口区域

    1172605-20190412121440039-1684531380.png

    接下来需要快速了解一遍上述步骤所需要用到的API。

    获取交换链的后备缓冲区

    由于此前我们创建好的交换链已经包含1个后备缓冲区了,我们可以通过IDXGISwapChain::GetBuffer方法直接获取后备缓冲区的ID3D11Texture2D接口:

    HRESULT IDXGISwapChain::GetBuffer( 
        UINT Buffer,        // [In]缓冲区索引号,从0到BufferCount - 1
        REFIID riid,        // [In]缓冲区的接口类型ID
        void **ppSurface);  // [Out]获取到的缓冲区

    为后备缓冲区创建渲染目标视图

    渲染目标视图用于将渲染管线的运行结果输出给其绑定的资源,很明显它也只能够设置给输出合并阶段。渲染目标视图要求其绑定的资源是允许GPU读写的,因为在作为管线输出时会通过GPU写入数据,并且在以后进行混合操作时还需要在GPU读取该资源。通常渲染目标是一个二维的纹理,但它依旧可能会绑定其余类型的资源。这里不做讨论。

    现在我们需要将后备缓冲区绑定到渲染目标视图,使用ID3D11Device::CreateRenderTargetView方法来创建:

    HRESULT ID3D11Device::CreateRenderTargetView( 
        ID3D11Resource *pResource,                      // [In]待绑定到渲染目标视图的资源
        const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,     // [In]忽略
        ID3D11RenderTargetView **ppRTView);             // [Out]获取渲染目标视图

    现在这里演示了获取后备缓冲区纹理,并绑定到渲染目标视图的过程:

    // 重设交换链并且重新创建渲染目标视图
    ComPtr<ID3D11Texture2D> backBuffer;
    HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));
    HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));

    创建深度/模板缓冲区

    ID3D11Device::CreateTexture2D--创建一个2D纹理

    除了渲染目标视图外,我们还需要创建深度/模板缓冲区用于深度测试。深度/模板缓冲区也是一个2D纹理,要求其宽度和高度必须要和窗口宽高保持一致。

    通过D3D设备可以新建一个2D纹理,但在此之前我们需要先描述该缓冲区的信息:

    typedef struct D3D11_TEXTURE2D_DESC
    {
        UINT Width;         // 缓冲区宽度
        UINT Height;        // 缓冲区高度
        UINT MipLevels;     // Mip等级
        UINT ArraySize;     // 纹理数组中的纹理数量,默认1
        DXGI_FORMAT Format; // 缓冲区数据格式
        DXGI_SAMPLE_DESC SampleDesc;    // MSAA采样描述
        D3D11_USAGE Usage;  // 数据的CPU/GPU访问权限
        UINT BindFlags;     // 使用D3D11_BIND_FLAG枚举来决定该数据的使用类型
        UINT CPUAccessFlags;    // 使用D3D11_CPU_ACCESS_FLAG枚举来决定CPU访问权限
        UINT MiscFlags;     // 使用D3D11_RESOURCE_MISC_FLAG枚举,这里默认0
    }   D3D11_TEXTURE2D_DESC;   
    

    由于要填充的内容很多,并且目前只有在初始化环节才用到,因此这部分代码可以先粗略看一下,在后续的章节还会详细讲到。

    填充好后,这时我们就可以用方法ID3D11Device::CreateTexture2D来创建2D纹理:

    HRESULT ID3D11Device::CreateTexture2D( 
        const D3D11_TEXTURE2D_DESC *pDesc,          // [In] 2D纹理描述信息
        const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用于初始化的资源
        ID3D11Texture2D **ppTexture2D);             // [Out] 获取到的2D纹理

    下面的代码是关于深度/模板缓冲区创建的完整过程:

    D3D11_TEXTURE2D_DESC depthStencilDesc;
    
    depthStencilDesc.Width = mClientWidth;
    depthStencilDesc.Height = mClientHeight;
    depthStencilDesc.MipLevels = 1;
    depthStencilDesc.ArraySize = 1;
    depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    
    // 要使用 4X MSAA?
    if (mEnable4xMsaa)
    {
        depthStencilDesc.SampleDesc.Count = 4;
        depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality - 1;
    }
    else
    {
        depthStencilDesc.SampleDesc.Count = 1;
        depthStencilDesc.SampleDesc.Quality = 0;
    }
    
    depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
    depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
    depthStencilDesc.CPUAccessFlags = 0;
    depthStencilDesc.MiscFlags = 0;
    
    HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, m_pDepthStencilBuffer.GetAddressOf()));

    创建深度/模板视图

    有了深度/模板缓冲区后,就可以通过ID3D11Device::CreateDepthStencilView方法将创建好的2D纹理绑定到新建的深度/模板视图:

    HRESULT ID3D11Device::CreateDepthStencilView( 
        ID3D11Resource *pResource,                      // [In] 需要绑定的资源
        const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc,     // [In] 深度缓冲区描述,这里忽略
        ID3D11DepthStencilView **ppDepthStencilView);   // [Out] 获取到的深度/模板视图

    演示如下:

    HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(), nullptr, m_pDepthStencilView.GetAddressOf()));

    为渲染管线的输出合并阶段设置渲染目标

    ID3D11DeviceContext::OMSetRenderTargets方法要求同时提供渲染目标视图和深度/模板视图,不过这时我们都已经准备好了:

    void ID3D11DeviceContext::OMSetRenderTargets( 
        UINT NumViews,                                      // [In] 视图数目
        ID3D11RenderTargetView *const *ppRenderTargetViews, // [In] 渲染目标视图数组
        ID3D11DepthStencilView *pDepthStencilView) = 0;     // [In] 深度/模板视图

    因此这里同样也是一句话的事情:

    m_pd3dImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());

    视口设置

    最终我们还需要决定将整个视图输出到窗口特定的范围。我们需要使用D3D11_VIEWPORT来设置视口

    typedef struct D3D11_VIEWPORT
    {
        FLOAT TopLeftX;     // 屏幕左上角起始位置X
        FLOAT TopLeftY;     // 屏幕左上角起始位置Y
        FLOAT Width;        // 宽度
        FLOAT Height;       // 高度
        FLOAT MinDepth;     // 最小深度,必须为0.0f
        FLOAT MaxDepth;     // 最大深度,必须为1.0f
    }   D3D11_VIEWPORT;

    ID3D11DeviceContext::RSSetViewports方法将设置1个或多个视口:

    void ID3D11DeviceContext::RSSetViewports(
        UINT  NumViewports,                     // 视口数目
        const D3D11_VIEWPORT *pViewports);      // 视口数组

    将视图输出到整个屏幕需要按下面的方式进行填充:

    m_ScreenViewport.TopLeftX = 0;
    m_ScreenViewport.TopLeftY = 0;
    m_ScreenViewport.Width    = static_cast<float>(mClientWidth);
    m_ScreenViewport.Height   = static_cast<float>(mClientHeight);
    m_ScreenViewport.MinDepth = 0.0f;
    m_ScreenViewport.MaxDepth = 1.0f;
    
    m_pd3dImmediateContext->RSSetViewports(1, &m_ScreenViewport);

    完成了这六个步骤后,基本的初始化就完成了。但是,如果涉及到窗口大小变化的情况,那么前面提到的后备缓冲区、深度/模板缓冲区、视口都需要重新调整大小。

    D3DApp::OnResize方法

    已知深度模板缓冲区和视口都可以直接重新创建一份来进行替换。至于后备缓冲区,我们可以通过IDXGISwapChain::ResizeBuffers来重新调整后备缓冲区的大小:

    HRESULT IDXGISwapChain::ResizeBuffers(
      UINT        BufferCount,          // [In]缓冲区数目
      UINT        Width,                // [In]缓冲区宽度
      UINT        Height,               // [In]缓冲区高度
      DXGI_FORMAT NewFormat,            // [In]DXGI格式
      UINT        SwapChainFlags        // [In]忽略
    );

    下面的方法演示了在窗口大小发生改变后,以及初次调用时进行的操作:

    void D3DApp::OnResize()
    {
        assert(m_pd3dImmediateContext);
        assert(m_pd3dDevice);
        assert(m_pSwapChain);
    
        if (m_pd3dDevice1 != nullptr)
        {
            assert(m_pd3dImmediateContext1);
            assert(m_pd3dDevice1);
            assert(m_pSwapChain1);
        }
    
        // 释放交换链的相关资源
        m_pRenderTargetView.Reset();
        m_pDepthStencilView.Reset();
        m_pDepthStencilBuffer.Reset();
    
        // 重设交换链并且重新创建渲染目标视图
        ComPtr<ID3D11Texture2D> backBuffer;
        HR(m_pSwapChain->ResizeBuffers(1, m_ClientWidth, m_ClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));
        HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));
        HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));
        
        
        backBuffer.Reset();
    
    
        D3D11_TEXTURE2D_DESC depthStencilDesc;
    
        depthStencilDesc.Width = m_ClientWidth;
        depthStencilDesc.Height = m_ClientHeight;
        depthStencilDesc.MipLevels = 1;
        depthStencilDesc.ArraySize = 1;
        depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    
        // 要使用 4X MSAA? --需要给交换链设置MASS参数
        if (m_Enable4xMsaa)
        {
            depthStencilDesc.SampleDesc.Count = 4;
            depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality - 1;
        }
        else
        {
            depthStencilDesc.SampleDesc.Count = 1;
            depthStencilDesc.SampleDesc.Quality = 0;
        }
        
    
    
        depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
        depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
        depthStencilDesc.CPUAccessFlags = 0;
        depthStencilDesc.MiscFlags = 0;
    
        // 创建深度缓冲区以及深度模板视图
        HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, m_pDepthStencilBuffer.GetAddressOf()));
        HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(), nullptr, m_pDepthStencilView.GetAddressOf()));
    
    
        // 将渲染目标视图和深度/模板缓冲区结合到管线
        m_pd3dImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());
    
        // 设置视口变换
        m_ScreenViewport.TopLeftX = 0;
        m_ScreenViewport.TopLeftY = 0;
        m_ScreenViewport.Width = static_cast<float>(m_ClientWidth);
        m_ScreenViewport.Height = static_cast<float>(m_ClientHeight);
        m_ScreenViewport.MinDepth = 0.0f;
        m_ScreenViewport.MaxDepth = 1.0f;
    
        m_pd3dImmediateContext->RSSetViewports(1, &m_ScreenViewport);
    }

    在后续的部分,该框架的代码基本上不会有什么太大的变动。因此后续代码的添加主要在GameApp类实现。如果现在对上面的一些过程不理解,也是正常的,可以在后续学习到视图相关的知识后再来回看这一整个过程。

    GameApp类

    对于一个初始化应用程序来说,目前GameApp类的非常简单:

    class GameApp : public D3DApp
    {
    public:
        GameApp(HINSTANCE hInstance);
        ~GameApp();
    
        bool Init();
        void OnResize();
        void UpdateScene(float dt);
        void DrawScene();
    };

    GameApp::DrawScene方法--每帧画面的绘制

    ID3D11DeviceContext::ClearRenderTargetView方法--清空需要绘制的缓冲区

    在每一帧画面绘制的操作中,我们需要清理一遍渲染目标视图绑定的缓冲区

    void ID3D11DeviceContext::ClearRenderTargetView(
        ID3D11RenderTargetView *pRenderTargetView,  // [In]渲染目标视图
        const FLOAT  ColorRGBA[4]);                 // [In]指定覆盖颜色

    这里的颜色值范围都是0.0f到1.0f

    比如我们要对后备缓冲区(R8G8B8A8)使用蓝色进行清空,可以这样写:

    float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f}
    m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), blue);

    ID3D11DeviceContext::ClearDepthStencilView方法--清空深度/模板缓冲区

    同样在进行渲染之前,我们也要清理一遍深度/模板缓冲区

    void ID3D11DeviceContext::ClearDepthStencilView(
        ID3D11DepthStencilView *pDepthStencilView,  // [In]深度/模板视图
        UINT ClearFlags,    // [In]D3D11_CLEAR_FLAG枚举
        FLOAT Depth,        // [In]深度
        UINT8 Stencil);     // [In]模板初始值

    若要清空深度缓冲区,则需要指定D3D11_CLEAR_DEPTH,模板缓冲区则是D3D11_CLEAR_STENCIL

    每一次清空我们需要将深度值设为1.0f,模板值设为0.0f。其中深度值1.0f表示距离最远处:

    m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

    IDXGISwapChain::Present方法--前后台缓冲区交换并呈现

    完成一切绘制操作后就可以调用该方法了

    HRESULT ID3D11DeviceContext::Present( 
        UINT SyncInterval,  // [In]通常为0
        UINT Flags);        // [In]通常为0

    GameApp::DrawScene的实现如下:

    void GameApp::DrawScene()
    {
        assert(m_pd3dImmediateContext);
        assert(m_pSwapChain);
        static float blue[4] = { 0.0f, 0.0f, 1.0f, 1.0f };  // RGBA = (0,0,255,255)
        m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), blue);
        m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
    
        HR(m_pSwapChain->Present(0, 0));
    }

    最终绘制的效果应该如下:

    1172605-20181111163838124-826515353.png

    程序退出后的清理

    因为之前我们用的是智能指针,所以D3DApp的析构函数十分简单,只需要通过ID3D11DeviceContext::ClearState方法来恢复D3D设备上下文到默认状态,卸下所有绑定的资源即可。剩下的事情就交给COM智能指针完成:

    D3DApp::~D3DApp()
    {
        // 恢复所有默认设定
        if (m_pd3dImmediateContext)
            m_pd3dImmediateContext->ClearState();
    }

    练习题

    粗体字为自定义题目

    1. 尝试修改项目代码,让窗口内的显示变红。
    2. 某些电脑可能有多于一个的显示适配器(显卡)。首先要通过CreateDXGIFactory创建IDXGIFactory,然后使用IDXGIFactory::EnumAdapters来枚举显示适配器。尝试通过这种方式查看你的电脑有多少个显示适配器(IDXGIAdapter),并察看它们的信息。
    3. 一个显示适配器可能关联了多个输出设备(IDXGIOutput),你可以使用IDXGIAdapter::EnumOutputs方法来枚举出特定的输出,尝试观察它们的信息。
      1172605-20181120125051905-1850534891.png
    4. 对于给定的像素格式,一个输出设备可以支持许多种显示模式(DXGI_MODE_DESC),通过它可以看到全屏宽度、高度、刷新率。尝试使用IDXGIOutput::GetDisplayModeList方法观察所有支持的模式(传递DXGI_FORMAT_R8G8B8A8_UNORM格式进去)。
    5. 默认情况下的窗口程序是可以通过ALT+ENTER来进入/退出全屏的。此外,我们可以通过IDXGISwapChain来动态设置窗口全屏属性,找到对应的方法并尝试一下。
    6. 现在尝试指定显示适配器来创建D3D设备。通过CreateDXGIFactory函数来创建IDXGIFactory,通常它会包含接口IDXGIFactory1,但有可能它也会包含接口IDXGIFactory2。在没有创建D3D设备的情况下,这种方式就可以帮助我们了解是否可以创建出Direct3D 11.1的设备。为了能够指定显示适配器来创建D3D11设备,我们需要将D3D_DRIVER_TYPE强行设置为D3D_DRIVER_TYPE_UNKNOWN,否则在创建设备的时候会得到如下报错信息:DX ERROR: D3D11CreateDevice: When creating a device from an existing adapter (i.e. pAdapter is non-NULL), DriverType must be D3D_DRIVER_TYPE_UNKNOWN. [ INITIALIZATION ERROR #3146141: ]

    DirectX11 With Windows SDK完整目录

    Github项目源码

    欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

    posted on 2019-05-05 09:46 NET未来之路 阅读(...) 评论(...) 编辑 收藏

    转载于:https://www.cnblogs.com/lonelyxmas/p/10811308.html

    展开全文
  • directx11 龙书

    热门讨论 2012-11-17 11:48:43
    directx11 龙书,还可以看看,高手慎入
  • DirectX11学习资料

    2018-04-02 14:32:45
    DirectX11 学习资料,龙书,非常值得学习,内容丰富,可供大家好好学习
  • https://blog.csdn.net/lishuzhai/article/details/52490164?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant....VS2013下DirectX11的配置。 配置步骤: 1.安装Di...

    https://blog.csdn.net/lishuzhai/article/details/52490164?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

    前言:


    VS2013下DirectX11的配置。

    配置步骤:


    1.安装DirectX SDK

    2.配置.h 和.lib文件路径

    3.在VS中链接.lib文件。

    一、下载DirectX SDK(DX11)


    点击打开链接  密码:o0sq

    下载后安装即可。

    二、配置.h 和.lib文件路径:


    在安装好之后,打开VS2013 创建一个工程在解决方案资源管理器的工程名处点击右键,打开“属性”:


    打开属性后,在“配置属性”->VC++ 目录"下的“包含目录”添加你的DirectX安装目录\Include

    如:我的安装路径是:C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Include

    "库目录"添加你的DirectX安装目录的\Lib\x86:

    如:我的安装路径是:C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86

    (要注意用“;”隔开前后目录)

    三、链接.lib文件:


    然后点击“链接器”->“输入”->“附加依赖项”,点击末尾的下拉箭头,选择编辑:


    将下列库文件粘贴到原有文件后面:

    d3d9.lib
    d3dx10d.lib
    d3dx9d.lib
    dxerr.lib
    dxguid.lib
    winmm.lib
    comctl32.lib


    然后确定应用即可。

    这样配置就完成了。

    那么我们检验一下是否配置完成:

    在 DirectX 安装目录下的 Samples\C++\Direct3D\Tutorials\Tut04_Lights 目录下,打开 .sln 文件。
    用 VS2013打开时,如果提示要进行升级,确认升级即可。(我的工程师升级后的,跟刚开始看到的工程有差别)

    升级后运行,如果看到此图则证明配置正确:

    安装过程出现的问题:

    安装SDK时可能会在最后出现:

    错误信息为:Error Code : S1023

    出现 S1023 错误的原因是 VC++运行库不能成功安装,而 VC++运行库不能安装的原因是系统中已经安装了VC++运行库,并且版本等于或高于要安装的版本。

    我们只需要把系统中已经安装的比SDK中VC++运行库版本高的卸载掉即可:
    卸载程序:(只要下面的两个程序版本高于或者等于10.0.30319就将其卸载掉,要注意:不要错误的卸载掉2012或者 2008)

    然后重新安装就解决了。
    ————————————————
    版权声明:本文为CSDN博主「Mr_Lsz」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/lishuzhai/article/details/52490164

    展开全文
  • DirectX11(二)

    2017-06-10 20:26:06
    directX11
  • DirectX11(一)

    2017-06-04 21:36:03
    DirectX11
  • Directx11_SSAO.rar

    2017-10-31 21:12:49
    Directx11_SSAO.rar Directx11_SSAO.rar Directx11_SSAO.rar Directx11_SSAO.rar
  • Tutorial 1: Setting up DirectX 11 with Visual Studio Tutorial 2: Creating a Framework and Window Tutorial 3: Initializing DirectX 11 Tutorial 4: Buffers, Shaders, and HLSL Tutorial 5: ...
  • DirectX 11 新技术

    2014-10-17 13:53:30
    DirectX 11 新技术预览 1,Direct3D11渲染管线: 看上去,DirectX 11DirectX 10更酷。DirectX 11的很多提升意味着更高的特性性能,而这些特性很少能在DX10中看到。DirectX 11DirectX 10两者最大的不同之处在于...
  • DirectX11-ModelViewer-源码

    2021-03-08 09:25:05
    DirectX11-ModelViewer
  • Directx11 龙书 好不容易搞到的,希望对大家有帮助 亚马逊的介绍...
  • Direct11研究 DirectX11勉强用です
  • Beginning DirectX 11 Game Programming
  • DirectX11学习笔记

    千次阅读 多人点赞 2014-10-31 17:45:37
    DirectX11学习笔记
  • 带有DirectX11和Win32的3D引擎。 要求 Visual Studio 19。 cmake 3.11+。 最新的Windows 10 SDK。
  • 泡泡堂 DirectX11 Demo

    2015-09-06 14:00:35
    本程序是模仿泡泡堂游戏功能的Demo,使用DirectX11图形库和C++语言,在VS2010环境下编译。4500行左右。更多内容见本人博客:http://blog.csdn.net/sinat_24229853
  • directx11吐血教程.zip

    2019-01-15 10:23:52
    directx11吐血教程,可以详细帮助你加深对D3D11的了解。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,013
精华内容 2,005
关键字:

directx11