3 votes

Tampon de profondeur métallique de SceneKit

J'essaie d'écrire une application de réalité augmentée à l'aide de SceneKit, et j'ai besoin de points 3D précis à partir de l'image rendue actuelle, étant donné un pixel 2D et une profondeur à l'aide de la fonction UnprojectPoint de SCNSceneRenderer méthode. Cela nécessite un x, un y et un z, où le x et le y sont des coordonnées de pixel et où le z est normalement une valeur lue dans le tampon de profondeur de cette image.

Le délégué du SCNView dispose de cette méthode pour rendre le cadre de profondeur :

func renderer(_ renderer: SCNSceneRenderer, willRenderScene scene: SCNScene, atTime time: TimeInterval) {
    renderDepthFrame()
} 

func renderDepthFrame(){

    // setup our viewport
    let viewport: CGRect = CGRect(x: 0, y: 0, width: Double(SettingsModel.model.width), height: Double(SettingsModel.model.height))

    // depth pass descriptor
    let renderPassDescriptor = MTLRenderPassDescriptor()

    let depthDescriptor: MTLTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: MTLPixelFormat.depth32Float, width: Int(SettingsModel.model.width), height: Int(SettingsModel.model.height), mipmapped: false)
    let depthTex = scnView!.device!.makeTexture(descriptor: depthDescriptor)
    depthTex.label = "Depth Texture"
    renderPassDescriptor.depthAttachment.texture = depthTex
    renderPassDescriptor.depthAttachment.loadAction = .clear
    renderPassDescriptor.depthAttachment.clearDepth = 1.0
    renderPassDescriptor.depthAttachment.storeAction = .store

    let commandBuffer = commandQueue.makeCommandBuffer()

    scnRenderer.scene = scene
    scnRenderer.pointOfView = scnView.pointOfView!

    scnRenderer!.render(atTime: 0, viewport: viewport, commandBuffer: commandBuffer, passDescriptor: renderPassDescriptor)

    // setup our depth buffer so the cpu can access it
    let depthImageBuffer: MTLBuffer = scnView!.device!.makeBuffer(length: depthTex.width * depthTex.height*4, options: .storageModeShared)
    depthImageBuffer.label   = "Depth Buffer"
    let blitCommandEncoder: MTLBlitCommandEncoder = commandBuffer.makeBlitCommandEncoder()
    blitCommandEncoder.copy(from: renderPassDescriptor.depthAttachment.texture!, sourceSlice: 0, sourceLevel: 0, sourceOrigin: MTLOriginMake(0, 0, 0), sourceSize: MTLSizeMake(Int(SettingsModel.model.width), Int(SettingsModel.model.height), 1), to: depthImageBuffer, destinationOffset: 0, destinationBytesPerRow: 4*Int(SettingsModel.model.width), destinationBytesPerImage: 4*Int(SettingsModel.model.width)*Int(SettingsModel.model.height))
    blitCommandEncoder.endEncoding()

    commandBuffer.addCompletedHandler({(buffer) -> Void in
        let rawPointer: UnsafeMutableRawPointer = UnsafeMutableRawPointer(mutating: depthImageBuffer.contents())
        let typedPointer: UnsafeMutablePointer<Float> = rawPointer.assumingMemoryBound(to: Float.self)
        self.currentMap = Array(UnsafeBufferPointer(start: typedPointer, count: Int(SettingsModel.model.width)*Int(SettingsModel.model.height)))

    })

    commandBuffer.commit()

}

Ça marche. J'obtiens des valeurs de profondeur entre 0 et 1. Le problème est que je ne peux pas les utiliser dans l'unprojectPoint parce qu'elles ne semblent pas être mises à l'échelle de la même manière que la passe initiale, malgré l'utilisation des mêmes SCNScene et SCNCamera.

Mes questions :

  1. Existe-t-il un moyen d'obtenir les valeurs de profondeur directement à partir de la passe principale de SceneKit SCNView sans avoir à effectuer une passe supplémentaire avec un SCNRenderer séparé ?

  2. Pourquoi les valeurs de profondeur dans mon passage ne correspondent-elles pas aux valeurs que j'obtiens en effectuant un test d'impact puis en déprojetant ? Les valeurs de profondeur de ma passe vont de 0,78 à 0,94. Les valeurs de profondeur du test d'impact vont de 0,89 à 0,97, ce qui, curieusement, correspond aux valeurs de profondeur OpenGL de la scène lorsque je l'ai rendue en Python.

Je pense que c'est une différence entre les fenêtres d'affichage et que SceneKit fait quelque chose pour mettre à l'échelle les valeurs de profondeur de -1 à 1, tout comme OpenGL.

EDIT : Et au cas où vous vous poseriez la question, je ne peux pas utiliser directement la méthode hitTest. C'est trop lent pour ce que j'essaie de faire.

1voto

hughesj919 Points 26

Comme solution de contournement, je suis passé à OpenGL ES et j'ai lu le tampon de profondeur en ajoutant un fragment shader qui place la valeur de la profondeur dans le tampon de rendu RGBA SCNShadable.

Voir ici pour plus d'informations : http://concord-consortium.github.io/lab/experiments/webgl-gpgpu/webgl.html

Je comprends qu'il s'agit d'une approche valable, car elle est souvent utilisée pour le mappage d'ombres sur les périphériques OpenGL ES et WebGL, mais j'ai l'impression de faire du bricolage et je ne devrais pas avoir à le faire. Je serais toujours intéressé par une autre réponse si quelqu'un peut comprendre la transformation du viewport de Metal.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X