顶点缓存之几何三角
老鸟:怎么样,昨天游戏玩的开心吗,有没有秀出你快乐刀妹的操作。
小白:哇,那当然的,最开心的一把我现在还记忆犹新,我们前期劣势,有一波我们打野“盲僧”
Q到对面。W摸眼,一个R踢飞对面四人,我们中单亚索接大,我接R,直接团灭对面,
一波推家赢得胜利,我同学的几何瞎,画出了一个完美的三角,都可以上起小点了,
可惜没录下来,不然我一定帮他扣7777777777777。
老鸟:三角?哈哈,看来三角在哪里都是艺术的存在,DX11的世界就是由三角形组成的,
而且我们用DX写的第一个图形实例就是一个三角形。
小白:什么都能被你联系到DX,看来我今天又有东西可学了。
老鸟:能学习知识你还不开心,好好听着。在DX11这座军营中,最基本的就是点(士兵),
而两个战士相互配合就成了线,三个士兵(不共线)组合就成了三角形,三角形便能
组成无数多边形,故DX11最基本的阵型就是“铁三角”。
本次博客借鉴了:https://blog.youkuaiyun.com/poem_qianmo/article/details/8276363(浅墨)
https://blog.youkuaiyun.com/BonChoix/article/details/8300939(BonChoix)
顶点缓存使用四步曲之一:设计顶点缓存
老鸟:下面我来介绍DX11最基本的顶点(士兵),士兵也分很多类:有步兵、骑兵、弓兵、
传信兵..., 顶点也分别有不同的形式,好的是顶点格式我们是可以设计的,也就是一个士兵
最后是什么样子 是由我们亲手打造的;创建自定义灵活顶点格式时,根据实际的需求,需要定
义一个包含特定顶点信息的结构体,主动权在我们这我们可以随心所欲地定义顶点包含的属性,
比如我们可以定义一个只包含顶点三维坐标和颜色的结构体(士兵模板)。
struct CUSTOMVERTEX
{
float x, y, z; //顶点的三维坐标值,x,y,z
DWORD color; //顶点的颜色值
};
在DX11中,我们给它换了一身装备,让它变得更炫酷了:
struct CUSTOMVERTEX
{
XMFLOAT3 pos;
XMFLOAT4 color;
};
ps:{
在DX9中,我们需定义一个D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE),
去帮助CPU辨别我们写的顶点顺序,但在DX11中,我们把它用更为快乐的方式替代了,我们把顶点
和像素的大部分处理工作全扔给了GPU,让CPU更加专心做自己的事。
(DX9)需要注意的是,我们在书写灵活顶点格式的宏定义的时候需要遵守一个顺序原则,
顺序就是优先级需要这样来分: 顶点坐标位置>RHW值>顶点混合权重值>
顶点法线向量>漫反射颜色值>镜面反射颜色值>纹理坐标信息。
具体信息参见:https://blog.youkuaiyun.com/poem_qianmo/article/details/8276363(浅墨)。
DX11虽然不需要定义顺序宏,但我们还是以规范的顺序方式定义顶点,当然你如果想搞个
怪,颠倒顺序,只要注意你的shader文件中格式做相对应改变就行。但是这会带来麻烦,尤其
是你或别人去维护代码时。}
老鸟:DX11中为了方便管理常用颜色,也给他们换了身新衣服放在一起:
//定义几个常见的颜色值,方便在程序中使用
//这里用到XMVECTORF32结构,在定义常量的XMVECTOR时使用,
//用它可以在创建变量时直接初始化且可以转化为XMVECTOR。
const XMVECTORF32 White = {1.0f, 1.0f, 1.0f, 1.0f};
const XMVECTORF32 Black = {0.0f, 0.0f, 0.0f, 1.0f};
const XMVECTORF32 Green = {0.0f, 1.0f, 0.0f, 1.0f};
const XMVECTORF32 Blue = {0.0f, 0.0f, 1.0f, 1.0f};
const XMVECTORF32 Yellow = {1.0f, 1.0f, 0.0f, 1.0f};
const XMVECTORF32 Cyan = {0.0f, 1.0f, 1.0f, 1.0f};
const XMVECTORF32 Magenta = {1.0f, 0.0f, 1.0f, 1.0f};
const XMVECTORF32 Silver = {0.75f,0.75f,0.75f,1.0f};
DX11龙书中采用的也是类似的形式:真是对颜色的偏爱呀:
namespace Colors
{
XMGLOBALCONST XMVECTORF32 White = { 1.0f, 1.0f, 1.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Black = { 0.0f, 0.0f, 0.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Red = { 1.0f, 0.0f, 0.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Green = { 0.0f, 1.0f, 0.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Blue = { 0.0f, 0.0f, 1.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Yellow = { 1.0f, 1.0f, 0.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Cyan = { 0.0f, 1.0f, 1.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Magenta = { 1.0f, 0.0f, 1.0f, 1.0f };
XMGLOBALCONST XMVECTORF32 Silver = { 0.75f, 0.75f, 0.75f, 1.0f };
XMGLOBALCONST XMVECTORF32 LightSteelBlue = { 0.69f, 0.77f, 0.87f, 1.0f };
}
// 创建顶点缓冲
Vertex vertices[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f), (const float*)&Colors::White }
}
顶点缓存使用四步曲之二:创建顶点缓存
老鸟:上面我们创建了一个具有(顶点坐标+颜色)的士兵模板(顶点),这听起来有点奇怪,
一个拥有颜色的士兵模板,什么鬼?模板做出来了,我们自然就要生产士兵了,几个好呢?
今天我们讲的是三角形,就试试水,创建三个士兵(顶点):
Vertex vertices[3] =
{
{XMFLOAT3(-1.0f,0.0f,1.0f),reinterpret_cast<const float*>(&Blue)},
{XMFLOAT3(0.0f,2.0f,1.0f),reinterpret_cast<const float*>(&Green)},
{XMFLOAT3(1.0f, 0.0f,1.0f),reinterpret_cast<const float*>(&Red)}
};
老鸟:嗯恩,气宇轩扬,英姿勃发,是我李云龙的兵!
小白:鬼脸(......);
老鸟:我李云龙(ID3D11Buffer)的兵,扔哪就扎在哪,打哪就响哪,你去打听打听,我的兵!
有名声( D3D11_BUFFER_DESC),我们现在要去前线,
给我打开战场(D3D11_SUBRESOURCE_DATA ),
你给还是不给, 杨书记(D11Device)!
DX11龙书:为了让 GPU 访问顶点数组,我们必须把它放置在一个称为缓冲(buffer)
的特殊资源容器中,该容器由 ID3D11Buffer 接口表示,
用于存储顶点的缓冲区称为顶点缓冲(vertex buffer)。Direct3D 缓冲不仅可以存储数据,
而且还说明了如何访问数据以及数据被绑定到图形管线的那个阶段。要创建一个顶点缓冲,
我们必须执行以下步骤:
1.填写一个 D3D11_BUFFER_DESC 结构体,描述我们所要创建的缓冲区。
2.填写一个 D3D11_SUBRESOURCE_DATA 结构体,为缓冲区指定初始化数据。
3.调用 ID3D11Device::CreateBuffer 方法来创建缓冲区。
(1)D3D11_BUFFER_DESC 结构体的定义如下:
typedef struct D3D11_BUFFER_DESC {
UINT ByteWidth;
D3D11_USAGE Usage;
UINT BindFlags;
UINT CPUAccessFlags;
UINT MiscFlags;
UINT StructureByteStride;
} D3D11_BUFFER_DESC;
ByteWidth:缓存大小,字节为单位;
Usage:对于不变化的顶点、索引缓存,我们设为为D3D11_USAGE_VERTEX_IMMUTABLE,
此外,针对CPU对缓存的读、写权限,这个成员有多个类型:
D3D11_USAGE_DEFAULT:CPU不可读写;
D3D11_USAGE_IMMUTABLE:表示在创建资源后,资源中的内容不会改变;
D3D11_USAGE_DYNAMIC:CPU可读写;
D3D11_USAGE_STAGING:CPU可读,即可拷贝;
BindFlags:针对顶点缓存为D3D11_BIND_VERTEX_BUFFER,
针对索引缓存为D3D11_BIND_INDEX_BUFFER;
CPUAccessFlags:指定 CPU 对资源的访问权限,此处设为0;
MiscFlags:不需要为顶点缓冲区指定任何杂项(miscellaneous)标志值,此处设为0;
StructureByteStride:这个属性只用于结构化缓冲,处设为0。
(2)D3D11_SUBRESOURCE_DATA 结构体的定义如下:
typedef struct D3D11_SUBRESOURCE_DATA {
const void *pSysMem;
UINT SysMemPitch;
UINT SysMemSlicePitch;
} D3D11_SUBRESOURCE_DATA;
pSysMem:包含初始化数据的系统内存数组的指针。当缓冲区可以存储 n 个顶点时,
对应的初始化数组也应至少包含 n 个顶点,从而使整个缓冲区得到初始化。
SysMemPitch:顶点缓冲区不使用该参数。(2D or 3D 纹理)
SysMemSlicePitch:顶点缓冲区不使用该参数。(3D 纹理)
杨书记(ID3D11Device):给给给,我惹不起。
李云龙:哈哈,你老小子就是爽快,走,兄弟们。
//创建顶点数据
Vertex vertices[3] =
{
{ XMFLOAT3(-1.0f, 0.0f, 1.0f), reinterpret_cast<const float*>(&Blue) },
{ XMFLOAT3(1.0f, 0.0f, 1.0f), reinterpret_cast<const float*>(&Red) },
{ XMFLOAT3(0.0f, 2.0f, 1.0f), reinterpret_cast<const float*>(&Green) },
};
//创建描述
D3D11_BUFFER_DESC vbDesc = { 0 };
vbDesc.ByteWidth = 3* sizeof(Vertex);
vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbDesc.Usage = D3D11_USAGE_DEFAULT;
//创建空间
D3D11_SUBRESOURCE_DATA vbData = { 0 };
vbData.pSysMem = vertices;
//根描述和空间创建顶点缓存
ID3D11Buffer *g_VB(NULL);
hr = g_device->CreateBuffer(&vbDesc, &vbData, &g_VB);
顶点缓存使用四步曲之三:访问顶点缓存
前方战场联线:
杨书记:前方战士如何?
李云龙:哈哈,你放心,我们这里不是号称鬼跳地吗,这里地形复杂,有16条地槽(or32?),
我已命令一团躲在0号槽中,只等敌人来到,便打个出其不意,除非二团来替更,不然
一团会死守到底。(上面都是老鸟自导自演)
//访问顶点缓存(绑定至流水线管道)
UINT stride = sizeof(Vertex);
UINT offset = 0;
g_deviceContext->IASetVertexBuffers(0, 1, &g_VB, &stride, &offset);
老鸟:下面是函数的解释:
void ID3D11DeviceContext::IASetVertexBuffers(
UINT StartSlot,
UINT NumBuffers,
ID3D10Buffer *const *ppVertexBuffers,
const UINT *pStrides,
const UINT *pOffsets);
StartSlot:顶点缓冲区所要绑定的起始输入槽。一共有 16 个输入槽,索引依次为 0到 15。
NumBuffers:顶点缓冲区所要绑定的输入槽的数量,如果起始输入槽为索引 k,我
们绑定了 n 个缓冲,那么缓冲将绑定在索引为 k, k+1 ……k+n-1 的输入槽上。
ppVertexBuffers:指向顶点缓冲区数组的第一个元素的指针。
pStrides:指向步长数组的第一个元素的指针。
pOffsets:指向偏移数组的第一个元素的指针。
因为 IASetVertexBuffers 方法支持将一个顶点缓冲数组设置到不同的输入槽中,其实是将
一个顶点缓冲按属性分成不同的部分,以便提高效率。
这里我简单介绍一下:
struct Vertex
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct vertexPos
{
XMFLOAT4 Color;
};
struct vertexColor
{
XMFLOAT4 Color;
};
vertexPos vertexPos[] =
{
{ XMFLOAT3(-1.0f, -1.0f, -1.0f) },
{ XMFLOAT3(-1.0f, +1.0f, -1.0f) },
{ XMFLOAT3(+1.0f, +1.0f, -1.0f) },
{ XMFLOAT3(+1.0f, -1.0f, -1.0f) },
{ XMFLOAT3(-1.0f, -1.0f, +1.0f) },
{ XMFLOAT3(-1.0f, +1.0f, +1.0f) },
{ XMFLOAT3(+1.0f, +1.0f, +1.0f) },
{ XMFLOAT3(+1.0f, -1.0f, +1.0f) }
};
vertexColor vertexColor[] =
{
{ (const float*)&Colors::White },
{ (const float*)&Colors::Black },
{ (const float*)&Colors::Red },
{ (const float*)&Colors::Green },
{ (const float*)&Colors::Blue },
{ (const float*)&Colors::Yellow },
{ (const float*)&Colors::Cyan },
{ (const float*)&Colors::Magenta }
};
修改相对应的属性:
D3D11_BUFFER_DESC vbd;
ID3D11Buffer *g_VB[2];
//顶点坐标
vbd.Usage = D3D11_USAGE_IMMUTABLE;
vbd.ByteWidth = sizeof(vertexPos) * 8;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
vbd.MiscFlags = 0;
vbd.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vinitData;
vinitData.pSysMem = vertexPos;
HR(md3dDevice->CreateBuffer(&vbd, &vinitData, &g_VB[[0]));
//顶点颜色
vbd.ByteWidth = sizeof(vertexColor) * 8;
vinitData.pSysMem = vertexColor;
HR(md3dDevice->CreateBuffer(&vbd, &vinitData, &g_VB[[1]));
UINT stride[2] = {sizeof(vertexPos),sizeof(vertexColor)};
UINT offset[2] = {0,0};
md3dImmediateContext->IASetVertexBuffers(0, 2, mBoxVB, stride, offset);
如果我们想让mBoxVB[0]里的所有顶点完整地装配到管线里,
而mBoxVB[1]里需要把除第一个顶点之外的其他顶点装配到管线,
那么pOffsets = {0,1}即可。
呼呼,看看美图休息一下:
真想让小萤草奶我一下,就能元气满满了。
顶点缓存使用四步曲之四:绘制顶点
很简单,调用函数就行。(csdn注释看不清楚,请复制至VS后观看)
void ID3D11DeviceContext::Draw(UINT VertexCount, UINT StartVertexLocation);
//指定图元拓扑类型
g_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//实际使用 //顶点数量:3 //选择从哪个顶点绘制:0
g_deviceContext->Draw(3, 0);
老鸟:顶点是以一个叫做顶点缓冲区的 Direct3D 数据结构的形式绑定到图形管线的。顶点缓
冲区只是在连续的内存中存储了一个顶点列表。它并没有说明以何种方式组织顶点,形成几
何图元。例如,是应该把顶点缓冲区中的每两个顶点解释为一条直线,还是应该把顶点缓冲
区中的每三个顶点解释为一个三角形?我们通过指定图元拓扑来告诉 Direct3D 以何种方式
组成几何图元:
void ID3D11Device::IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY Topology);
typedef enum D3D11_PRIMITIVE_TOPOLOGY
{
D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED = 0,
D3D11_PRIMITIVE_TOPOLOGY_POINTLIST = 1,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST = 2,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP = 3,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST = 4,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP = 5,
D3D11_PRIMITIVE_TOPOLOGY_LINELIST_ADJ = 10,
D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ = 11,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ = 12,
D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ= 13,
D3D11_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST = 33,
D3D11_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST = 34,
.
.
.
D3D11_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST = 64,
} D3D11_PRIMITIVE_TOPOLOGY;
这里我们主讲几个:
(1)点列表(point list)由 D3D11_PRIMITIVE_TOPOLOGY_POINTLIST 标志值表示。
(2)线带(line strip)由 D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP 标志值表示。
(3)线列表(line list)由 D3D11_PRIMITIVE_TOPOLOGY_LINELIST 标志值表示。
(4)三角形带(triangle strip)由 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
标志值表示。
(5)三角形列表(triangle list)由 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST
标志值表示。(最常用)
(6)通过指定 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ 拓扑标志
值可以使管线知道如何从顶点缓冲区中构建三角形以及它的邻接三角形。
(7)D3D11_PRIMITIVE_TOPOLOGY_N_CONTROL_POINT_PATCHLIST 拓扑标志表
示将顶点数据作为 N 控制点的面片列表,这些点用于(可选)图形管线的曲面细分阶段。
ps:带 与 列表 的区别就在其单元图形是 连结 还是可分的。
顶点缓存使用四步曲之零:顶点的脚踏两只船(一)
上面我们DX11顶点按DX9的CPU方面的实现就大功告成,但是DX11的顶点与时俱进,
她选择投入GPU的环抱,这个DX11的新宠儿,
这里我建议您看以下此篇博客,对GPU语言"HLSL"有一个提前的准备:
https://blog.youkuaiyun.com/chenjinxian_3D/article/details/51811944(1)
https://blog.youkuaiyun.com/chenjinxian_3D/article/details/51813948(2)
因为笔者的知识技术不到位,讲解也许会使您误解、迷惑,这都是一篇博文不该
有的元素,望君能查看更为专业的文档。
HLSL 听起来似乎很高端,全名叫高阶着色器语言(High Level Shader Language)
实际上有点c++底子的人10分钟就能掌握个大概,很简单、很人性化的语言,
与此相对的是 OpenGL 的GLSL ,现在如日中天的还有CG(语法跟HLSL相似)
读者若想了解更多,可自行查阅资料。
老鸟:咳咳,还想不想继续了,再插嘴我可就罢工了,
小二,上菜,好叻,来了,客官,小心烫嘴:
struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};
小白:咦,这不是士兵模板吗?好像多了个尾巴。
老鸟:是的,这就是士兵模板,只不过在HLSL中我们给它贴个小尾巴做标示用;
小尾巴名叫“语意”,顾名思义,就是用来解释主人的属性的,便于c++文件传入相
对应的数据;VertexIn:顶点输入,这是一个未经人事的小纯男。
老鸟:有输入,自然少不了输出:
struct VertexOut
{
float4 posH : SV_POSITION;
float4 color : COLOR;
};
这就是小纯男被玩弄后的状态,最基本的东西(坐标)已经变了,而且还加了一个奇奇怪怪
的float,因为向量的列数必须和矩阵的行数相匹配。因为要做变换操作的向量是一个position,
把第4个float值(w分量)指定为1。要是向量表示的是一个direction,w分量应该被设置为0。
(向量和点表示及其相像,故我们用第四个w分量去区别他们)
小白:啊啊,HLSL这么这个的吗,快点,我要看小纯男被玩弄的那段。
老鸟:。。。。。。。。(一脸黑线),顶点在HLSL中变化函数:
VertexOut VS(VertexIn In)
{
VertexOut Out;
vout.posH = mul(float4(In.pos,1.f),g_worldViewProj);
Out.color = In.color;
return Out;
}
老鸟:函数很简单,VS的名字是VertexShaer的缩写,你可以按照你自己的喜好改名,
mul:HLSL的内置函数,就是乘法。
g_worldViewProj:一个混合矩阵,(不是本节博客范围,下节将会详细介绍)。
老鸟:顶点进行变化了,我们的像素也不能闲着:
float4 PS(VertexOut In):SV_TARGET
{
return In.color;
}
小白:等等,顶点我知道,像素是个神马?
老鸟:啊,怪我,一时疏忽,忘记向你介绍HLSL的两大成员:
The Vertex Shader 先生,
The Pixel Shader 女士。
The Vertex Shader 先生 :主要是接待C++来宾(其实是c++文件的副本),
帮助它们完成一系列的升级和变化,但是颜色这种细致活,他就做不来了,
但他有个好伉俪:The Pixel Shader 女士。
The Pixel Shader 女士:从先生那里取得任务,开始了她那令人惊叹的手艺,
其在视觉上的造诣,令人叹为观止!当然做完后便通过GPU去展示。
老鸟:下面我们看一下其整体的面貌:
struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};
struct VertexOut
{
float4 posH : SV_POSITION;
float4 color : COLOR;
};
VertexOut VS(VertexIn In)
{
VertexOut Out;
Out.posH = mul(float4(In.pos, 1.f), g_worldViewProj);
Out.color = In.color;
return Out;
}
float4 PS(VertexOut In) :SV_TARGET
{
return In.color;
}
老鸟:小白,你看一下,这总览有什么不妥之处。
小白:嗯恩,g_worldViewProj这个小玩意似乎没有定义。
老鸟:不错,看来你已经入门了,是的,HLSL现在并不认识g_worldViewProj,
所以我们要为它安装身份:
cbuffer CBufferPerObject
{
float4x4 g_worldViewProj : WORLDVIEWPROJECTION;
}
老鸟:WORLDVIEWPROJECTION这个语意是我们自己加的,这样方便我们改变量名,
CBufferPerObject:每个实例对象都拥有此常量,(出现一个新对象更新一次)。
同理还有:CBufferPerFrame则是指该buffer的数据每一帧更新一次(灯光),
允许多个objects使用相同的shader常量进行渲染。
老鸟 : DX9的时候,The Vertex Shader 先生 和The Pixel Shader 女士是可以分开的,
当然,现在也可以,为了能让一个对象能进行不同的特效渲染,我们常常得去
内存中去找不同的特效文件,这给我们带来了困扰,我们就想能不能把他们放在
一个文件中,一起处理,这就是Effect的来源,DX9时就有了,在DX11中被单独
提取出来,这不是我们不需要它,而是把他开源,让用户更加自由设计。
ps:如果项目很大,需要将The Vertex Shader和The Pixel Shader分开,也是可以尝试的,
但文件管理会让人头痛。
老鸟:下面我们就看看其是怎么将The Vertex Shader和The Pixel Shader整合的:
technique11 main11
{
Pass p0
{
SetVertexShader(CompileShader(vs_5_0,VS()));
SetPixelShader(CompileShader(ps_5_0,PS()));
}
}
老鸟:不可思议,是不是,就是这么简单,(其实c++文件中会有相对应的改变,
但这种改变是有益的,至少我们不要单独去记忆顶点和像素的区别,并小心翼翼的
使用他们,当然使用Effect让错误变得更加不易查找,但这点负担我们乐于承受。
读者想知道怎样分开使用,阅读DX9龙书即可)。
老鸟:到这里整个HLSL(其实我们更应该叫做FX文件)就写完了:
cbuffer CBufferPerObject
{
float4x4 g_worldViewProj : WORLDVIEWPROJECTION;
}
struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};
struct VertexOut
{
float4 posH : SV_POSITION;
float4 color : COLOR;
};
VertexOut VS(VertexIn In)
{
VertexOut Out;
Out.posH = mul(float4(In.pos, 1.f), g_worldViewProj);
Out.color = In.color;
return Out;
}
float4 PS(VertexOut In) :SV_TARGET
{
return In.color;
}
technique11 main11
{
Pass p0
{
SetVertexShader(CompileShader(vs_5_0, VS()));
SetPixelShader(CompileShader(ps_5_0, PS()));
}
}
顶点缓存使用四步曲之零:顶点的脚踏两只船(二)
老鸟:上文我们讲完了顶点与GPU的缠绵,而我们的CPU只能在一旁苦看吗?当然不!
CPU决定插入此事,却 “ 有心栽花花不成,无心护柳柳成荫“,反倒成了月下之老,气哉气哉!
那么我们就来看看CPU是如何去”帮助我的敌人,痛击我自己“的:
老鸟:我叫CPU,我深爱顶点缓存,可是她最近跟一个叫GPU的小子走的很近,啊呸,
以为从C变成G就能得到顶点缓存妹妹的喜爱吗?
顶点缓存:啊,我们是不是前世在哪里见过,GPU哥哥。
CPU:啊,不行,我一定要阻止他们,嘿嘿,找到了,作为CPU,我最擅长的便是
找BUG,我要把GPU的缺点全显示出来:
//编译Effect的参数
UINT flag(0);
#if defined(DEBUG) || defined(_DEBUG)
flag |= D3D10_SHADER_DEBUG;
flag |= D3D10_SHADER_SKIP_OPTIMIZATION;
#endif
//两个ID3D10Blob用来存放编译好的shader及错误消息
ID3D10Blob *shader(NULL);
ID3D10Blob *errMsg(NULL);
//编译effect
HRESULT hr = D3DX11CompileFromFile(L"FX/BasicDraw.fx", 0, 0, 0, "fx_5_0", flag, 0, 0, &shader, &errMsg, 0);
//如果有编译错误,显示之
if (errMsg)
{
MessageBoxA(NULL, (char*)errMsg->GetBufferPointer(), "ShaderCompileError", MB_OK);
errMsg->Release();
return FALSE;
}
if (FAILED(hr))
{
MessageBox(NULL, L"CompileShader错误!", L"错误", MB_OK);
return FALSE;
}
//从编译好的Effect创建Effect
hr = D3DX11CreateEffectFromMemory(shader->GetBufferPointer(), shader->GetBufferSize(), 0, g_device, &g_effect);
if (FAILED(hr))
{
MessageBox(NULL, L"CreateEffectFromMemory错误!", L"错误", MB_OK);
shader->Release();
return FALSE;
}
详细解释:
HRESULT D3DX11CompileFromFile (
LPCTSTR pSrcFile ,
CONST D3D10_SHADE R_MACRO *pDefines,
LPD3D10INCLUDE pInclude ,
LPCSTR pFunctionName ,
LPCSTR pProfile,
UINT Flags 1,
UINT Flags 2,
ID3DX11ThreadPump *pPump ,
ID3D10Blob **ppShader,
ID3D10Blob **ppErrorMsgs,
HRESULT *pHResult);
1 .pSrcFile:.fx 文件名,该文件包含了我们所要编译的效果源代码。
2 .pDefines:高级选项,我们不使用;请参阅 SDK 文档。
3 .pInclude:高级选项,我们不使用;请参阅 SDK 文档。
4 .pFunctionName:着色器入口函数的名字。只用于单独编译着色器程序的情况。当
使用 effect 框架时设置为 null,这是因为在 effect 文件中已经定义了入口点。
5 .pProfile:用于指定着色器版本的字符串。对于 Direct3D 11 来说,我们使用的着色
器版本为 5.0(“fx_5_0”)。
6 .Flags1:用于指定着色器代码编译方式的标志值。SDK 文档列出了很多标志值,但
本书只使用其中的 2 个:
D3D10_SHADER_DEBUG:以调试模式编译着色器。
D3D10_SHADER_SKIP_OPTIMIZATION:告诉编译器不做优化处理(用于进行
调试)。
7 .Flags2:高级选项,我们不使用;请参阅 SDK 文档。
8 .pPump:指向线程泵的指针,多线程编程时使用,是高级选项,我们不使用;请参
阅 SDK 文档。本书中这个值都设为 null。
9 .ppShader:返回一个指向 ID3D10Blob 数据对象的指针,这个数据对象保存了经过
编译的代码。
10 .ppErrorMsgs:返回一个指向 ID3D10Blob 数据对象的指针,这个数据对象存储了
一个包含错误信息的字符串。
11 .pHResult:在使用异步编译时,用于获得返回的错误代码。仅当使用 pPump 时才
使用该参数;我们在本书中将该参数设为空值。
ID3D10Blob 只是一个通用内存块,它有两个方法:
(a)LPVOID GetBufferPointer:返回指向数据的一个 void*,所以在使用时应该对它执
行相应的类型转换 (对应下面1)。
(b)SIZE_T GetBufferSize:返回缓冲的大小,以字节为单位(对应下面2)。
HRESULT D3DX11CreateEffectFromMemory(
void *pData,
SIZE_T DataLength,
UINT FXFlags ,
ID3D11Device *pDevice,
ID3DX11Effect **ppEffect);
1 .pData:指向编译好的 effect 数据的指针。
2 .DataLength:effect 数据的长度,以字节为单位。
3 .FXFlags:Effect 标识必须与定义在 D3DX11CompileFromFile 方法中的
Flags2(0) 匹配。
4 .pDevice:指向 Direct3D 11 设备的指针。
5 .ppEffect:指向创建好的 effect 的指针。
CPU:顶点缓存妹妹,你看,这些都是GPU的缺点!
GPU:原谅我,我是一个对待爱情认真的男人,在自己喜欢的女孩面前,我毫无保留,
通过别人之手让你来了解我,是我的错误。
顶点缓存:好真诚,好感动,好man。
CPU:哪里真诚,哪里值得感动了,这都是渣男的通用台词好吗?不行,
我不能让小顶落入他的手中。
某天上午,CPU走在小路上,一张被扔在草丛的废纸引起了他的注意。
CPU:咦,这是什么,”啊,啊,啊,小顶“,后面还有一小串字符:D3DX11_PASS_DESC,
署名:GPU。看来这是GPU写的情书,D3DX11_PASS_DESC这个就是破解情书的
关键。
下午时分:
CPU:小顶,你看这是什么:
//开始创建输入布局:InputLayout
//因为这个程序顶点着色器输入只有两个变量:顶点坐标(float3)和颜色值(float4),
//因此输入的描述数组含有两个成员,分别针对“顶点”和“颜色”
D3D11_INPUT_ELEMENT_DESC inputDesc[2] =
{
//顶点
//语义名 语义名索引 数据格式 输入槽 偏移 输入类型 此处不用,设0
{ "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 }
};
//创建InputLayout
//这里用到了编译好的shader的输入信息(InputSignature),可以从Effect相应的Technique中获取
D3DX11_PASS_DESC passDesc = { 0 };
ID3D11InputLayout *g_inputLayout(NULL);
g_effect->GetTechniqueByName("main10")->GetPassByIndex(0)->GetDesc(&passDesc);
hr = g_device->CreateInputLayout(inputDesc, 2, passDesc.pIAInputSignature,
passDesc.IAInputSignatureSize, &g_inputLayout);
if (FAILED(hr))
{
MessageBox(NULL, L"CreateInputLayout错误!", L"错误", MB_OK);
return FALSE;
}
我们上文有提到,我们不再用DX9那种顶点顺序宏声明方式,我们把它改成了
上文的 D3D11_INPUT_ELEMENT_DESC inputDesc[2] ,
老鸟: CreateInputLayout(inputDesc, 2, passDesc.pIAInputSignature,
passDesc.IAInputSignatureSize, &g_inputLayout);
这个函数,小白,你应该猜都能猜出来他的释义吧。
小白:是的,
(1)D3D11_INPUT_ELEMENT_DESC 结构体数组指针。
(2)D3D11_INPUT_ELEMENT_DESC 数组元素个数。
(3)指向Efftcts中顶点着色器字节码的指针
(4 ) 顶点着色器参数的字节码长度,单位为字节。
(5)返回创建后的 ID3D11InputLayout 指针。
老鸟:欣慰状。
好了,我们的CPU该继续上场了。
CPU:小顶,GPU居然拍你的生活私密照(inputDesc),居然还上传分享到Effects,
你看这条评论:这个小顶好清纯呀,居然只有顶点坐标和颜色两个属性,
真是人间不可多的一朵鲜花呀,我好想蹂躏她。 The Vertex Shader 先生留。
我就说这样一个刚刚认识3天19分钟零23秒的男人怎么信的过,看本性暴露无疑了吧,
啊,呸,渣男!”果然世界上像我这样长得又帅有纯情的美男子真是太少了呀,可惜我已经
爱上小顶,不然我一定让世界女子知道我的风采。“
GPU:抱歉,是我的错,我从认识你的第一刻开始,便认为你是我的终身,故此我所做的一切
都是把你已经当做了我一生的陪伴,我想跟世界传达我的感受,想让世界见证你的美,是我用情
太深,怪我,怪我。
小顶:一脸深情与怜惜的望着GPU。
CPU:怎么办,他说的好有道理,这样下去,我就彻底没戏了呀,不行,我一定要
阻止他。
两天后:
一个人走到了CPU的面前,你是不是想揭露GPU的真面目呀,我可以帮你,但你要帮我做三件事。
CPU:思虑万分,道:什么事?
陌生人:就是这三件:(世界变换,下节博客详解,本例只需看着开心就行)
XMMATRIX world=XMMatrixIdentity();
//视角变换
XMVECTOR eyePos = XMVectorSet(0.f, 2.f, -5.f, 1.f);
XMVECTOR lookAt = XMVectorSet(0.f, 0.f, 0.f, 1.f);
XMVECTOR up = XMVectorSet(0.f, 1.f, 0.f, 1.f);
XMMATRIX view = XMMatrixLookAtLH(eyePos, lookAt, up);
//投影变换
XMMATRIX proj = XMMatrixPerspectiveFovLH(XM_PI*0.25f, g_winWidth*1.f / g_winHeight, 1.f, 1000.f);
//把三个变换相乘,合成一个
XMMATRIX worldViewProj = world*view*proj;
这个是我们之间的通信凭证:g_fxWorldViewProj
ID3DX11EffectMatrixVariable *g_fxWorldViewProj(NULL);
g_fxWorldViewProj = g_effect->GetVariableBySemantic("WORLDVIEWPROJECTION")->AsMatrix();
g_fxWorldViewProj->SetMatrix(reinterpret_cast<float*>(&worldViewProj));
你创建好worldViewProj 之后,去g_effect那里给 g_fxWorldViewProj
获取权限(跟WORLDVIEWPROJECTION绑定),然后把worldViewProj放进去,
(把C++文件数据传入)。这样你的工作就完成了,而我也将实现我的诺言。
获取权限:应在资源初始化时完成。
C++文件数据传入以及三件事:在资源更新里进行,因为要求会发生改变。
简单来说:通过g_fxWorldViewProj获取(绑定)HLSL中的常量buffer,
然后在C++中创建对应实体,并通过g_fxWorldViewProj传入数据。
CPU:啊,就这点小事,等我1s,做好了,给你。
CPU:我叫CPU,万万没想到,我成功散拆了GPU跟小顶,但小顶还是没跟我在一起,
因为有一个叫FX女人缠上了我。小顶,我还是爱你的,呜 呜 呜。
(FX:陌生人,因cpu创建worldViewProj 一事而爱上cpu,是一个腹黑美少女)
FX:cpu 哥哥,你认真办事的样子好帅,我好喜欢。(未完)。
源码链接:https://pan.baidu.com/s/1T5JfjCKRdoKgqJjd1rpTMA
张爱玲:在这个光怪陆离的人间,没有谁可以将日子过得行云流水。但我始终相信,走过平湖烟雨,岁月山河,那些历尽劫数、尝遍百味的人,会更加生动而干净。时间永远是旁观者,所有的过程和结果,都需要我们自己承担。(浅墨csdn)