在 SCNShadable 入口点之间传递值

Passing values between SCNShadable entry points

在 OpenGL 程序中,您通常会在顶点着色器中声明类似这样的内容

varying bool aBooleanVariable;

然后读取片段着色器中的值。您如何在 SCNShadable 入口点的框架内执行此操作?例如从 SCNShaderModifierEntryPointGeometrySCNShaderModifierEntryPointFragment.

接收参数好像是用pragma参数定义的,我提供我的测试SCNShaderModifierEntryPointFragment来说明。

#pragma arguments
bool clipFragment

#pragma body

if (clipFragment) {
    discard_fragment();
}

但是,参数 pragma 不适用于在 SCNShaderModifierEntryPointGeometry 入口点输出值。

found an article 这表明它可以用 GLSL 语法完成,但我试图找到 Metal 方式,但我什至无法重现结果。

在某些时候,您会遇到使用 SCNShadable 可以完成的事情的限制,需要移动到 SCNProgram。不幸的是,这样做意味着您需要提供顶点和片段着色器的完整实现。从好的方面来说,从顶点向片段着色器传递一个值是相当简单的。您只需向使用 stage_in 限定符标识的结构添加一个变量(假设您使用的是 Metal)。但这并不是您真正要求的,并且有一个解决方法。

#pragma arguements 如您所述,在这种情况下不起作用;它用于传递在渲染过程中不变的常量。

相反,我建议您考虑重新利用 SceneKit 在其着色器中使用的现有变量之一。到目前为止,您肯定已经注意到 SceneKit 在编译失败时将其着色器源代码转储到标准输出。 Metal 着色器定义了一个 commonprofile_io 结构,它从顶点传递到片段着色器,它包括:位置、颜色、纹理坐标、法线等。如果你不使用顶点颜色,那么可以使用这个存储 4 个浮点值 (rgba)。重要的是要注意 SceneKit 为正在渲染的几何源优化着色器;如果您的几何源不包含顶点颜色,则 commonprofile_io 结构将没有 vertexColor 变量。

非常想知道更好的解决方案。

被迫使用 GLSL

事实证明,如果将 SCNView 渲染 API 切换为 OpenGL ES,就可以让它工作。然后它将接受包含可变限定符的 GLSL 代码。

几何入口点

varying float clipFragment;

#pragma body
clipFragment = 1.0;

片段入口点

varying float clipFragment;

#pragma body
if (clipFragment > 0.0) {
    discard;
}

来自 Apple 的更新

我在 Apple 开了一张工单,得到了以下回复 -

没错。 SceneKit 团队已提出正式的增强请求(bug #28206462)以研究将此功能添加到 Metal。在那之前,您必须选择使用 OpenGL 渲染器。

我遇到了类似的问题并设法找到了解决方案。我不确定这有多远,但它适用于 XCode 9 和 iOS 11。这是一个使用不同的 float3 作为坐标查找自定义立方体贴图纹理的示例。

几何修改器:

#include <metal_stdlib>

#pragma varyings
float3 cubemapCoord;

#pragma body
out.cubemapCoord = _geometry.normal;

表面改性剂:

#include <metal_stdlib>

#pragma arguments
texturecube<float> texture;

#pragma body
constexpr sampler s(filter::linear, mip_filter::linear);
_surface.diffuse = texture.sample(s, in.cubemapCoord);

和Swift分配material的代码:

do {
    let loader = MTKTextureLoader.init(device: MTLCreateSystemDefaultDevice()!)
    let url = Bundle.main.url(forResource: "cubemap", withExtension: "pvr", subdirectory: "art.scnassets")!
    let texture = try loader.newTexture(URL: url, options: nil)
    let materialProperty = SCNMaterialProperty.init(contents: texture)
    geometry.setValue(materialProperty, forKey: "texture")
} catch {
    print(error.localizedDescription)
}

我也一直在为这个问题苦苦挣扎,而且我最近的一个发现让我对这个问题大开眼界。它极大地帮助我掌握了 SceneKit、着色器、着色器修改器和 OpenGL/Metal 如何协同工作以及 tools/methods 我在编写着色器修改器时可用的内容。 此外,我认为目前接受的答案不再正确(幸运的是!)。

SceneKit 提供默认的顶点和片段金属着色器。正是在这些着色器中注入了着色器修改器。 当您在 Xcode 中捕获 GPU 帧时,您可以找到这些默认的 Metal 着色器。 双击 commonprofile_vertcommonprofile_frag(两个着色器都在同一个 "file" 内,如“详细信息”列中的相同值所示)。

此文件中有 很多 信息,它演示了渲染时 SceneKit 如何与 GPU 通信的一些内部工作原理。 This is an example of the file(虽然这个版本已经有几年了,但它仍然是典范)。

注意以下几行是着色器修改器的注入位置。

// DoLightModifier START

// DoLightModifier END

...

// DoGeometryModifier START

// DoGeometryModifier END

...

// DoSurfaceModifier START

// DoSurfaceModifier END

...

// DoFragmentModifier START

// DoFragmentModifier END

您会看到您的着色器修改器已放置在这里。

寻找 #ifdef USE_EXTRA_VARYINGS。您在几何修饰符中声明的任何 varying 变量在 commonprofile_io 结构上显示为 属性。其中您使用以下实例:commonprofile_io out;.

此文件还演示了为什么需要 varyings 来实现几何体和片段着色器之间的通信。以及为什么 _surface 可以在片段着色器中访问:片段修改器和表面修改器具有相同的范围。

我完全不明白 none 这些东西是如何被 Apple 记录甚至暗示的,除了一些隐藏的 WWDC 幻灯片。我不知道没有几周空闲时间的人应该如何弄清楚这一切是如何运作的。