集成金属深度缓冲与SceneKit渲染

huangapple go评论46阅读模式
英文:

Integrating a metal depth buffer with scenekit rendering

问题

  1. SceneKit uses a reversed logarithmic z-buffer format for its depth values. The range is typically from 1 (near) to 0 (far).

  2. In your Metal shader for generating the z-buffer, it appears that you are correctly converting the depth values to a reversed logarithmic format for SceneKit using the formula:

float logDepth = log(z / zNear) / log(zFar / zNear);
out.depth = 1.0 - logDepth; // Reverse the depth for SceneKit

This part seems to be fine.

The issue might be in how you integrate the depth buffer into SceneKit with a full-screen quad and SCNProgram. Make sure that you set up the SCNProgram correctly and that the depth texture is being passed to the fragment shader as expected.

If objects drawn with SceneKit do not properly respect the depth buffer and are always showing or hidden, you might need to double-check the setup and ensure that the depth buffer is being used appropriately in your SceneKit rendering pipeline.

英文:

I'm using Metal to render a scene with a z buffer and now need to integrate this z-buffer into SceneKit's rendering. However I can't figure out how to get SceneKit to use this depth better correctly and am not even 100% sure what format SceneKit expects it's z-buffers to be in

Base on this question, my understanding was that SceneKit uses a reverse logarithmic z-buffer in the range of 1 (near) to 0 (far). However I can't get this working and objects I draw with SceneKit don't properly respect the depth buffer: they are either always showing or always hidden

First, here's how the generate a z-buffer texture in a Metal render pass:

struct FragmentOut {
    float4 color [[color(0)]];
    float depth [[depth(any)]];
};

fragment FragmentOut metalRenderFragment(const InOut in [[ stage_in ]]) {
    FragmentOut out;
    out.depth = 0; // 0 is far with reverse z buffer

    ...
    
    float cameraSpaceZ = ...; // Computed in shader

    // There constants are taken from SceneKit's camera and inlined here
    const float zNear = 0.0010000000474974513; 
    const float zFar = 1000.0;

    float logDepth = log(z / zNear) / log(zFar / zNear);

    out.depth = 1.0 - logDepth; // Reverse the depth for scenekit

    return out;
}

Then to integrate the depth buffer into SceneKit, I render a full screen quad in scenekit with a SCNProgram that uses the depth texture generated in the previous step:

fragment FragmentOut sceneKitFullScreenQuadFragment(const InOut in [[ stage_in ]],
                                depth2d<float, access::sample> depthTexture [[texture(1)]])
{
    constexpr sampler sampler(filter::linear);
    
    const float depth = depthTexture.sample(sampler, in.uv);
    return {
        .color = float4(0),
        .depth = depth,
    };
}

So two questions:

  1. What format does SceneKit use for its z-buffer? Is it a reversed logarithmic z-buffer?

  2. What am I doing wrong in generating the z-buffer values for SceneKit?

答案1

得分: 2

SceneKit使用反对数对数Z缓冲。这篇文章这篇文章展示了如何获得一个归一化的线性映射空间 [0...1]。您需要相反的公式。

此外,您可以通过以下方式将值从reverseZ切换到directZ:

let sceneView = self.view as! SCNView
sceneView.usesReverseZ = true // 默认值
英文:

SceneKit uses a reverse logarithmic Z-Buffer. This post and this post show you how to get a normalized linear mapping space [0...1]. You need the opposite formula.

Also, you can toggle the value from reverseZ to directZ this way:

let sceneView = self.view as! SCNView
sceneView.usesReverseZ = true             // default

答案2

得分: 0

Andy Jazz的回答有所帮助,但我仍然觉得链接令人困惑。以下是最终对我有用的方法(虽然可能还有其他方法可以实现):

在生成深度图时(这将位于我的原始示例中的金属着色器内部),传递SceneKit的投影变换矩阵,并使用它来转换深度值:

// 在生成深度图的金属着色器中

// 相机到物体的z距离,例如,如果当前位置处的物体距离相机5个单位,这将是5。
const float z = ...;

// 相机沿着-z轴指向,所以使用SceneKit的投影矩阵(您可以从SCNCamera获取)来变换-z位置
const float4 depthPos = (sceneKitState.projectionTransform * float4(0, 0, -z, 1));

// 然后进行透视除法以获得最终深度值
out.depth = depthPos.z / depthPos.w;

然后在SceneKit着色器内,简单地写出深度,考虑usesReverseZ

// 在SceneKit的全屏四边形着色器中

const float depth = depthTexture.sample(sampler, in.uv);
return {
    .color = float4(0),
    .depth = 1.0 - depth,
};

❗️ 以上假定您正在使用sceneView.usesReverseZ = true(默认值)。如果您使用usesReverseZ = false,则简单地使用.depth = depth

英文:

Andy Jazz's answer helped but I still found the links confusing. Here's what ultimately worked for me (although there are possibly other ways to do this):

When generating the depth map (this would be inside the the metal shader in my original example) pass in SceneKit's projection transform matrix and use this to transform the depth value:

// In a metal shader generating the depth map

// The z distance from the camera, e.g. if the object
// at the current position is 5 units away, this would be 5.
const float z = ...;

// The camera points along the -z axis, so transform the -z position
// with SceneKit's projection matrix (you can get this from SCNCamera)
const float4 depthPos = (sceneKitState.projectionTransform * float4(0, 0, -z, 1));

// Then do perspective division to get the final depth value
out.depth = depthPos.z / depthPos.w;

Then inside of the SceneKit shader, simply write out the depth, taking into account usesReverseZ:

// In a scenekit, full screen quad shader

const float depth = depthTexture.sample(sampler, in.uv);
return {
    .color = float4(0),
    .depth = 1.0 - depth,
};

❗️ The above assumes you are using sceneView.usesReverseZ = true (the default). If you are using usesReverseZ = false, simply do .depth = depth instead

huangapple
  • 本文由 发表于 2023年2月6日 08:10:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/75356379.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定