首页 > 解决方案 > SceneKit 在调用 didApplyConstraints 和 willRenderScene 之间做了什么?

问题描述

SceneKit 渲染循环在这里有很好的记录https://developer.apple.com/documentation/scenekit/scnscenerendererdelegate和这里​​ https://www.raywenderlich.com/1257-scene-kit-tutorial-with-swift-part-4 -渲染循环。然而,这些文档都没有解释 SceneKit 在调用 didApplyConstraints 和 willRenderScene 之间的作用。

我已经修改了我的 SCNSceneRendererDelegate 来测量每次调用之间的时间,我可以看到这两个调用之间经过了大约 5 毫秒。那时它没有运行我的代码,但大概我设置场景的方式的某些方面是创建必须在那里完成的工作。任何深入了解 SceneKit 正在做什么都会非常有帮助。

我自己从 MTKView 的绘图调用(而不是使用 SCNView)调用 SceneKit,以便我可以渲染场景两次。第一个渲染是正常的,第二个使用第一个的深度缓冲区,但只绘制了我想要“发光”到单独颜色缓冲区的场景子集。然后将该颜色缓冲区按比例缩小、高斯模糊、按比例放大,然后在第一个场景的顶部混合(全部使用自定义金属着色器)。

当我引入这个额外的渲染通道时,在 didApplyConstraints 和 willRenderScene 之间花费的 5ms 开始发生。为了控制每个场景中的哪些节点,我在 0 和 1 之间切换少量父节点的不透明度。如果我删除切换不透明度但保留其他所有内容的代码(因此有两个渲染通道,但它们都绘制了所有内容)额外的 5 毫秒消失了,整体帧速率实际上更快,即使正在发生更多的渲染。

我正在 2018 MacBook Pro 上编写针对 MacOS 的 Swift。

更新:mnuages 解释说,改变不透明度会导致 SceneKit 重建场景图,它解释了部分损失的时间。但是,我现在发现我在一次渲染过程中对节点使用自定义 SCNProgram 也会触发 didApplyConstraints 和 willRenderScene 之间的 5ms 暂停。有谁知道这可能是为什么?

这是我设置 SCNProgram 和 SCNMaterial 的代码,都完成了一次:

let device = MTLCreateSystemDefaultDevice()!
let library = device.makeDefaultLibrary()
glowProgram = SCNProgram()
glowProgram.library = library
glowProgram.vertexFunctionName = "emissionGlowVertex"
glowProgram.fragmentFunctionName = "emissionGlowFragment"

...

let glowMaterial = SCNMaterial()
glowMaterial.program = glowProgram
let emissionImageProperty = SCNMaterialProperty(contents: emissionImage)
glowMaterial.setValue(emissionImageProperty, forKey: "tex")

这是我将材料应用于节点的地方:

let nodeWithGeometryClone = nodeWithGeometry.clone()
nodeWithGeometryClone.categoryBitMask = 2
let geometry = nodeWithGeometryClone.geometry!
nodeWithGeometryClone.geometry = SCNGeometry(sources: geometry.sources, elements: geometry.elements)
glowNode.addChildNode(nodeWithGeometryClone)
nodeWithGeometryClone.geometry!.firstMaterial = glowMaterial

发光节点是常规节点的深层克隆,但具有替代 SCNProgram。这是金属代码:

#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>

struct NodeConstants {
    float4x4 modelTransform;
    float4x4 modelViewProjectionTransform;
};

struct EmissionGlowVertexIn {
    float3 pos [[attribute(SCNVertexSemanticPosition)]];
    float2 uv [[attribute(SCNVertexSemanticTexcoord0)]];
};

struct EmissionGlowVertexOut {
    float4 pos [[position]];
    float2 uv;
};

vertex EmissionGlowVertexOut emissionGlowVertex(EmissionGlowVertexIn in [[stage_in]],
                                               constant NodeConstants &scn_node [[buffer(1)]]) {
    EmissionGlowVertexOut out;
    out.pos = scn_node.modelViewProjectionTransform * float4(in.pos, 1) + float4(0, 0, -0.01, 0);
    out.uv = in.uv;
    return out;
}

constexpr sampler linSamp = sampler(coord::normalized, address::clamp_to_zero, filter::linear);

fragment half4 emissionGlowFragment(EmissionGlowVertexOut in [[stage_in]],
                                    texture2d<half, access::sample> tex [[texture(0)]]) {
    return tex.sample(linSamp, in.uv);
}

标签: scenekit

解决方案


通过更改节点的不透明度,您会使场景图的某些部分无效,这可能会导致渲染器的额外工作。

看看设置相机的categoryBitMask性能是否更好(它不会修改场景图)会很有趣。


推荐阅读