使用Dx11渲染纹理2——渲染三角形

渲染管线(Pipeline)

image.png
  • Input-Assember Stage 输入装配阶段
    输入:顶点缓存、顶点索引缓存
    输出:三角形
    该阶段为指定三角形顶点位置的阶段。

渲染三角形步骤

  1. 创建顶点
    比如:
Vertex vertices[] =
{
    // float   |  unsigned char
    // x    y     r   g  b  a
    { 0.0f,0.5f, 255, 0, 0, 0 },
    { 0.5f,-0.5f, 0, 255, 0, 0 },
    { -0.5f,-0.5f, 0, 0, 255, 0 }
};
  1. 创建顶点缓存
    D3D11_BUFFER_DESC描述顶点缓存,D3D11_SUBRESOURCE_DATA存放真实顶点数据。
wrl::ComPtr<ID3D11Buffer> pVertexBuffer;
D3D11_BUFFER_DESC bd = {};
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.Usage = D3D11_USAGE_DEFAULT;
bd.CPUAccessFlags = 0u;
bd.MiscFlags = 0u;
bd.ByteWidth = sizeof( vertices );
bd.StructureByteStride = sizeof( Vertex );
D3D11_SUBRESOURCE_DATA sd = {};
sd.pSysMem = vertices;  // 为缓冲区指定初始化数据
GFX_THROW_INFO( pDevice->CreateBuffer( &bd,&sd,&pVertexBuffer ) );
  1. 绑定顶点缓存到管线
    使用ID3D11DeviceContext::IASetVertexBuffers绑定到输入装配器阶段(Input-Assember Stage)。
const UINT stride = sizeof( Vertex );
const UINT offset = 0u;
pContext->IASetVertexBuffers( 0u,1u,pVertexBuffer.GetAddressOf(),&stride,&offset );
  1. 创建顶点索引(如有必要)
  2. 创建顶点索引缓存(如有必要)
  3. 绑定顶点索引缓存到管线(如有必要)
    使用ID3D11DeviceContext::IASetIndexBuffer绑定到输入装配器阶段(Input-Assember Stage)。
  4. 设置图元拓扑
    图元拓扑表示告诉D3D,输入的顶点如何相连。
    使用ID3D11DeviceContext::IASetPrimitiveTopology绑定顶点的图元拓扑到输入装配器阶段(Input-Assember Stage)。
  5. 编写着色器(顶点、像素)
    渲染时一定需要顶点着色器。见下述的[创建着色器]。
  6. 编译着色器(顶点、像素)
    可以选择使用D3DCompileFromFile接口在运行时编译着色器,或者在VS中在编译阶段编译着色器(该阶段编译的着色器需使用D3DReadFileToBlob,直接生成着色器对象)。
  7. 创建着色器对象(顶点、像素)
    使用CreateVertexShader/CreatePixelShader创建顶点和像素着色器。
  8. 绑定着色器对象到管线
    使用VSSetShader绑定顶点着色器对象到顶点着色器管线(Vertex-Shader Stage)。
    使用PSSetShader绑定像素着色器对象到像素着色器管线(Pixel-Shader Stage)。
  9. 创建顶点输入布局
    创建顶点输入布局是为了要让着色器知道按照何种规则读取输入的顶点数据。
    使用ID3D11Device::CreateInputLayout创建输入布局。
  10. 绑定顶点输入布局到管线
    使用ID3D11DeviceContext::IASetInputLayout绑定顶点输入布局到到输入装配器阶段(Input-Assember Stage)。
  11. 设置视窗大小
    使用RSSetViewports设置视窗大小。通常我们会把3D场景渲染到整个后台缓冲区上,但也可以把3D场景渲染到后台缓冲区的一个子矩形区域中。
  12. 绑定渲染目标
    像素着色器需要指定一个渲染目标视图,否则像素着色器会不知道输出到哪里。渲染管线中像素着色器之后的Output Merger会输出到渲染目标上。使用OMSetRenderTargets绑定渲染目标到渲染管线Output Merger阶段。
    绑定渲染目标前,需要先创建好渲染目标。使用交换链的后台缓冲创建渲染目标,如下:
wrl::ComPtr<ID3D11Resource> pBackBuffer;
pSwap->GetBuffer( 0,__uuidof(ID3D11Resource),&pBackBuffer);
pDevice->CreateRenderTargetView( pBackBuffer.Get(),nullptr,&pTarget);
  1. Draw
    调用Draw或者DrawIndexed渲染到后台缓冲。
  2. 呈现
    调用IDXGISwapChain::Present将后台缓冲呈现到前台。

着色器

定义着色器结构

// 定义顶点着色器的输入结构体
struct VinputType
{
    float4 position : POSITION; // 顶点位置
    float4 color : COLOR;       // 顶点颜色
};

// 定义顶点着色器的输出结构体/像素着色器的输入结构
struct PinputType
{
    float4 position : SV_POSITION;  // 位置。SV_POSITION像素着色器内部变量
    float4 color : COLOR;           // 颜色
};

编写顶点着色器

当前顶点着色器不做任何转换,直接将从输入装配阶段过来的顶点缓存数据送给像素着色器。
顶点着色器的输入是二维顶点数据,输出是一个四维向量。
只传入顶点位置时,着色器可以写作如下:

//                  自定义的输入语义 | 系统的输出语义(顶点位置)
float4 main(float2 pos : Postion)   : SV_Position 
{
    return float4(pos.x, pos.y, 0.0f, 1.0f);
}

当着色器加上颜色输入时,可以写作如下,输入顶点位置、颜色,输出则需要输出位置和颜色,此时需要定义一个输出结构,作为输出。

struct VSout
{
    //         |   语义
    float4 pos : SV_Position;
    float3 color : Color;       // 传递给像素着色器
}

VSout main(float2 pos : Postion, float3 color : Color)
{
    VSout out;
    out.pos = float4(pos.x, pos.y, 0.0f, 1.0f);
    out.color = color;
    return out;
}
// 入口函数
PinputType ColorVsMain(VinputType input)
{
    PinputType output;

    // w分量不从CPU传入,而默认设置为1.0f
    input.position.w = 1.0f;

    output.position = input.position;
    output.color = input.color;

    return output;
}

编写像素着色器

像素着色器在每个像素上运行。光栅化决定了像素的位置,像素着色器仅仅决定每个像素的颜色,它通常不需要像素在屏幕上的位置来完成它的工作。
像素着色器返回颜色值,颜色值为一个float4类型,包含了r g b a四分量。
输出语义SV_TARGET表示渲染目标。

//                                   | 输出语义
float4 ColorPsMain(PinputType input) : SV_TARGET
{
    // return float4(1.0f, 1.0f, 1.0f, 1.0f);   // 常量颜色
    return input.color;
}

着色器环境配置

VS上创建一个shader文件夹,里面存放各类型的着色器文件。
在VS中添加一个着色器文件:


image.png

指定项目类型为HLSL编译器:


image.png

配置入口函数、着色器类型:
image.png

配置输出目录和名称:
image.png

编译着色器

方法1:运行期间编译着色器代码,生成字节码,使用D3DCompileFromFile接口编译原始着色器文件。其他方法参考:DirectX11--HLSL编译着色器的三种方法

HRESULT D3DCompileFromFile(
    LPCWSTR pFileName,                  // [In]要编译的.hlsl文件
    CONST D3D_SHADER_MACRO* pDefines,   // [In_Opt]忽略
    ID3DInclude* pInclude,              // [In_Opt]如何应对#include宏
    LPCSTR pEntrypoint,                 // [In]入口函数名
    LPCSTR pTarget,                     // [In]使用的着色器模型
    UINT Flags1,                        // [In]D3DCOMPILE系列宏
    UINT Flags2,                        // [In]D3DCOMPILE_FLAGS2系列宏
    ID3DBlob** ppCode,                  // [Out]获得着色器的二进制块
    ID3DBlob** ppErrorMsgs);            // [Out]可能会获得错误信息的二进制块

注意如果在着色器中添加了引用头文件,则第三个参数ID3DInclude* pInclude需要指定为:D3D_COMPILE_STANDARD_FILE_INCLUDE
例如:

ID3D10Blob* vertexShaderBuffer;
HRESULT hr = D3DCompileFromFile(vsFilename, NULL, D3D_COMPILE_STANDARD_FILE_INCLUDE,
    "TextureVsMain",    // 着色器入口函数 
    "vs_5_0", 
    D3D10_SHADER_ENABLE_STRICTNESS, 
    0,
    &vertexShaderBuffer, &errorMessage);

创建着色器对象

一旦顶点着色器和像素着色器代码成功编译到缓冲区中,我们就可以使用这些缓冲区来创建着色器对象本身。
使用CreateVertexShader/CreatePixelShader来创建定点着色器和像素着色器。

顶点、顶点索引

创建顶点缓冲、索引缓冲

创建缓冲有如下三步:
1.填写一个D3D11_BUFFER_DESC结构体,描述我们所要创建的缓冲区。
2.填写一个D3D11_SUBRESOURCE_DATA结构体,为缓冲区指定初始化数据。
3.调用ID3D11Device::CreateBuffer方法来创建缓冲区。

typedef struct D3D11_BUFFER_DESC{
    UINT ByteWidth;   // 缓冲区大小,单位字节
    D3D11_USAGE Usage; 
    UINT BindFlags; 
    UINT CPUAccessFlags; 
    UINT MiscFlags; 
    UINT StructureByteStride;
} D3D11_BUFFER_DESC; 

缓冲描述结构,具体含义参考:6.2 顶点缓冲

绑定顶点缓冲、索引缓冲到渲染管线

unsigned int stride = sizeof(VertexType);
unsigned int offset = 0;

// 绑定顶点缓存到渲染管线的输入装配阶段(input assembler)
deviceContext->IASetVertexBuffers(0, 1, &m_vertexBuffer, &stride, &offset);
// 绑定索引缓存到渲染管线的输入装配阶段(input assembler)
deviceContext->IASetIndexBuffer(m_indexBuffer, DXGI_FORMAT_R32_UINT, 0);

创建顶点输入布局

顶点数据描述结构体:

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:一个与元素相关的字符串。它可以是任何有效的语义名。语义(semantic)用于将顶点结构体中的元素映射为顶点着色器参数,如下图。


    顶点结构体中的每个元素分别由D3D11_INPUT_ELEMENT_DESC数组中的对应元素描述。语义名和语义索引提供了一种将顶点元素映射为顶点着色器参数的方法。
  • SemanticIndex:附加在语义上的索引值。上图说明了使用该索引的原因;举例来说,当顶点结构体包含多组纹理坐标时,我们不是添加一个新的语义名,而是在语义名的后面加上一个索引值。在着色器代码中没有指定索引的语义默认索引为0,例如,在上图中的POSITION相当于POSITION0。
  • Format:一个用于指定元素格式的DXGI_FORMAT枚举类型成员。DXGI_FORMAT_R32G32B32_FLOAT表示传入给着色器3个4字节的数据。
    更多参数说明可以参考:6.1 顶点和顶点布局
// 创建顶点数据布局
const D3D11_INPUT_ELEMENT_DESC inputElementDescs[] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },  // 位置分量 x/y/z
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 8u, D3D11_INPUT_PER_VERTEX_DATA, 0 },  // 颜色分量 r/g/b/a
};

device->CreateInputLayout(
    inputElementDescs, 
    sizeof(inputElementDescs)/sizeof(D3D11_INPUT_ELEMENT_DESC),
    m_vertexShaderBuffer->GetBufferPointer(),
    m_vertexShaderBuffer->GetBufferSize(), 
    &m_inputLayout);

绑定渲染目标到渲染管线

ID3D11DeviceContext::OMSetRenderTargets() 
* 绑定一个或更多渲染目标和深度模板缓冲区到管线的输出合并器阶段 
* @param NumViews            将要绑定的渲染目标的数量  
* @param ppRenderTargetViews 将要绑定的渲染目标视图数组中的第一个元素的指针 
* @param pDepthStencilView   将要绑定到管线的深度/模板视图 

为后台缓冲区和深度缓冲区创建视图后,就可以将这些视图绑定到渲染管线的输出合并器阶段(output merger stage),使些资源成为管线的渲染目标和深度/模板缓冲区。

完整代码见:使用git log可以查看渲染三角形的过程
参考:Direct3D 11教程2:绘制一个三角形

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容