如何使用Stencil测试在金属上绘制凹形

How to draw concave shape using Stencil test on Metal

这是我第一次尝试使用 Stencil 测试,但我看到了一些使用 OpenGL 的示例和一些使用 Metal 的示例,但我专注于深度测试。我了解 Stencil 测试背后的理论,但我不知道如何在 Metal 上设置它。

我想画不规则的形状。为了简单起见,让我们考虑以下二维多边形:

concave shape

我想让模板通过重叠三角形数量为奇数的地方,这样我就可以达到这样的效果,白色区域是要忽略的区域:

Area to be ignored

我正在按照确切的顺序执行以下步骤:

设置深度模板像素格式:

mtkView.depthStencilPixelFormat = .stencil8
mtkView.clearStencil = .allZeros

模具附件:

let textureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .stencil8, width: drawable.texture.width, height: drawable.texture.height, mipmapped: true)

textureDescriptor.textureType = .type2D
textureDescriptor.storageMode = .private
textureDescriptor.usage = [.renderTarget, .shaderRead, .shaderWrite]
mainPassStencilTexture = device.makeTexture(descriptor: textureDescriptor)

let stencilAttachment = MTLRenderPassStencilAttachmentDescriptor()

stencilAttachment.texture = mainPassStencilTexture
stencilAttachment.clearStencil = 0
stencilAttachment.loadAction = .clear
stencilAttachment.storeAction = .store
renderPassDescriptor.stencilAttachment = stencilAttachment

模板描述符:

stencilDescriptor.depthCompareFunction = MTLCompareFunction.always
stencilDescriptor.isDepthWriteEnabled = true

stencilDescriptor.frontFaceStencil.stencilCompareFunction = MTLCompareFunction.equal
stencilDescriptor.frontFaceStencil.stencilFailureOperation = MTLStencilOperation.keep
stencilDescriptor.frontFaceStencil.depthFailureOperation = MTLStencilOperation.keep
stencilDescriptor.frontFaceStencil.depthStencilPassOperation = MTLStencilOperation.invert

stencilDescriptor.frontFaceStencil.readMask = 0x1
stencilDescriptor.frontFaceStencil.writeMask = 0x1
stencilDescriptor.backFaceStencil = nil

depthStencilState =  device.makeDepthStencilState(descriptor: stencilDescriptor)

最后,我在主通道中设置参考值和模板状态:

renderEncoder.setStencilReferenceValue(0x1)
renderEncoder.setDepthStencilState(self.depthStencilState)

我是不是遗漏了什么,因为我得到的结果就像根本没有模板一样。在更改深度测试的设置时我可以看到一些差异,但在更改模板的设置时没有任何反应 ...

有什么线索吗?

提前致谢

您正在将模板纹理清除为0。参考值为1。比较函数为"equal"。因此,比较将失败(1 不等于 0)。模板比较失败时的操作是"keep",所以模板纹理保持0。后续片段没有变化。

我预计你不会得到任何渲染,尽管根据你的顶点顺序和正面缠绕模式,你可能正在看三角形的背面,在这种情况下模板测试被有效禁用。如果您不关心正面与背面,只需以相同的方式设置两个模板描述符即可。

我认为您需要进行两次渲染:首先,仅模板渲染;其次,由模板缓冲区控制的颜色渲染。仅对于模板,您可以使比较函数 .always。这将切换(反转)在给定像素上绘制的每个三角形的低位,从而指示偶数或奇数。因为比较函数和运算都不涉及参考值,所以不管是什么都无所谓

对于第二遍,您将比较函数设置为 .equal,将参考值设置为 1。所有操作都应为 .keep。另外,确保将模板附件加载操作设置为 .load(而不是 .clear)。