A complete Metal or OpenGL shader program that replaces SceneKit's rendering of a geometry or material.
SDKs
- iOS 8.0+
- macOS 10.8+
- Mac Catalyst 13.0+
- tvOS 9.0+
Framework
- Scene
Kit
Declaration
class SCNProgram : NSObject
Overview
You use an SCNProgram
object to perform custom rendering using shader programs written in the Metal shading language or the OpenGL Shading Language (GLSL). A program object contains a vertex shader and a fragment shader. To use a program object for rendering, assign it to the program
property of a geometry or material.
Use a program object when you want to completely replace SceneKit’s rendering. Your shaders take input from SceneKit and become responsible for all transform, lighting, and shading effects that you want to produce.
Note
If instead you want to simply modify or extend SceneKit’s rendering, use the shader
property of a geometry or material to insert snippets of Metal or GLSL source code into SceneKit’s built-in shader programs. For details on creating and using shader modifiers, see SCNShadable
.
To use a custom shader program in SceneKit, create an SCNProgram
object and optionally specify its delegate
object for handling errors. Next, provide shaders:
To provide precompiled Metal shader functions, set the
vertex
andFunction Name fragment
properties. SceneKit loads the functions from a Metal shader library in your app’s bundle resources.Function Name To provide OpenGL or OpenGL ES shader source code, set the
vertex
andShader fragment
properties.Shader
Finally, assign your program object to the geometries or materials you want rendered using the shader program.
Rendering with Metal shaders requires that the rendering
property of your SCNView
object (or other renderer) be set to SCNRendering
, which in turn requires that your app be running on Metal-capable hardware. If you provide both Metal and OpenGL shaders in the same SCNProgram
object, SceneKit automatically selects the appropriate shaders to use when rendering, falling back to OpenGL or OpenGL ES shaders when Metal is not supported on the current hardware.
Providing Input to a Metal Shader
Metal shaders for use with SceneKit require an #include <Scene
directive to gain access to SceneKit-specific symbols. Use these symbols to access the kinds of data listed below.
Vertex Attributes
To use vertex attributes provided by SCNGeometry
objects in your shader program, declare those attributes in your Metal shader source code using attribute qualifiers (see Attribute Qualifiers to Locate Per-Vertex Inputs in Metal Shading Language Guide) and the constants listed in Table 1. For example, the partial shader below declares an input structure using the vertex position and normal attributes.
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
typedef struct {
float3 position [[ attribute(SCNVertexSemanticPosition) ]];
float3 normal [[ attribute(SCNVertexSemanticNormal) ]];
} MyVertexInput;
SceneKit Vertex Attribute Qualifiers for Metal Shaders
Metal Constant | Definition |
---|---|
SCNVertexSemanticPosition | The vertex position, provided by the geometry source for the |
SCNVertexSemanticNormal | The surface normal vector at the vertex, provided by the geometry source for the |
SCNVertexSemanticTangent | The surface-space tangent vector. SceneKit automatically infers this vector based on texture coordinates. To obtain a bitangent vector, take the cross product of the tangent vector and the surface normal vector, and scale the result by the w component of the tangent vector. |
SCNVertexSemanticColor | The vertex color, provided by the geometry source for the |
SCNVertexSemanticSkinJoints | Skeletal animation index information, provided by the geometry source for the |
SCNVertexSemanticSkinWeights | Skeletal animation weight information, provided by the geometry source for the |
SCNVertexSemanticTexcoord0 | Texture coordinates, provided by the first geometry source for the |
SCNVertexSemanticTexcoord1 | Texture coordinates, provided by the second geometry source for the |
SCNVertexSemanticTexcoord2 | Texture coordinates, provided by the third geometry source for the |
SCNVertexSemanticTexcoord3 | Texture coordinates, provided by the fourth geometry source for the |
Frame-Constant Data
To use information from SceneKit that is constant for all invocations of your shader when rendering a single frame—such as view and projection matrices, fog parameters, and scene time—declare a parameter to your shader function whose type is SCNScene
, with an attribute qualifier binding it to buffer zero. For example, the shader function declaration below uses scene data in its second parameter.
vertex MyVertexOutput myVertex(MyVertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant default_node_t& scn_node [[buffer(1)]])
Your function can then access scene data from the fields in the SCNScene
structure, outlined below.
struct SCNSceneBuffer {
float4x4 viewTransform;
float4x4 inverseViewTransform; // view space to world space
float4x4 projectionTransform;
float4x4 viewProjectionTransform;
float4x4 viewToCubeTransform; // view space to cube texture space (right-handed, y-axis-up)
float4 ambientLightingColor;
float4 fogColor;
float3 fogParameters; // x: -1/(end-start) y: 1-start*x z: exponent
float time; // system time elapsed since first render with this shader
float sinTime; // precalculated sin(time)
float cosTime; // precalculated cos(time)
float random01; // random value between 0.0 and 1.0
};
Per-Node Data
To use information from SceneKit that varies for each object being rendered with a shader—such as model and normal matrices—declare a parameter to your shader function with an attribute qualifier binding it to buffer one. For the type of this parameter, declare your own struct type containing any of the fields in Listing 4.
Available Fields for Per-Node Shader Data
float4x4 modelTransform;
float4x4 inverseModelTransform;
float4x4 modelViewTransform;
float4x4 inverseModelViewTransform;
float4x4 normalTransform; // Inverse transpose of modelViewTransform
float4x4 modelViewProjectionTransform;
float4x4 inverseModelViewProjectionTransform;
float2x3 boundingBox;
float2x3 worldBoundingBox;
For example, the partial shader below declares a struct with model and model-view-projection matrices and uses it in a vertex function.
struct MyNodeBuffer {
float4x4 modelTransform;
float4x4 modelViewProjectionTransform;
};
vertex MyVertexOutput myVertex(MyVertexInput in [[ stage_in ]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant MyNodeBuffer& scn_node [[buffer(1)]])
Custom Variables
To use custom input variables in a Metal shader, first declare those variables as input parameters to your Metal shader functions, using an attribute qualifier to bind to buffer 2 (or higher). Because these variables pass to your Metal shader in a buffer, you typically also define a data structure for your variables, as seen in the partial shader below.
struct MyAccentColors {
float4 primaryColor;
float4 secondaryColor;
};
fragment half4 myFragmentShader(default_io in [[stage_in]],
constant MyAccentColors& colors [[buffer(2)]]) { ... }
There are two options for providing data for your custom variables: manually and at render time.
To make a single change to your custom variable data, use Key-value coding: Call the
set
method on the geometry or material to be rendered with your shader, passing anValue(_: for Key:) NSData
object containing your data structure as the value and the name of the corresponding shader function parameter as the key. Be aware of layout and alignment when encoding an entire structure as anNSData
object—for best results, use data types from the SIMD library (such asvector
and_float4 matrix
), because those types match the layout and alignment of the GPU data types used in a Metal shader._float4x4 You can also animate such a change by calling the
set
method within anValue(_: for Key:) SCNTransaction
animation or by creating aCAAnimation
object whose key is the shader function parameter name.In either case, you can alternatively provide a value for a specific member of a structure by wrapping that value in an
NSValue
object and using the fully qualified name of that member as the key. For example, usecolors
as the key in the example above..primary Color To update custom variable data at render time, call the
handle
method to register a block that SceneKit calls before rendering using your shader program. In the block, SceneKit provides anBinding(of Buffer Named: frequency: handler:) SCNBuffer
object, whoseStream write
you can call to provide a new value for your data structure.Bytes(_: count:)
Providing Input to an OpenGL Shader
Vertex attributes. To use vertex attributes provided by SCNGeometry
objects in your shader program, map SceneKit semantics to the input vertex attribute names declared in the shader. Use the set
method and the constants listed in Geometry Semantic Identifiers.
Coordinate transformations. To use the coordinate transformations defined by the scene’s node hierarchy and point-of-view camera in your shader program, map SceneKit’s transform matrices to the uniform variable names declared in the shader. Use the set
method and the constants listed in Rendering Transform Keys.
Custom uniform variables. To provide values for your own custom uniform variables declared in the shader, choose when and how you want to update these values.
To update a value once, use Key-value coding: Call the
set
method, providing the uniform name from shader source code as the key and an appropriate type of data as the value. To smoothly transition a one-time value change, call theValue(_: for Key:) set
method inside anValue(_: for Key:) SCNTransaction
animation or create an animation object with theinit(key
method, passing the uniform name as the key.Path:) To update a value every time SceneKit renders an object with your shader program, assign binding blocks using the
handle
method of the geometry or material to be rendered with your custom program. Within a binding block you can execute OpenGL commands to bind shader uniforms or set any other state necessary for rendering.Binding(of Symbol: handler:)