Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 Graphics & Games

WWDC21 · 30 min · Graphics & Games

Enhance your app with Metal ray tracing

Achieve photorealistic 3D scenes in your apps and games through ray tracing, a core part of the Metal graphics framework and Shading Language. We’ll explore the latest improvements in implementing ray tracing and take you through upgrades to the production rendering process. Discover Metal APIs to help you create more detailed scenes, integrate natively-supported content with motion, and more.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 22 snippets

Specify intersection functions on render pipeline state objectivec · at 4:48 ↗
// Create and attach MTLLinkedFunctions object
NSArray <id <MTLFunction>> *functions = @[ sphere, cone, torus ];

MTLLinkedFunctions *linkedFunctions = [MTLLinkedFunctions linkedFunctions];
linkedFunctions.functions = functions;

pipelineDescriptor.fragmentLinkedFunctions = linkedFunctions;

// Create pipeline
id<MTLRenderPipelineState> rayPipeline;
rayPipeline = [device newRenderPipelineStateWithDescriptor:pipelineDescriptor
                                                     error:&error];
Create intersection function table objectivec · at 5:02 ↗
// Fill out intersection function table descriptor
MTLIntersectionFunctionTableDescriptor *tableDescriptor =
    [MTLIntersectionFunctionTableDescriptor intersectionFunctionTableDescriptor];

tableDescriptor.functionCount = functions.count;

// Create intersection function table
id<MTLIntersectionFunctionTable> table;
table = [rayPipeline newIntersectionFunctionTableWithDescriptor:tableDescriptor
                                                          stage:MTLRenderStageFragment];
Populate intersection function table objectivec · at 5:14 ↗
id<MTLFunctionHandle> handle;

for (NSUInteger i = 0 ; i < functions.count ; i++) {
    // Get a handle to the linked intersection function in the pipeline state
    handle = [rayPipeline functionHandleWithFunction:functions[i]
                                               stage:MTLRenderStageFragment];

    // Insert the function handle into the table
    [table setFunction:handle atIndex:i];
}
Bind resources objectivec · at 5:48 ↗
[renderEncoder setFragmentAccelerationStructure:accelerationStructure atBufferIndex:0];
[renderEncoder setFragmentIntersectionFunctionTable:table atBufferIndex:1];
Intersect from fragment shader objectivec · at 5:57 ↗
[[fragment]]
float4 rayFragmentShader(vertex_output vo [[stage_in]],
                         primitive_acceleration_structure accelerationStructure,
                         intersection_function_table<triangle_data> functionTable,
                         /* ... */)
{
    // generate ray, create intersector...

    intersection = intersector.intersect(ray, accelerationStructure, functionTable);

    // shading...
}
Triangle intersection function objectivec · at 9:32 ↗
[[intersection(triangle, triangle_data)]]
bool alphaTestIntersectionFunction(uint primitiveIndex        [[primitive_id]],
                                   uint geometryIndex         [[geometry_id]],
                                   float2 barycentricCoords   [[barycentric_coord]],
                                   device Material *materials [[buffer(0)]])

{
    texture2d<float> alphaTexture = materials[geometryIndex].alphaTexture;

    float2 UV = interpolateUVs(materials[geometryIndex].UVs,
        primitiveIndex, barycentricCoords);

    float alpha = alphaTexture.sample(sampler, UV).x;

    return alpha > 0.5f;
}
Custom intersection with intersection query objectivec · at 10:36 ↗
intersection_query<instancing, triangle_data> iq(ray, as, params);

// Step 1: start traversing acceleration structure
while (iq.next())
{
    // Step 2: candidate was found. Check type and run custom intersection.
    switch (iq.get_candidate_intersection_type())
    {
	    case intersection_type::triangle:
	    { 
	       bool alphaTestResult = alphaTest(iq.get_candidate_geometry_id(),
	                                iq.get_candidate_primitive_id(),
	                                iq.get_candidate_triangle_barycentric_coord());
    	   // Step 3: commit candidate or ignore
           if (alphaTestResult) 
               iq.commit_triangle_intersection()
  	  }
    }
}
Custom intersection with intersection query 2 objectivec · at 10:39 ↗
switch (iq.get_committed_intersection_type())
{
  // Miss case  
  case intersection_type::none:
  {
      missShading();
      break;
  } 
  
  // Triangle intersection was committed. Query some info and do shading.
  case intersection_type::triangle:
  {
      shadeHitTriangle(iq.get_committed_instance_id(),
                       iq.get_committed_distance(),
                       iq.get_committed_triangle_barycentric_coord());
      break;
  }
}
Specifying user instance IDs objectivec · at 15:30 ↗
// New instance descriptor type

typedef struct {
    uint32_t userID;
    // Members from MTLAccelerationStructureInstanceDescriptor...
} MTLAccelerationStructureUserIDInstanceDescriptor;

// Specify instance descriptor type through acceleration structure descriptor

accelDesc.instanceDescriptorType = MTLAccelerationStructureInstanceDescriptorTypeUserID;
Retrieving user instance IDs 1 objectivec · at 15:47 ↗
// Available in intersection functions

[[intersection(bounding_box, instancing)]]
IntersectionResult sphereInstanceIntersectionFunction(unsigned int userID[[user_instance_id]],
                                                      /** other args **/)
{
    // ...
}
Retrieving user instance IDs 2 objectivec · at 15:58 ↗
// Available from intersection result

intersection_result<instancing> intersection = instanceIntersector.intersect(/* args */);

if (intersection.type != intersection_type::none)
    instanceIndex = intersection.user_instance_id;

// Available from intersection query

intersection_query<instancing> iq(/* args */);

iq.next()

if (iq.get_committed_intersection_type() != intersection_type::none)
    instanceIndex = iq.get_committed_user_instance_id();
Instance transforms objectivec · at 16:36 ↗
// Available in intersection functions

[[intersection(bounding_box, instancing, world_space_data)]]
IntersectionResult intersectionFunction(float4x3 objToWorld [[object_to_world_transform]],
                                        float4x3 worldToObj [[world_to_object_transform]],
                                        /** other args **/)
{
    // ...
}
Instance transforms 2 objectivec · at 16:51 ↗
// Available from intersection result

intersection_result<instancing, world_space_data> result = 
    intersector.intersect(/* args */);

if (result.type != intersection_type::none) {
    output.myObjectToWorldTransform = result.object_to_world_transform;
    output.myWorldToObjectTransform = result.world_to_object_transform;
}
Instance transforms 3 objectivec · at 17:03 ↗
// Available from intersection query

intersection_query<instancing> iq(/* args */);

iq.next()

if(iq.get_committed_intersection_type() != intersection_type::none){
    output.myObjectToWorldTransform = iq.get_committed_object_to_world_transform();
    output.myWorldToObjectTransform = iq.get_committed_world_to_object_transform();
}
Extended limits objectivec · at 19:17 ↗
// Specify through acceleration structure descriptor

accelDesc.usage = MTLAccelerationStructureUsageExtendedLimits;

// Specify intersector tag

intersector<extended_limits> extendedIntersector;
Sampling time objectivec · at 22:30 ↗
// Randomly sample time

float time = random(exposure_start, exposure_end);

result = intersector.intersect(ray, acceleration_structure, time);
Motion instance descriptor objectivec · at 25:54 ↗
descriptor = [MTLInstanceAccelerationStructureDescriptor new];

descriptor.instanceDescriptorType = MTLAccelerationStructureInstanceDescriptorTypeMotion;

// Buffer containing motion instance descriptors
descriptor.instanceDescriptorBuffer = instanceBuffer;
descriptor.instanceCount = instanceCount;

// Buffer containing MTLPackedFloat4x3 transformation matrices
descriptor.motionTransformBuffer = transformsBuffer;
descriptor.motionTransformCount = transformCount;

descriptor.instancedAccelerationStructures = primitiveAccelerationStructures;
Instance motion objectivec · at 26:33 ↗
// Specify intersector tag

kernel void raytracingKernel(acceleration_structure<instancing, instance_motion> as,
                             /* other args */)
{
    intersector<instancing, instance_motion> intersector;

    // ...
}
Primitive motion 1 objectivec · at 27:24 ↗
// Collect keyframe vertex buffers

NSMutableArray<MTLMotionKeyframeData*> *vertexBuffers = [NSMutableArray new];

for (NSUInteger i = 0 ; i < keyframeBuffers.count ; i++) {
    MTLMotionKeyframeData *keyframeData = [MTLMotionKeyframeData data];

    keyframeData.buffer = keyframeBuffers[i];

    [vertexBuffers addObject:keyframeData];
}
Primitive motion 2 objectivec · at 27:39 ↗
// Create motion geometry descriptor

MTLAccelerationStructureMotionTriangleGeometryDescriptor *geometryDescriptor =
    [MTLAccelerationStructureMotionTriangleGeometryDescriptor descriptor];

geometryDescriptor.vertexBuffers = vertexBuffers;
geometryDescriptor.triangleCount = triangleCount;
Primitive motion 3 objectivec · at 27:57 ↗
// Create acceleration structure descriptor

MTLPrimitiveAccelerationStructureDescriptor *primitiveDescriptor =
    [MTLPrimitiveAccelerationStructureDescriptor descriptor];

primitiveDescriptor.geometryDescriptors = @[ geometryDescriptor ];

primitiveDescriptor.motionKeyframeCount = keyframeCount;
Primitive motion 4 objectivec · at 28:10 ↗
// Specify intersector tag

kernel void raytracingKernel(acceleration_structure<primitive_motion> as,
                             /* other args */)
{
    intersector<primitive_motion> intersector;

    // ...
}

Resources