【问题标题】:Draw portion of MTLBuffer?绘制 MTLBuffer 的一部分?
【发布时间】:2019-10-12 00:16:25
【问题描述】:

我正在使用此调用从缓冲区渲染点片段:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: 0,
                             vertexCount: 1,
                             instanceCount: emitter.currentParticles)

emitter.currentParticles 是缓冲区中的粒子总数。是否可以以某种方式仅绘制缓冲区的一部分?

我试过这个,但它绘制了缓冲区的前半部分:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: emitter.currentParticles / 2,
                             vertexCount: 1,
                             instanceCount: emitter.currentParticles / 2)

其实vertexStart似乎没有任何作用。我似乎可以将它设置为任何值,它仍然从 0 开始。

编辑:

管道配置:

private func buildParticlePipelineStates() {
    do {
        guard let library = Renderer.device.makeDefaultLibrary(),
        let function = library.makeFunction(name: "compute") else { return }

        // particle update pipeline state
        particlesPipelineState = try Renderer.device.makeComputePipelineState(function: function)

        // render pipeline state
        let vertexFunction = library.makeFunction(name: "vertex_particle")
        let fragmentFunction = library.makeFunction(name: "fragment_particle")
        let descriptor = MTLRenderPipelineDescriptor()
        descriptor.vertexFunction = vertexFunction
        descriptor.fragmentFunction = fragmentFunction

        descriptor.colorAttachments[0].pixelFormat = renderPixelFormat
        descriptor.colorAttachments[0].isBlendingEnabled = true
        descriptor.colorAttachments[0].rgbBlendOperation = .add
        descriptor.colorAttachments[0].alphaBlendOperation = .add
        descriptor.colorAttachments[0].sourceRGBBlendFactor = .sourceAlpha
        descriptor.colorAttachments[0].sourceAlphaBlendFactor = .sourceAlpha
        descriptor.colorAttachments[0].destinationRGBBlendFactor = .oneMinusSourceAlpha
        descriptor.colorAttachments[0].destinationAlphaBlendFactor = .oneMinusSourceAlpha

        renderPipelineState = try
        Renderer.device.makeRenderPipelineState(descriptor: descriptor)
        renderPipelineState = try Renderer.device.makeRenderPipelineState(descriptor: descriptor)
    } catch let error {
        print(error.localizedDescription)
    }
}

顶点着色器:

struct VertexOut {
    float4 position   [[ position ]];
    float  point_size [[ point_size ]];
    float4 color;
};


vertex VertexOut vertex_particle(constant float2 &size [[buffer(0)]],
                             device Particle *particles [[buffer(1)]],
                             constant float2 &emitterPosition [[ buffer(2) ]],
                             uint instance [[instance_id]])
{
    VertexOut out;

    float2 position = particles[instance].position + emitterPosition;
    out.position.xy = position.xy / size * 2.0 - 1.0;
    out.position.z = 0;
    out.position.w = 1;
    out.point_size = particles[instance].size * particles[instance].scale;
    out.color = particles[instance].color;
    return out;
}

fragment float4 fragment_particle(VertexOut in [[ stage_in ]],
                              texture2d<float> particleTexture [[ texture(0) ]],
                              float2 point [[ point_coord ]]) {
    constexpr sampler default_sampler;

    float4 color = particleTexture.sample(default_sampler, point);

    if ((color.a < 0.01) || (in.color.a < 0.01)) {
        discard_fragment();
    }
    color = float4(in.color.xyz, 0.2 * color.a * in.color.a);
    return color;
}

【问题讨论】:

  • 您使用实例计数而不是顶点计数来表示要渲染的点数是什么?
  • 这就是他们在我使用的 Metal by Tutorials 一书中的示例中设置它的方式。那么,我应该将 instaceCount 设置为 1,并将 vertexCount 设置为要渲染的粒子数吗?要么?是否可以绘制缓冲区的范围?
  • 实例化是一种特殊的、有点先进的技术。如果你不熟悉它,你可能还不应该使用它。所以,是的,您通常应该使用 vertexCount 作为要渲染的粒子数。您可以将 instanceCount 指定为 1 或仅使用不带 instanceCount 参数的方法。综上所述,是否使用实例化对顶点描述符(如果您正在使用它)和顶点着色器都有影响。这些可能需要调整,但我不能告诉你如何调整,因为你没有展示它们。
  • 看来它必须设置为使用实例化,因为我尝试按照您的建议进行更改,但它不起作用。如果更多代码会有所帮助,请告诉我您想看到什么。知道为什么 vertexStart 似乎没有效果吗?
  • 显示你是如何配置管道描述符的,特别是它的顶点描述符,如果你正在使用它。另外,显示顶点着色器。

标签: ios rendering metal mtlbuffer


【解决方案1】:

您的顶点着色器没有使用顶点描述符或[[stage_in]] 参数。因此,Metal 不会为您获取/收集顶点数据。您只是索引到一个缓冲区,该缓冲区已经以您想要的格式布置了您的顶点数据。没关系。有关顶点描述符的更多信息,请参阅我的回答 here

尽管如此,绘图调用的vertexStart 参数仅影响具有[[vertex_id]] 属性的顶点函数的参数值。您的顶点函数没有这样的参数,更不用说使用它了。相反,它使用[[instance_id]] 参数来索引顶点数据缓冲区。您可以阅读我的另一个答案 here,快速了解绘制调用以及它们如何导致调用顶点着色器函数。

有几种方法可以改变事物以仅绘制一半的点。您可以将您使用的绘图调用更改为:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: 0,
                             vertexCount: 1,
                             instanceCount: emitter.currentParticles / 2,
                             baseInstance: emitter.currentParticles / 2)

这不需要对顶点着色器进行任何更改。它只是改变了输入instance 参数的值的范围。但是,由于这似乎不是实例化的情况,我建议您更改着色器和绘制调用。对于着色器,将instance 参数重命名为vertexvid,并将其属性从[[instance_id]] 更改为[[vertex_id]]。然后,将绘图调用更改为:

renderEncoder.drawPrimitives(type: .point,
                             vertexStart: emitter.currentParticles / 2,
                             vertexCount: emitter.currentParticles / 2)

实际上,在这种情况下,它们的行为方式基本相同,但后者更好地代表了您正在做的事情(并且绘制调用更简单,这很好)。

【讨论】:

  • 谢谢。如果我使用实例化,您认为我会获得更好的性能吗?我试图让尽可能多的粒子在一次通过中渲染。我猜如何在此设置中使用实例化是另一个问题。 ;)
  • 如果可以的话,还有一个问题(如果可能的话,我会发布另一个问题)。给定相同的发射器缓冲区,是否可以说每个点有 10 个不同的实例,每个实例的坐标略有不同?
  • 我不确定,但我怀疑实例化性能会更好(或更差)。对于您的第二个问题,是的,这或多或少是实例化的重点,无需在 CPU 上为它们计算单独的顶点数据,也无需将那么多顶点数据发送到 GPU。
猜你喜欢
  • 2019-10-30
  • 1970-01-01
  • 2012-03-28
  • 2013-10-22
  • 2011-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-05
相关资源
最近更新 更多