I want to use a SCNTechnique with Metal shaders in Swift Playgrounds on a SCNView. Therefore I tried compiling my shaders with the following code:
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
}
}
The file is loaded but I get the following compiler error:
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>
^
}
So it seems like the »scn_metal« library can't be found. Any idea how I can solve this? The same setup is running perfectly fine in an iOS App project.
Here is the shader code:
#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);
};
Attached is an image of the playground.
Thanks!
I finally got it working by precompiling the metal library of an actual app project and then use the result default.metallib with the included shader functions in the playground.
This seems necessary as the SCNTechnique documentation for the vertex and frag,ent »These functions must exist in the app’s default Metal library.«
A bit hacky but works for my purpose.
Concrete steps: - create a new app project for the selected platform - setup SCNTechnique plist with all definitions - add .metal file with shader code - archieve the project - open the archieved bundle and copy the default.metallib file into your playground Resources folder - now just set your technique on the SCNView – profit.
Thanks again to Oliver for your help!