在Swift Playgrounds中使用Metal着色器的SCNTechnique

7

我希望在Swift Playgrounds的SCNView上使用Metal着色器中的SCNTechnique。因此,我尝试使用以下代码编译我的着色器:

guard let metalDevice = sceneView.device else {
    return
}

if let path = Bundle.main.path(forResource: "distortTechnique", ofType: "metal") {
    do {
        let contents = try String(contentsOf: URL(fileURLWithPath: path))
        do {
            try metalDevice.makeLibrary(source: contents, options: nil)
        } catch { error
            print(error)
        }
    } catch {
        // contents could not be loaded
    }
}

文件已加载,但我遇到以下编译器错误:
Error Domain=MTLLibraryErrorDomain Code=3 "Compilation failed: 

program_source:11:10: fatal error: 'SceneKit/scn_metal' file not found
#include <SceneKit/scn_metal>
         ^
" UserInfo={NSLocalizedDescription=Compilation failed: 

program_source:11:10: fatal error: 'SceneKit/scn_metal' file not found
#include <SceneKit/scn_metal>
         ^
}

看起来好像找不到 »scn_metal« 库。你有什么解决办法吗?在 iOS 应用程序项目中,相同的设置运行得非常好。

这是着色器代码:

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

struct custom_vertex_t
{
    float4 position [[attribute(SCNVertexSemanticPosition)]];
};

constexpr sampler s = sampler(coord::normalized,
                              address::repeat,
                              filter::linear);

struct out_vertex_t
{
    float4 position [[position]];
    float2 uv;
    float time;
    float random;
};

vertex out_vertex_t pass_through_vertex(custom_vertex_t in [[stage_in]],
                                        constant SCNSceneBuffer& scn_frame [[buffer(0)]])
{
    out_vertex_t out;
    out.position = in.position;
    out.uv = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);
    out.time = scn_frame.time;

    return out;
};

fragment half4 pass_through_fragment(out_vertex_t vert [[stage_in]],
                            texture2d<float, access::sample> colorSampler [[texture(0)]])
{

    float4 fragment_color = colorSampler.sample( s, vert.uv);
    return half4(fragment_color);
};

fragment half4 distort_fragment(out_vertex_t vert [[stage_in]],
                                              texture2d<float, access::sample> colorSampler [[texture(0)]])
{
    float multiplier = sin(vert.time * 0.5);
    float effectRadius = 0.75;
    float effectAmount = 360. * 2.;
    float effectAngle = multiplier * effectAmount;
    effectAngle = effectAngle * M_PI_F / 180;

    float2 resolution = float2(colorSampler.get_width(), colorSampler.get_height());
    float2 center = float2(0.5, 0.5);//iMouse.xy / iResolution.xy;

    float2 uv = vert.position.xy / resolution - center;
    float len = length(uv * float2(resolution.x / resolution.y, 1.));
    float angle = atan2(uv.y, uv.x) + effectAngle * smoothstep(effectRadius, 0., len);
    float radius = length(uv);

    float4 fragment_color = colorSampler.sample(s, float2(radius * cos(angle), radius * sin(angle)) + center);

    return half4(fragment_color);

};

这是操场的图片。

谢谢!

在此输入图像描述

2个回答

5
我能够通过在播放器中包含SCNMetalDefines的内容来使金属着色器作为SCNProgram工作。我不确定scn_metal是否比这些定义更多。尽管如此,这里有我需要的一切顶点/片段管道。你能发布你的着色器代码吗?或者至少告诉我们在scn_metal中着色器引用了什么?
#include <metal_stdlib>
using namespace metal;

#ifndef __SCNMetalDefines__
#define __SCNMetalDefines__

enum {
SCNVertexSemanticPosition,
SCNVertexSemanticNormal,
SCNVertexSemanticTangent,
SCNVertexSemanticColor,
SCNVertexSemanticBoneIndices,
SCNVertexSemanticBoneWeights,
SCNVertexSemanticTexcoord0,
SCNVertexSemanticTexcoord1,
SCNVertexSemanticTexcoord2,
SCNVertexSemanticTexcoord3
};

// This structure hold all the informations that are constant through a render pass
// In a shader modifier, it is given both in vertex and fragment stage through an argument named "scn_frame".
struct SCNSceneBuffer {
float4x4    viewTransform;
float4x4    inverseViewTransform; // transform from view space to world space
float4x4    projectionTransform;
float4x4    viewProjectionTransform;
float4x4    viewToCubeTransform; // transform from view space to cube texture space (canonical Y Up space)
float4      ambientLightingColor;
float4        fogColor;
float3        fogParameters; // x:-1/(end-start) y:1-start*x z:exp
float2      inverseResolution;
float       time;
float       sinTime;
float       cosTime;
float       random01;
};

// In custom shaders or in shader modifiers, you also have access to node relative information.
// This is done using an argument named "scn_node", which must be a struct with only the necessary fields
// among the following list:
//
// float4x4 modelTransform;
// float4x4 inverseModelTransform;
// float4x4 modelViewTransform;
// float4x4 inverseModelViewTransform;
// float4x4 normalTransform; // This is the inverseTransposeModelViewTransform, need for normal transformation
// float4x4 modelViewProjectionTransform;
// float4x4 inverseModelViewProjectionTransform;
// float2x3 boundingBox;
// float2x3 worldBoundingBox;

#endif /* defined(__SCNMetalDefines__) */

谢谢你,Oliver!我找到了这个文件:https://github.com/theos/sdks/blob/master/iPhoneOS11.2.sdk/System/Library/Frameworks/SceneKit.framework/Headers/scn_metal,并添加了SCNMetalDefines。现在正在编译,但仍然无法工作。场景现在仍然是粉色的。 - arthurschiller
你能发一下剩余的游乐场吗?我认为关于游乐场比较棘手的问题之一是与完整项目相比,调试有限。您不能使用GPU帧捕获等选项。 - OliverD
当然!这是链接:https://www.dropbox.com/s/gpe7jzs882scl3b/SCNTechniqueMetalMacTest.playground.zip?dl=0 - arthurschiller
我必须补充说明最终目标是在iPad上使用整个东西,但我创建了Mac Playground以便更轻松地进行调试。 - arthurschiller

4

我最终通过预编译实际应用程序项目的金属库,然后使用结果默认的.default.metallib和游乐场中包含的着色器函数使其正常工作。

这似乎是必需的,因为SCNTechnique文档对于顶点和片段的函数指出:“这些函数必须存在于应用程序的默认Metal库中。”

有点投机取巧,但对于我的目的而言有效。

具体步骤: - 为所选平台创建一个新的应用程序项目 - 使用所有定义设置SCNTechnique plist - 添加包含着色器代码的.metal文件 - 归档项目 - 打开归档束并将default.metallib文件复制到您的游乐场资源文件夹中 - 现在只需设置SCNView上的技术即可 - 获得收益。

再次感谢Oliver的帮助!


1
很高兴你解决了它。遗憾的是,你不能在 playground 中编辑金属着色器。正如你所说,这一定是 SCNTechnique 的要求。SCNProgram 通过从字符串编译着色器可以很好地运行。关于你的代码有一个小建议:你不需要嵌套 do/catch 块。你可以在一个 do/catch 块中拥有多个 try 调用。第一个失败的将中断 do 块并调用适当的错误处理程序。 - OliverD
是的,那绝对是个遗憾。也许有一些方法可以在运行时注入代码到默认库中。等我有更多时间的时候会再试一次。感谢你提醒关于 do/catch 块的问题。我在摸索时也注意到了。 - arthurschiller

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接