@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);
}