DirectX11 With Windows SDK--16 流输出阶段
原文:
DirectX11 With Windows SDK--16 流输出阶段
前言在上一章,我们知道了如何使用几何着色器来重新组装图元,比如从一个三角形分裂成三个三角形。但是为了实现更高阶的分形,我们必须要从几何着色器拿到输出的顶点。这里我们可以使用可选的流输出阶段来拿到顶点集合。 注意: 本章末尾有大量的GIF动图! 在此之前需要额外了解的章节如下:
DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。 流输出阶段现在我们知道GPU可以写入纹理(textures),例如深度/模板缓冲区以及后备缓冲区。当然,我们也可以通过渲染管线的流输出阶段让GPU将几何着色器输出的顶点集合写入到指定的顶点缓冲区(vertex buffer)。除此之外,我们还能够指定不进行光栅化以及后续的所有阶段,仅让顶点数据经过流输出阶段。 在几何着色器中,最多四个流输出对象可以被设置,即几何着色器的入口函数中只允许设置四个流输出对象的参数。当多个流输出对象存在时,它们必须都要为 上一章也提到,几何着色器的单次调用不能产出超过1024个标量。因此分配给所有流输出对象的标量总和不能超过1024。比如现在我有2个流输出对象,它们的结构体相同,容纳512个标量,那最多仅允许输出2个这样的顶点来分配给这2个流输出对象。 流输出状态的配置ID3D11DeviceContext::SOSetTargets方法--绑定流输出对应用于接收数据的顶点缓冲区void ID3D11DeviceContext::SOSetTargets( UINT NumBuffers,// [In]顶点缓冲区数目 ID3D11Buffer * const *ppSOTargets,// [In]顶点缓冲区数组 const UINT *pOffsets // [In]一个数组包含对每个顶点缓冲区的字节偏移量 ); 该方法多允许设置4个顶点缓冲区。 每个要绑定到流输出阶段的缓冲区资源必须要在创建的时候额外设置 若偏移值设为-1,则会引起流输出缓冲区被追加到最后一个缓冲区的后面 顶点缓冲区绑定到流输出阶段的输出槽0操作如下: UINT offset = 0; m_pd3dImmediateContext->SOSetTargets(1,vertexBufferOut.GetAddressOf(),&offset); 如果我们需要恢复默认的状态,则可以这样调用: ID3D11Buffer* nullBuffer = nullptr; UINT offset = 0; m_pd3dImmediateContext->SOSetTargets(1,&nullBuffer,&offset);
因为后续我们是将每一阶输出的顶点都保存下来,即便不需要交换顶点缓冲区,但也有可能出现同时绑定输入/输出的情况。一种合理的绑定顺序如下: // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段 UINT stride = sizeof(VertexPosColor); UINT offset = 0; ID3D11Buffer * nullBuffer = nullptr; m_pd3dImmediateContext->SOSetTargets(1,&offset); // ... m_pd3dImmediateContext->IASetInputLayout(mVertexPosColorLayout.Get()); // ... m_pd3dImmediateContext->SOSetTargets(1,&offset); 当渲染管线完成一次流输出后,我们就可以用下面的方法来获取绑定在流输出阶段上的顶点缓冲区(当然你本身持有该缓冲区的指针的话就不需要了) ID3D11DeviceContext::SOGetTargets方法--获取绑定在流输出阶段的顶点缓冲区void ID3D11DeviceContext::SOGetTargets( UINT NumBuffers,// [In]缓冲区数目 ID3D11Buffer **ppSOTargets // [Out]获取绑定流输出阶段的顶点缓冲区 ); 输出的顶点缓冲区引用数会加1,最好是能够使用 ID3D11Device::CreateGeometryShaderWithStreamOutput方法--创建带流输出阶段的几何着色器接下来我们需要指定数据会流向哪个输出槽,首先我们需要填充结构体 typedef struct D3D11_SO_DECLARATION_ENTRY { UINT Stream; // 输出流索引,从0开始 LPCSTR SemanticName; // 语义名 UINT SemanticIndex; // 语义索引 BYTE StartComponent; // 从第几个分量(xyzw)开始,只能取0-3 BYTE ComponentCount; // 分量的输出数目,只能取1-4 BYTE OutputSlot; // 输出槽索引,只能取0-3 }; 其中,语义名 然后 输出槽索引 由于这里一个结构体只能指定某个输出流中的某一向量,所以通常我们需要像顶点输入布局那样传递一个数组来取出组合成特定顶点。 比如说现在顶点着色器输入的顶点和流输出的顶点是一致的: struct VertexPosColor { DirectX::XMFLOAT3 pos; DirectX::XMFLOAT4 color; static const D3D11_INPUT_ELEMENT_DESC inputLayout[2]; }; 输入布局描述如下: const D3D11_INPUT_ELEMENT_DESC VertexPosColor::inputLayout[2] = { { "POSITION",DXGI_FORMAT_R32G32B32_FLOAT,D3D11_INPUT_PER_VERTEX_DATA,0 },{ "COLOR",DXGI_FORMAT_R32G32B32A32_FLOAT,12,0 } }; HLSL中的结构体如下: struct VertexPosColor { float3 PosL : POSITION; float4 Color : COLOR; }; 流输出的入口描述如下: const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = { { 0,"POSITION",3,{ 0,"COLOR",4,0 } }; 这里对应的是索引为0的流输出对象,输出给绑定在索引为0的输出槽的顶点缓冲区,先输出语义为POSITION的向量中的xyz分量,然后输出COLOR整个向量。这样一个输出的顶点就和原来的顶点一致了。 接下来给出 HRESULT ID3D11Device::CreateGeometryShaderWithStreamOutput( const void *pShaderBytecode,// [In]编译好的着色器字节码 SIZE_T BytecodeLength,// [In]字节码长度 const D3D11_SO_DECLARATION_ENTRY *pSODeclaration,// [In]D3D11_SO_DECLARATION_ENTRY的数组 UINT NumEntries,// [In]入口总数 const UINT *pBufferStrides,// [In]一个数组包含了每个绑定到流输出的缓冲区中顶点字节大小 UINT NumStrides,// [In]上面数组的元素数目 UINT RasterizedStream,// [In]按索引指定哪个流输出对象用于传递到光栅化阶段 ID3D11ClassLinkage *pClassLinkage,// [In]忽略 ID3D11GeometryShader **ppGeometryShader // [Out]创建好的几何着色器 ); 如果不需要有流输出对象提供数据给光栅化阶段,则 下面是一个调用的例子: const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = { { 0,0 } }; HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(),blob->GetBufferSize(),posColorLayout,ARRAYSIZE(posColorLayout),&stridePosColor,1,D3D11_SO_NO_RASTERIZED_STREAM,nullptr,m_pTriangleSOGS.GetAddressOf())); 当该着色器被绑定到渲染管线上,流输出阶段就会被激活,我们可以使用 绘制各种酷炫的分形由于现在的着色器多到令人发指,而且有没有很好的办法归类整合,故在下面用一张表列出所有绘制流程用到的着色器hlsl文件名称:
首先给出 #include "LightHelper.hlsli" cbuffer CBChangesEveryFrame : register(b0) { matrix g_World; matrix g_WorldInvTranspose; } cbuffer CBChangesOnResize : register(b1) { matrix g_Proj; } cbuffer CBChangesRarely : register(b2) { DirectionalLight g_DirLight[5]; PointLight g_PointLight[5]; SpotLight g_SpotLight[5]; Material g_Material; matrix g_View; float3 g_SphereCenter; float g_SphereRadius; float3 g_EyePosW; float g_Pad; } struct VertexPosColor { float3 PosL : POSITION; float4 Color : COLOR; }; struct VertexPosHColor { float4 PosH : SV_POSITION; float4 Color : COLOR; }; struct VertexPosHLColor { float4 PosH : SV_POSITION; float3 PosL : POSITION; float4 Color : COLOR; }; struct VertexPosNormalColor { float3 PosL : POSITION; float3 NormalL : NORMAL; float4 Color : COLOR; }; struct VertexPosHWNormalColor { float4 PosH : SV_POSITION; float3 PosW : POSITION; float3 NormalW : NORMAL; float4 Color : COLOR; }; 实战1: 绘制分形三角形通过流输出阶段,一个三角形就分裂出了三个三角形,顶点的数目翻了3倍。若规定1阶分形三角形的顶点数为3,则N阶分形三角形的顶点数为(3^{N})。 HLSL代码首先是 // TriangleSO_VS.hlsl #include "Basic.hlsli" VertexPosColor VS(VertexPosColor vIn) { return vIn; } 然后和上一章一样, // TriangleSO_GS.hlsl #include "Basic.hlsli" [maxvertexcount(9)] void GS(triangle VertexPosColor input[3],inout TriangleStream<VertexPosColor> output) { // // 将一个三角形分裂成三个三角形,即没有v3v4v5的三角形 // v1 // / // / // v3/____v4 // /xxxx/ // / xx/ // /____/____ // v0 v5 v2 VertexPosColor vertexes[6]; int i; [unroll] for (i = 0; i < 3; ++i) { vertexes[i] = input[i]; vertexes[i + 3].Color = (input[i].Color + input[(i + 1) % 3].Color) / 2.0f; vertexes[i + 3].PosL = (input[i].PosL + input[(i + 1) % 3].PosL) / 2.0f; } [unroll] for (i = 0; i < 3; ++i) { output.Append(vertexes[i]); output.Append(vertexes[3 + i]); output.Append(vertexes[(i + 2) % 3 + 3]); output.RestartStrip(); } } 接下来的 // Triangle_VS.hlsl #include "Basic.hlsli" VertexPosHColor VS(VertexPosColor vIn) { matrix worldViewProj = mul(mul(g_World,g_View),g_Proj); VertexPosHColor vOut; vOut.Color = vIn.Color; vOut.PosH = mul(float4(vIn.PosL,1.0f),worldViewProj); return vOut; } // Triangle_PS.hlsl #include "Basic.hlsli" float4 PS(VertexPosHColor pIn) : SV_Target { return pIn.Color; } 实战2: 绘制分形雪花现在规定第一张图为一阶分形雪花,第二张为二阶分形雪花。观察二者之间的变化,可以发现前者的每一条直线变成了四条折线。其中每个尖锐角的度数都在60度,并且每条边的长度都应该是一致的。 HLSL代码和之前一样, // SnowSO_VS.hlsl #include "Basic.hlsli" VertexPosNormalColor VS(VertexPosNormalColor vIn) { return vIn; } 然后重点就在于 // SnowSO_GS.hlsl #include "Basic.hlsli" [maxvertexcount(5)] void GS(line VertexPosColor input[2],inout LineStream<VertexPosColor> output) { // 要求分形线段按顺时针排布 // z分量必须相等,因为顶点没有提供法向量无法判断垂直上方向 // v1 // / // ____________ => ____/ ____ // i0 i1 i0 v0 v2 i1 VertexPosColor v0,v1,v2; v0.Color = lerp(input[0].Color,input[1].Color,0.25f); v1.Color = lerp(input[0].Color,0.5f); v2.Color = lerp(input[0].Color,0.75f); v0.PosL = lerp(input[0].PosL,input[1].PosL,1.0f / 3.0f); v2.PosL = lerp(input[0].PosL,2.0f / 3.0f); // xy平面求出它的垂直单位向量 // // | // ____|_____ float2 upDir = normalize(input[1].PosL - input[0].PosL).yx; float len = length(input[1].PosL.xy - input[0].PosL.xy); upDir.x = -upDir.x; v1.PosL = lerp(input[0].PosL,0.5f); v1.PosL.xy += sqrt(3) / 6.0f * len * upDir; output.Append(input[0]); output.Append(v0); output.Append(v1); output.Append(v2); output.Append(input[1]); } 可以发现分形雪花每升一阶,需要绘制的顶点数就变成了上一阶的4倍。 这里要求了z分量必须相等,因为使用的着色器仍把一切的顶点仍当做3D顶点来对待出来(当然你也可以写成2D的着色器)。 然后开始具体分析从直线变折线的过程,可以看到因为顶点v1所在角的度数在60度,且v0,v2构成等边三角形,故v0v2, 其中 其中t的取值范围在[0.0f,1.0f],并且操作对象p0和p1可以是标量,也可以是矢量,对矢量来说则是对每个分量都进行线性插值。 当t = 0.5f时,描述的就是p0和p1的中值或中点。 该函数很容易描述两点之间某一相对位置。 由于我们规定了连续线段必须按顺时针排布,我们就可以利用向量i0i1逆时针旋转90度得到对应的突出方向向量,然后标准化,乘上相应的高度值即可得到顶点v1的位置。 最后就是用于绘制的着色器代码: // Snow_VS.hlsl #include "Basic.hlsli" VertexPosHColor VS(VertexPosColor vIn) { matrix worldViewProj = mul(mul(g_World,worldViewProj); return vOut; } // Snow_PS.hlsl #include "Basic.hlsli" float4 PS(VertexPosHColor pIn) : SV_Target { return pIn.Color; } 实战3: 绘制分形圆球以下是一阶和二阶的分形圆球: 仔细观察可以看到,原先的一个三角形分裂出了四个三角形,即每升一阶,需要绘制的顶点数就变成了上一阶的4倍。 HLSL代码
// SphereSO_VS.hlsl #include "Basic.hlsli" VertexPosNormalColor VS(VertexPosNormalColor vIn) { return vIn; } // SphereSO_GS.hlsl #include "Basic.hlsli" [maxvertexcount(12)] void GS(triangle VertexPosNormalColor input[3],inout TriangleStream<VertexPosNormalColor> output) { // // 将一个三角形分裂成四个三角形,但同时顶点v3,v4,v5也需要在球面上 // v1 // / // / // v3/____v4 // /xxxx/ // / xx/ // /____/____ // v0 v5 v2 VertexPosNormalColor vertexes[6]; matrix viewProj = mul(g_View,g_Proj); [unroll] for (int i = 0; i < 3; ++i) { vertexes[i] = input[i]; vertexes[i + 3].Color = lerp(input[i].Color,input[(i + 1) % 3].Color,0.5f); vertexes[i + 3].NormalL = normalize(input[i].NormalL + input[(i + 1) % 3].NormalL); vertexes[i + 3].PosL = g_SphereCenter + g_SphereRadius * vertexes[i + 3].NormalL; } output.Append(vertexes[0]); output.Append(vertexes[3]); output.Append(vertexes[5]); output.RestartStrip(); output.Append(vertexes[3]); output.Append(vertexes[4]); output.Append(vertexes[5]); output.RestartStrip(); output.Append(vertexes[5]); output.Append(vertexes[4]); output.Append(vertexes[2]); output.RestartStrip(); output.Append(vertexes[3]); output.Append(vertexes[1]); output.Append(vertexes[4]); } 由于v3,v5也需要在球面上,我们还需要额外知道球的半径和球心位置。虽然说通过三角形三个顶点位置和法向量可以算出圆心和半径,但直接从常量缓冲区提供这两个信息会更方便一些。 要计算诸如v3顶点所在位置,我们可以先求出它的法向量,将v0和v1的法向量相加取其单位向量即为v3的法向量,然后从圆心开始加上半径长度的法向量即可得到顶点v3的位置。 剩下绘制圆的着色器代码如下: // Sphere_VS.hlsl #include "Basic.hlsli" VertexPosHWNormalColor VS(VertexPosNormalColor vIn) { VertexPosHWNormalColor vOut; matrix viewProj = mul(g_View,g_Proj); float4 posW = mul(float4(vIn.PosL,g_World); vOut.PosH = mul(posW,viewProj); vOut.PosW = posW.xyz; vOut.NormalW = mul(vIn.NormalL,(float3x3) g_WorldInvTranspose); vOut.Color = vIn.Color; return vOut; } // Sphere_PS.hlsl #include "Basic.hlsli" float4 PS(VertexPosHWNormalColor pIn) : SV_Target { // 标准化法向量 pIn.NormalW = normalize(pIn.NormalW); // 顶点指向眼睛的向量 float3 toEyeW = normalize(g_EyePosW - pIn.PosW); // 初始化为0 float4 ambient = float4(0.0f,0.0f,0.0f); float4 diffuse = float4(0.0f,0.0f); float4 spec = float4(0.0f,0.0f); // 只计算方向光 ComputeDirectionalLight(g_Material,g_DirLight[0],pIn.NormalW,toEyeW,ambient,diffuse,spec); return pIn.Color * (ambient + diffuse) + spec; } C++代码的变化BasicEffect::InitAll方法的变化现在着色器的创建按绘制类别进行分组: bool BasicEffect::InitAll(ID3D11Device * device) { if (!device) return false; if (!pImpl->m_pCBuffers.empty()) return true; if (!RenderStates::IsInit()) throw std::exception("RenderStates need to be initialized first!"); const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = { { 0,0 } }; const D3D11_SO_DECLARATION_ENTRY posNormalColorLayout[3] = { { 0,"NORMAL",0 } }; UINT stridePosColor = sizeof(VertexPosColor); UINT stridePosNormalColor = sizeof(VertexPosNormalColor); ComPtr<ID3DBlob> blob; // ****************** // 流输出分裂三角形 // HR(CreateShaderFromFile(L"HLSLTriangleSO_VS.cso",L"HLSLTriangleSO_VS.hlsl","VS","vs_5_0",blob.ReleaseAndGetAddressOf())); HR(device->CreateVertexShader(blob->GetBufferPointer(),pImpl->m_pTriangleSOVS.GetAddressOf())); // 创建顶点输入布局 HR(device->CreateInputLayout(VertexPosColor::inputLayout,ARRAYSIZE(VertexPosColor::inputLayout),blob->GetBufferPointer(),pImpl->m_pVertexPosColorLayout.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLTriangleSO_GS.cso",L"HLSLTriangleSO_GS.hlsl","GS","gs_5_0",blob.ReleaseAndGetAddressOf())); HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(),pImpl->m_pTriangleSOGS.GetAddressOf())); // ****************** // 绘制分形三角形 // HR(CreateShaderFromFile(L"HLSLTriangle_VS.cso",L"HLSLTriangle_VS.hlsl",pImpl->m_pTriangleVS.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLTriangle_PS.cso",L"HLSLTriangle_PS.hlsl","PS","ps_5_0",blob.ReleaseAndGetAddressOf())); HR(device->CreatePixelShader(blob->GetBufferPointer(),pImpl->m_pTrianglePS.GetAddressOf())); // ****************** // 流输出分形球体 // HR(CreateShaderFromFile(L"HLSLSphereSO_VS.cso",L"HLSLSphereSO_VS.hlsl",pImpl->m_pSphereSOVS.GetAddressOf())); // 创建顶点输入布局 HR(device->CreateInputLayout(VertexPosNormalColor::inputLayout,ARRAYSIZE(VertexPosNormalColor::inputLayout),pImpl->m_pVertexPosNormalColorLayout.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLSphereSO_GS.cso",L"HLSLSphereSO_GS.hlsl",posNormalColorLayout,ARRAYSIZE(posNormalColorLayout),&stridePosNormalColor,pImpl->m_pSphereSOGS.GetAddressOf())); // ****************** // 绘制球体 // HR(CreateShaderFromFile(L"HLSLSphere_VS.cso",L"HLSLSphere_VS.hlsl",pImpl->m_pSphereVS.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLSphere_PS.cso",L"HLSLSphere_PS.hlsl",pImpl->m_pSpherePS.GetAddressOf())); // ****************** // 流输出分形雪花 // HR(CreateShaderFromFile(L"HLSLSnowSO_VS.cso",L"HLSLSnowSO_VS.hlsl",pImpl->m_pSnowSOVS.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLSnowSO_GS.cso",L"HLSLSnowSO_GS.hlsl",pImpl->m_pSnowSOGS.GetAddressOf())); // ****************** // 绘制雪花 // HR(CreateShaderFromFile(L"HLSLSnow_VS.cso",L"HLSLSnow_VS.hlsl",pImpl->m_pSnowVS.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLSnow_PS.cso",L"HLSLSnow_PS.hlsl",pImpl->m_pSnowPS.GetAddressOf())); // ****************** // 绘制法向量 // HR(CreateShaderFromFile(L"HLSLNormal_VS.cso",L"HLSLNormal_VS.hlsl",pImpl->m_pNormalVS.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLNormal_GS.cso",L"HLSLNormal_GS.hlsl",blob.ReleaseAndGetAddressOf())); HR(device->CreateGeometryShader(blob->GetBufferPointer(),pImpl->m_pNormalGS.GetAddressOf())); HR(CreateShaderFromFile(L"HLSLNormal_PS.cso",L"HLSLNormal_PS.hlsl",pImpl->m_pNormalPS.GetAddressOf())); pImpl->m_pCBuffers.assign({ &pImpl->m_CBFrame,&pImpl->m_CBOnResize,&pImpl->m_CBRarely}); // 创建常量缓冲区 for (auto& pBuffer : pImpl->m_pCBuffers) { HR(pBuffer->CreateBuffer(device)); } return true; } BasicEffect::SetRenderSplitedTriangle方法--绘制分形三角形由于新增了流输出的阶段,这里开始接下来的每一个用于绘制的方法都需要把流输出绑定的顶点缓冲区都解除绑定。 void BasicEffect::SetRenderSplitedTriangle(ID3D11DeviceContext * deviceContext) { // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段 UINT stride = sizeof(VertexPosColor); UINT offset = 0; ID3D11Buffer* nullBuffer = nullptr; deviceContext->SOSetTargets(1,&offset); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get()); deviceContext->VSSetShader(pImpl->m_pTriangleVS.Get(),0); deviceContext->GSSetShader(nullptr,0); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(pImpl->m_pTrianglePS.Get(),0); } BasicEffect::SetRenderSplitedSnow方法--绘制分形雪花void BasicEffect::SetRenderSplitedSnow(ID3D11DeviceContext * deviceContext) { // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段 UINT stride = sizeof(VertexPosColor); UINT offset = 0; ID3D11Buffer* nullBuffer = nullptr; deviceContext->SOSetTargets(1,&offset); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get()); deviceContext->VSSetShader(pImpl->m_pSnowVS.Get(),0); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(pImpl->m_pSnowPS.Get(),0); } BasicEffect::SetRenderSplitedSphere方法--绘制分形球体void BasicEffect::SetRenderSplitedSphere(ID3D11DeviceContext * deviceContext) { // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段 UINT stride = sizeof(VertexPosColor); UINT offset = 0; ID3D11Buffer* nullBuffer = nullptr; deviceContext->SOSetTargets(1,&offset); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexPosNormalColorLayout.Get()); deviceContext->VSSetShader(pImpl->m_pSphereVS.Get(),0); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(pImpl->m_pSpherePS.Get(),0); } BasicEffect::SetStreamOutputSplitedTriangle方法--经过流输出保存下一阶分形三角形的顶点为了简化设置,这里还需要提供额外的输入缓冲区和输出缓冲区。为了防止出现顶点缓冲区同时被绑定到输入装配和流输出阶段的情况,需要先清空流输出绑定的顶点缓冲区,然后将用于输入的顶点缓冲区绑定到输入装配阶段,最后才是把输出的顶点缓冲区绑定到流输出阶段。 void BasicEffect::SetStreamOutputSplitedTriangle(ID3D11DeviceContext * deviceContext,ID3D11Buffer * vertexBufferIn,ID3D11Buffer * vertexBufferOut) { // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段 UINT stride = sizeof(VertexPosColor); UINT offset = 0; ID3D11Buffer * nullBuffer = nullptr; deviceContext->SOSetTargets(1,&offset); deviceContext->IASetInputLayout(nullptr); deviceContext->SOSetTargets(0,&offset); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get()); deviceContext->IASetVertexBuffers(0,&vertexBufferIn,&stride,&offset); deviceContext->VSSetShader(pImpl->m_pTriangleSOVS.Get(),0); deviceContext->GSSetShader(pImpl->m_pTriangleSOGS.Get(),0); deviceContext->SOSetTargets(1,&vertexBufferOut,&offset); deviceContext->RSSetState(nullptr); deviceContext->PSSetShader(nullptr,0); } BasicEffect::SetStreamOutputSplitedSnow方法--经过流输出保存下一阶分形雪花的顶点注意这里是用LineList而不是LineStrip方式。 void BasicEffect::SetStreamOutputSplitedSnow(ID3D11DeviceContext * deviceContext,&offset); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get()); deviceContext->IASetVertexBuffers(0,&offset); deviceContext->VSSetShader(pImpl->m_pSnowSOVS.Get(),0); deviceContext->GSSetShader(pImpl->m_pSnowSOGS.Get(),0); } BasicEffect::SetStreamOutputSplitedSphere方法--经过流输出保存下一阶分形球体的顶点void BasicEffect::SetStreamOutputSplitedSphere(ID3D11DeviceContext * deviceContext,ID3D11Buffer * vertexBufferOut) { // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段 UINT stride = sizeof(VertexPosNormalColor); UINT offset = 0; ID3D11Buffer * nullBuffer = nullptr; deviceContext->SOSetTargets(1,&offset); deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); deviceContext->IASetInputLayout(pImpl->m_pVertexPosNormalColorLayout.Get()); deviceContext->IASetVertexBuffers(0,&offset); deviceContext->VSSetShader(pImpl->m_pSphereSOVS.Get(),0); deviceContext->GSSetShader(pImpl->m_pSphereSOGS.Get(),0); } GameApp类的变化GameApp::ResetSplitedTriangle方法--重新建立包含1-7阶的分形三角形顶点的缓冲区首先我们只需要给1阶的顶点缓冲区使用指定三角形的三个顶点,然后后续阶数的顶点缓冲区就根据上一阶产出的顶点缓冲区进行"绘制"。经过6次 注意顶点缓冲区在创建的时候一定要加上 void GameApp::ResetSplitedTriangle() { // ****************** // 初始化三角形 // // 设置三角形顶点 VertexPosColor vertices[] = { { XMFLOAT3(-1.0f * 3,-0.866f * 3,0.0f),XMFLOAT4(1.0f,1.0f) },{ XMFLOAT3(0.0f * 3,0.866f * 3,XMFLOAT4(0.0f,1.0f,{ XMFLOAT3(1.0f * 3,1.0f) } }; // 设置顶点缓冲区描述 D3D11_BUFFER_DESC vbd; ZeroMemory(&vbd,sizeof(vbd)); vbd.Usage = D3D11_USAGE_DEFAULT; // 这里需要允许流输出阶段通过GPU写入 vbd.ByteWidth = sizeof vertices; vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT; // 需要额外添加流输出标签 vbd.CPUAccessFlags = 0; // 新建顶点缓冲区 D3D11_SUBRESOURCE_DATA InitData; ZeroMemory(&InitData,sizeof(InitData)); InitData.pSysMem = vertices; HR(m_pd3dDevice->CreateBuffer(&vbd,&InitData,m_pVertexBuffers[0].ReleaseAndGetAddressOf())); // 三角形顶点数 m_VertexCounts[0] = 3; // 初始化所有顶点缓冲区 for (int i = 1; i < 7; ++i) { vbd.ByteWidth *= 3; m_VertexCounts[i] = m_VertexCounts[i - 1] * 3; HR(m_pd3dDevice->CreateBuffer(&vbd,m_pVertexBuffers[i].ReleaseAndGetAddressOf())); m_BasicEffect.SetStreamOutputSplitedTriangle(m_pd3dImmediateContext.Get(),m_pVertexBuffers[i - 1].Get(),m_pVertexBuffers[i].Get()); m_pd3dImmediateContext->Draw(m_VertexCounts[i - 1],0); } } GameApp::ResetSplitedSnow方法--重新建立包含1-7阶的分形雪花顶点的缓冲区由于绘制方式统一用LineList,初始阶段应当提供3条线段的6个顶点,虽然说每个顶点都被重复使用了2次。 void GameApp::ResetSplitedSnow() { // ****************** // 雪花分形从初始化三角形开始,需要6个顶点 // // 设置三角形顶点 float sqrt3 = sqrt(3.0f); VertexPosColor vertices[] = { { XMFLOAT3(-3.0f / 4,-sqrt3 / 4,{ XMFLOAT3(0.0f,sqrt3 / 2,{ XMFLOAT3(3.0f / 4,{ XMFLOAT3(-3.0f / 4,1.0f) } }; // 将三角形宽度和高度都放大3倍 for (VertexPosColor& v : vertices) { v.pos.x *= 3; v.pos.y *= 3; } // 设置顶点缓冲区描述 D3D11_BUFFER_DESC vbd; ZeroMemory(&vbd,m_pVertexBuffers[0].ReleaseAndGetAddressOf())); // 顶点数 m_VertexCounts[0] = 6; // 初始化所有顶点缓冲区 for (int i = 1; i < 7; ++i) { vbd.ByteWidth *= 4; m_VertexCounts[i] = m_VertexCounts[i - 1] * 4; HR(m_pd3dDevice->CreateBuffer(&vbd,m_pVertexBuffers[i].ReleaseAndGetAddressOf())); m_BasicEffect.SetStreamOutputSplitedSnow(m_pd3dImmediateContext.Get(),0); } } GameApp::ResetSplitedSphere方法--重新建立包含1-7阶的分形圆球顶点的缓冲区这里不使用 void GameApp::ResetSplitedSphere() { // ****************** // 分形球体 // VertexPosNormalColor basePoint[] = { { XMFLOAT3(0.0f,2.0f,XMFLOAT3(0.0f,{ XMFLOAT3(2.0f,XMFLOAT3(1.0f,2.0f),{ XMFLOAT3(-2.0f,XMFLOAT3(-1.0f,-2.0f),-1.0f),-2.0f,-1.0f,}; int indices[] = { 0,2,5,5 }; std::vector<VertexPosNormalColor> vertices; for (int pos : indices) { vertices.push_back(basePoint[pos]); } // 设置顶点缓冲区描述 D3D11_BUFFER_DESC vbd; ZeroMemory(&vbd,sizeof(vbd)); vbd.Usage = D3D11_USAGE_DEFAULT; // 这里需要允许流输出阶段通过GPU写入 vbd.ByteWidth = (UINT)(vertices.size() * sizeof(VertexPosNormalColor)); vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT; // 需要额外添加流输出标签 vbd.CPUAccessFlags = 0; // 新建顶点缓冲区 D3D11_SUBRESOURCE_DATA InitData; ZeroMemory(&InitData,sizeof(InitData)); InitData.pSysMem = vertices.data(); HR(m_pd3dDevice->CreateBuffer(&vbd,m_pVertexBuffers[0].ReleaseAndGetAddressOf())); // 顶点数 m_VertexCounts[0] = 24; // 初始化所有顶点缓冲区 for (int i = 1; i < 7; ++i) { vbd.ByteWidth *= 4; m_VertexCounts[i] = m_VertexCounts[i - 1] * 4; HR(m_pd3dDevice->CreateBuffer(&vbd,m_pVertexBuffers[i].ReleaseAndGetAddressOf())); m_BasicEffect.SetStreamOutputSplitedSphere(m_pd3dImmediateContext.Get(),0); } } 由于篇幅过大,该类的其它方法就在此省略,具体细节可以查阅该章对应源码。来看一下动图感受一些这些酷炫的效果吧: 分形三角形绘制效果分形雪花绘制效果分形圆球绘制效果由于文件大小限制,这里分成两个部分: 下面是带法向量的: 遗留问题由于我现在找不到ID3D11DeviceContext::DrawAuto方法无法输出的原因,故在这里使用的依然是原来的Draw方法进行绘制。若有人能够在不使用FX11框架的情况下能够调用DrawAuto方法绘制到流输出,可以在下面评论我,并告诉我解决方案。 除此之外,该项目使用图形调试器并退出的时候,会引发内存泄漏,而具体的泄漏对象估计是 DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- mvvm-light – 在Windows Phone MVVM应用程序中播放/暂停mp
- Windows上的Nodejitsu
- windows – 复选框 – 更改通知
- windows-7 – 错误:“保存给定卷的扩展区的所有磁盘必须具
- windows-server-2008 – Windows服务器需要多久重启一次?
- windows – 服务:“手动”和“禁用”之间是否有任何真正的
- 存储空间 – 存储空间直接 – 虚拟磁盘上的新卷
- 如何在Windows 7中安装和配置mysql 5.6.16
- windows-sbs-2011 – 如何将AD帐户限制为仅限特定计算机?
- 有时 – 无法构建Windows Azure云服务 – 对象引用未设置为