【问题标题】:Metal depth test not working with multiple draw calls金属深度测试不适用于多个绘制调用
【发布时间】:2021-08-28 20:07:56
【问题描述】:

我在我的 Metal 渲染器中启用了深度测试,创建了一个 MTLPixelFormatDepth32Float_Stencil8 格式的 MTLTexture。

然后我为我的渲染通道创建一个深度附件:

renderPassDescriptor.depthAttachment.texture = textureDepth;
renderPassDescriptor.depthAttachment.clearDepth = 1.0;
renderPassDescriptor.depthAttachment.loadAction = MTLLoadActionLoad;
renderPassDescriptor.depthAttachment.storeAction = MTLStoreActionDontCare;

最后,我像往常一样设置编码器的深度模板状态:

[renderEncoder setDepthStencilState:depthStencilState];

配置如下:

MTLDepthStencilDescriptor *depthStencilDescriptor = [MTLDepthStencilDescriptor new];
depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionLess;
depthStencilDescriptor.depthWriteEnabled = YES;
depthStencilState = [gpu newDepthStencilStateWithDescriptor:depthStencilDescriptor];

我还确保使用正确的深度像素格式配置我的管道状态描述符:

pipelineStateDescriptor.depthAttachmentPixelFormat = MTLPixelFormatDepth32Float_Stencil8;

这足以让深度测试适用于单次绘制调用。所有顶点都以正确的顺序呈现。

奇怪的是,如果我在单独的绘制调用中绘制多个对象,它们仍然表现得好像没有启用深度测试一样。它们可以很好地渲染自己,但它们的深度仍然取决于绘制调用顺序,而不是它们的实际深度值。

在 OpenGL 中一切正常,正确的输出如下所示:

然而,在 Metal 中,我得到以下输出(注意右侧的框重叠):

首先渲染左边的盒子,然后是右边的两个盒子。

我还在 GPU 帧捕获中查看了深度缓冲区,粗略一看,它似乎是正确的:

我还需要启用/配置什么以允许根据框的深度对框进行正确排序吗?

对于我的投影矩阵,我将它与模型矩阵一起创建(使用 AAPLMathUtilities 进行矩阵函数):

  vector_float3 translate = {mesh->x, mesh->y, mesh->z};
  vector_float3 scale = {mesh->sx, mesh->sy, mesh->sz};
  matrix_float4x4 modelMatrix = matrix_multiply(matrix4x4_translation(translate), matrix4x4_scale(scale));
  vector_float3 eye = { item->camera_position.x, item->camera_position.y, item->camera_position.z };
  vector_float3 target = {
    item->camera_position.x + item->camera_front.x,
    item->camera_position.y + item->camera_front.y,
    item->camera_position.z + item->camera_front.z
  };
  vector_float3 up = { item->camera_up.x, item->camera_up.y, item->camera_up.z };
  matrix_float4x4 viewMatrix = matrix_look_at_right_hand(eye, target, up);

  auto modelViewMatrix = matrix_multiply(viewMatrix, modelMatrix);
  auto projectionMatrix = matrix_perspective_right_hand(radians_from_degrees(45.0f), screenWidth / screenHeight, 0.1f, 100.0f);

着色器计算顶点位置如下:

float4 position = float4(in.data.xyz, 1.0);
out.position = uniforms.projectionMatrix * uniforms.modelViewMatrix * position;

【问题讨论】:

  • 您为金属输出的深度范围是多少?你如何创建投影矩阵?我问的原因是因为在 OpenGL 中深度将从 -1 变为 1,但在 Metal 中它从 0 变为 1
  • @JustSomeGuy 我已经用关于投影矩阵的更多细节更新了这个问题。我使用的深度范围是 0.1f 到 100.0f。
  • 我尝试切换到像在我的 OpenGL 渲染器中那样使用 GLM,并且使用我在 OpenGL 中使用的完全相同的投影矩阵代码,我仍然遇到问题,所以我认为这与投影无关矩阵。

标签: metal


【解决方案1】:

事实证明,我应该使用MTLStoreActionStore,而不是使用MTLStoreActionDontCare 作为renderPassDescriptor.depthAttachment.storeAction。这修复了深度排序并使渲染输出与 OpenGL 保持一致。

【讨论】:

  • 只有当您在一个命令编码器中写入深度并在另一个命令编码器中读取它时才会产生效果。你在做深度预通吗?
  • @JustSomeGuy 目前我的命令编码器和绘图调用之间的比率为 1:1,我不使用深度预通。这是错误的做法吗?
  • 为每个绘图调用制作编码器是错误的做法。您应该在一个编码器中渲染尽可能多的东西。那么不存储深度不是问题。我可以详细说明:每次你结束一个编码器,驱动程序实际上会执行存储动作,而每次你启动一个编码器,它会执行所有的加载动作。这意味着,它将为您拥有的每个绘图调用写入和读取附件。内存带宽有限,短时间内无法画很多东西。
  • 经验法则基本上是:使用尽可能少的编码器。仅当您确实需要在其他地方使用它们产生的结果时才结束编码器:含义、计算编码器,或者您需要整个纹理用于​​其他绘制调用。否则,请尝试使用可编程混合和平铺着色器(如果它们在平台上可用),因为它们可以为您提供一种在不结束编码器的情况下运行求和处理的方法。
  • @JustSomeGuy 感谢您的提示!通过切换到使用一个渲染编码器,免费获得 20 FPS 的提升。
猜你喜欢
  • 2014-04-28
  • 2017-09-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-11
  • 2017-05-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多