今天看第四个example。老方法,通过对比来学习。
一,效果
二,提问并解决
- 图中涉及到了模型,一个是牛一个是龙。更换模型的代码应该如何设计?在哪里修改?
答:设计方法,通过周期draw函数中的vkCmdUpdateBuffer来实现。
uint32_t psoIndices[NumDrawIndirectCommands]
{
models[mCurrentModel].pipelineObjectTableIndices[mRenderModeOverride[0]],
models[mCurrentModel].pipelineObjectTableIndices[mRenderModeOverride[1]]
};
NvVkContext::DebugMarkerScope marker(vk(), mainCmd, "UpdateDeviceGeneratedPSOBuffer");
vkCmdUpdateBuffer(mainCmd, deviceGeneratedPsoBuffer.buffer, 0, sizeof(psoIndices), psoIndices);
- render模式可以设置3个,然后随意选择的,如何设计代码?
答:
- 初始化设置3种pipeline(里面的render mode分别为points/lines/solid)其他的设置项可以共用。
// point
pipelineInfo.pRasterizationState = &rsStateInfoPoint;
result = vkCreateGraphicsPipelines(device(), VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &models[i].pipelines[FillPoint]);
// wireframe
pipelineInfo.pRasterizationState = &rsStateInfoWireFrame;
result = vkCreateGraphicsPipelines(device(), VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &models[i].pipelines[FillLine]);
// solid
pipelineInfo.pRasterizationState = &rsStateInfoSolid;
result = vkCreateGraphicsPipelines(device(), VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &models[i].pipelines[FillSolid]);
2)周期运行时,执行command buffer时候绑定选择的pipeline
// PSO
{
NvVkContext::DebugMarkerScope marker(vk(), mainCmd, "Part0");
vkCmdBindPipeline(mainCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, models[mCurrentModel].pipelines[mRenderModeOverride[0]]);
vkCmdDrawIndexed(mainCmd, drawIndirectArgs[0].indexCount, drawIndirectArgs[0].instanceCount, drawIndirectArgs[0].firstIndex, drawIndirectArgs[0].vertexOffset, drawIndirectArgs[0].firstInstance);
}
// PSO
{
NvVkContext::DebugMarkerScope marker(vk(), mainCmd, "Part1");
vkCmdBindPipeline(mainCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, models[mCurrentModel].pipelines[mRenderModeOverride[1]]);
vkCmdDrawIndexed(mainCmd, drawIndirectArgs[1].indexCount, drawIndirectArgs[1].instanceCount, drawIndirectArgs[1].firstIndex, drawIndirectArgs[1].vertexOffset, drawIndirectArgs[1].firstInstance);
}
- 可以分别为左边和右边进行不同的格式渲染,如何做到的?
答:可以理解为能单独控制模型的一半数据进行渲染。也可以理解为控制2个数据。所以把原始数据一份为二即可。最后通过vkCmdDrawIndexed来绘制顶点数据。
const float r = meshSplitRatio / float(MeshSplitRange);
const int32_t triangleCount = model.getIndexCount() / 3;
const int32_t splitTriangleCount = triangleCount *r;
const int32_t splitIndex = splitTriangleCount * 3;
const int32_t remaindingIndices = 3 * triangleCount - splitIndex;
// subset of mesh to be rendered with the first PSO
drawIndirectArgs[0].firstIndex = 0;
drawIndirectArgs[0].firstInstance = 0;
drawIndirectArgs[0].indexCount = splitIndex;
drawIndirectArgs[0].vertexOffset = 0;
drawIndirectArgs[0].instanceCount = 1;
// subset of the mesh rendered with the second PSO
drawIndirectArgs[1].firstIndex = splitIndex;
drawIndirectArgs[1].firstInstance = 0;
drawIndirectArgs[1].indexCount = remaindingIndices;
drawIndirectArgs[1].vertexOffset = 0;
drawIndirectArgs[1].instanceCount = 1;
- draw mode设置为可选代码如何设计?
答:代码通过switch (mDrawMode),最后可以调用vkCmdDrawIndexed或者vkCmdDrawIndexedIndirect等。
5.什么时候使用间接绘制?
在vkCmdDraw() and vkCmdDrawIndexed()命令中,命令的参数(vertexCount,vertexOffset, 等等)以立即参数直接传递给命令本身。这意味这你需要知道在你的应用程序中构建命令缓冲区时每一次绘制调用的准确参数。然而,在一些情况下,你并不知道每一次绘制的准确参数。比如以下例子:
1)几何物体的所有结构是已知的,但是顶点的个数和在顶点缓冲区的位置是未知的,一遍一个对象总是参加渲染但是LoD的层数会改变。
2)绘制命令由设备生成,而非主机。在这个情况下,顶点数据的个数和布局永远被会被主机端所知。
在这些情况下,你可以使用间接绘制,此时,绘制命令可以从设备可访问的内存获取参数,而非把参数随着命令嵌入在命令缓冲区中。第一个间接绘制命令是vkCmdDrawIndirect(),它执行非索引化绘制,使用的参数包含在一个缓冲区中
6.vkCmdBindDescriptorSets函数时设置pDynamicOffsets?
果需要高频修改uniform或storage缓冲offset(比如在每个draw操作进行修改),我们推荐使用。建议:
尽可能早地创建描述符集。
不要绑定没有被draw操作使用的资源。
如果需要高频修改缓冲offset(比如在不同draw操作之间进行修改),应该在绑定描述符时使用pDynamicOffsets。
避免高频修改描述符集。如果需要动态更新描述符集,需要进行同步,保证数据安全。
其实本教程没有动态更新描述符集,也不算高频修改uniform缓冲。所以我把vkCmdBindDescriptorSets注释掉,功能还是一样的,估计在此教程总是为了性能。
7.更新描述符?
此例子比昨天的例子中多了贴图,为image增加了更新描述符。我们要提供特定的资源(采集器,图像视图,缓冲区或缓冲区视图),这些资源应该稍后通过描述符集绑定到管道。定义应该使用的资源是通过更新描述符集的过程完成的。
若用顶点着色器来更新效果的话,所有顶点数据都要保存到shader中,很大。
疑问
- 关于VkWriteDescriptorSet的作用和效果还不是很理解,后续关注。
- 那些模型可以分为一半,我若替换为其它obj模型,是否能直接使用,还要修改那些代码,后续继续关注。