11

I'm looking for just a working Metal shader that works in SceneKit with SCNProgram.

Can someone show me the correct method declarations/how to hook this up?

let program = SCNProgram()
program.vertexFunctionName = "myVertex"
program.fragmentFunctionName = "myFragment"
material.program = program

and then the shader

//MyShader.metal

vertex something myVertex(something)
{
    return something;
}

fragment float4 myFragment(something)
{
    return something
}

I'm just looking for the most basic example please.

Morty
  • 1,436
  • 14
  • 12
Chris
  • 2,739
  • 4
  • 29
  • 57

2 Answers2

15

I clipped out all the 'unnecessary' stuff, this is about as basic as it gets and pretty much what my first Metal shader was.

Next I'd start looking into wiring up the other vertex attributes (colour, normals), and maybe do some basic lighting calculations.

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

struct MyNodeBuffer {
    float4x4 modelTransform;
    float4x4 modelViewTransform;
    float4x4 normalTransform;
    float4x4 modelViewProjectionTransform;
};

typedef struct {
    float3 position [[ attribute(SCNVertexSemanticPosition) ]];
} MyVertexInput;

struct SimpleVertex
{
    float4 position [[position]];
};


vertex SimpleVertex myVertex(MyVertexInput in [[ stage_in ]],
                             constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                             constant MyNodeBuffer& scn_node [[buffer(1)]])
{
    SimpleVertex vert;
    vert.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);

    return vert;
}

fragment half4 myFragment(SimpleVertex in [[stage_in]])
{
    half4 color;
    color = half4(1.0 ,0.0 ,0.0, 1.0);

    return color;
}

Apologies for any typos, edited it down on my phone...

lock
  • 2,861
  • 1
  • 13
  • 20
  • This is really great, do you have an example of passing through the texture that the SCNNode's material is set to? - Understandably at the moment, it just paints my object red. – Chris Jun 09 '16 at 20:29
15

@lock's answer above is great, so I wanted to expand on it by providing an example of texturing, as OP requested in the comments.

Here's how you'd configure your material to make use of the shaders and wire up the custom texture:

let program = SCNProgram()
program.fragmentFunctionName = "myFragment"
program.vertexFunctionName = "myVertex"

material.program = program

let image = UIImage(named: "diffuse")!
let imageProperty = SCNMaterialProperty(contents: image)
// The name you supply here should match the texture parameter name in the fragment shader
material.setValue(imageProperty, forKey: "diffuseTexture")

and here are the modified shaders that sample from the texture:

#include <metal_stdlib>

using namespace metal;

#include <SceneKit/scn_metal>

struct MyNodeBuffer {
    float4x4 modelTransform;
    float4x4 modelViewTransform;
    float4x4 normalTransform;
    float4x4 modelViewProjectionTransform;
};

typedef struct {
    float3 position [[ attribute(SCNVertexSemanticPosition) ]];
    float2 texCoords [[ attribute(SCNVertexSemanticTexcoord0) ]];
} MyVertexInput;

struct SimpleVertex
{
    float4 position [[position]];
    float2 texCoords;
};

vertex SimpleVertex myVertex(MyVertexInput in [[ stage_in ]],
                             constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                             constant MyNodeBuffer& scn_node [[buffer(1)]])
{
    SimpleVertex vert;
    vert.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
    vert.texCoords = in.texCoords;

    return vert;
}

fragment half4 myFragment(SimpleVertex in [[stage_in]],
                          texture2d<float, access::sample> diffuseTexture [[texture(0)]])
{
    constexpr sampler sampler2d(coord::normalized, filter::linear, address::repeat);
    float4 color = diffuseTexture.sample(sampler2d, in.texCoords);
    return half4(color);
}
warrenm
  • 31,094
  • 6
  • 92
  • 116
  • Thank you Thank you Thank you! I would totally tip you for this. I've already brought your book Warren, this is exactly what I need! – Chris Jun 10 '16 at 20:04
  • Haha, happy to help. And thanks for supporting the book :) – warrenm Jun 10 '16 at 21:18
  • Could this setup be used for some sort of multi-shader setup, wherein the first shader creates a texture that the second shader uses, and the second shader is the one that renders? – Chris Jun 10 '16 at 22:30
  • Thanks @warrenm, that's pretty much what I would have typed up... eventually. – lock Jun 11 '16 at 08:35
  • 1
    @Chris yes, the functionality you refer to is packaged up in the [`SCNTechnique`](https://developer.apple.com/library/ios/documentation/SceneKit/Reference/SCNTechnique_Class/) class. You may be able to rely on the default SceneKit shader, but will probably end up writing your own. – lock Jun 11 '16 at 08:43
  • Thanks @lock - I've looked at SCNTechnique and it seems difficult to use, I wonder if I can just use a MTLDevice/MTLFunction directly to change my textures, it would be a lot easier... – Chris Jun 12 '16 at 19:56
  • 1
    @Chris sure can. Set an objects material to contain a 'MTLTexture' then modify via a compute shader. Works well, performance is good (no CPU round trip), and is easier than sorting out 'SCNTechnique'. Although 'SCNTechnique' is the way to go for screen space effects (blur, bloom, etc). – lock Jun 13 '16 at 01:40
  • @Chris there's some 'SCNTechnique' example code in my [GitHub repo](https://github.com/lachlanhurst/SCNTechniqueTest). Example in the master branch is a simple blur, the bloom branch isn't quite as self explanatory but demonstrates a more advanced multi pass renderer. – lock Jun 13 '16 at 01:44
  • @lock Thank you for this! I think I'll give both versions a try! – Chris Jun 13 '16 at 12:28
  • @warrenm: dummy question, but how do you reference the metal file from the code snippet where you set the program? – Guig Sep 16 '16 at 04:18
  • Right, contrary to glsl shaders, metal code is compiled and functions are available in the global space – Guig Sep 16 '16 at 06:35
  • 1
    As nice as this looks in theory, all I get is a plain white silhouette with no shading or texture. Any idea what I'm doing wrong? Something changed in recent iOS versions maybe? – Ash May 21 '18 at 14:38