Apple Metal 2 7.反射和层分割

原文https://developer.apple.com/documentation/metal/advanced_techniques/reflections_with_layer_selection

Reflections with Layer Selection

Demonstrates how to use layer selection to reduce the number of render passes needed to render a reflective object.

Overview

You can implement an object that reflects its environment by sampling its reflections from a cubemap of the environment. A cubemap is a single texture composed of six 2D texture layers arranged in the shape of a cube. The reflections vary based on the position of other objects in the environment, so each of the cubemap's six faces must be rendered dynamically every frame. This would normally require six separate render passes, one for each face, but Metal allows you to render an entire cubemap in a single pass.

This sample demonstrates dynamic reflections on a chrome sphere, using layer selection to render the frame in two passes. The first pass renders the environment onto the cubemap; the second pass renders the environment reflections onto the sphere, as well as additional actors in the scene and the environment itself.

Splitting up the Scene

A cubemap is represented as a render target array with six layers, one for each of its faces. The [[render_target_array_index]] attribute qualifier, specified for a structure member of a vertex function return value, identifies each array layer separately. This layer selection feature allows you to decide which part of the environment gets rendered to which cubemap face.

An AAPLActorData object represents an actor in the scene. In this sample, each actor is a temple model with the same mesh data but a different diffuse color. These actors sit on the XZ-plane; they are always reflected in the X or Z direction relative to the sphere and could be rendered to any of the +X, -X, +Z, or -Z faces of the cubemap.

Performing Culling Tests for the Reflection Pass

Before rendering to the cubemap, it's useful to know which faces each actor should be rendered to. This procedure is known as a culling test and it's performed for each actor for each cubemap face.

At the start of every frame, for each cubemap face, a view matrix is calculated and the view's frustum is stored in the culler_probe array.

// 1) Get the view matrix for the face given the sphere's updated position
viewMatrix[i] = _cameraReflection.GetViewMatrixForFace_LH (i);

// 2) Calculate the planes bounding the frustum using this updated viewMatrix.
//    We use these planes later to test whether an actor's bounding sphere
//    intersects with the frustum, and is therefore visible in this face's viewport.
culler_probe[i].Reset_LH (viewMatrix [i], _cameraReflection);

These culler probes are used to test the intersection between an actor and the viewing frustum of each cubemap face. The test results determine how many faces the actor is rendered to (instanceCount) and which faces the actor is rendered to (instanceParams) in the reflection pass.

if (_actorData[actorIdx].passFlags & EPassFlags::Reflection)
{
    int instanceCount = 0;
    for (int faceIdx = 0; faceIdx < 6; faceIdx++)
    {
        // if the actor is visible for the current probe face
        if (culler_probe [faceIdx].Intersects (_actorData[actorIdx].modelPosition.xyz, _actorData[actorIdx].bSphere))
        {
            // Add this face index to the the list of faces for this actor
            InstanceParams instanceParams = {(ushort)faceIdx};
            instanceParams_reflection [MaxVisibleFaces * actorIdx + instanceCount].viewportIndex = instanceParams.viewportIndex;
            instanceCount++;
        }
    }
    _actorData[actorIdx].instanceCountInReflection = instanceCount;
}

The following diagram shows the test results of the temple actors based on their position relative to the reflective sphere. Because _actorData[0] and actorData[1] bisect two viewing frustums, their instanceCount property is set to 2 and there are 2 elements in their instanceParams array (the instanceParams array contains the cubemap face indices of the viewing frustums that the actors intersect).

Configuring Render Targets for the Reflection Pass

The render target for the reflection pass is a cubemap. This is configured by using a MTLRenderPassDescriptor object with a color render target, a depth render target, and six layers. The renderTargetArrayLength property sets the number of cubemap faces and allows the render pipeline to render into any or all of these faces.

reflectionPassDesc.colorAttachments[0].texture    = _reflectionCubeMap;
reflectionPassDesc.depthAttachment.texture        = _reflectionCubeMapDepth;
reflectionPassDesc.renderTargetArrayLength        = 6;

Issuing Draw Calls for the Reflection Pass

The drawActors:pass: method sets up the graphics rendering state for each actor. Actors are only drawn if they are visible in any of the six cubemap faces, determined by the visibleVpCount value (accessed through the instanceCountInReflection property). The value of visibleVpCount determines the number of instances for the instanced draw call.

[renderEncoder drawIndexedPrimitives: metalKitSubmesh.primitiveType
                          indexCount: metalKitSubmesh.indexCount
                           indexType: metalKitSubmesh.indexType
                         indexBuffer: metalKitSubmesh.indexBuffer.buffer
                   indexBufferOffset: metalKitSubmesh.indexBuffer.offset
                       instanceCount: visibleVpCount
                          baseVertex: 0
                        baseInstance: actorIdx * MaxVisibleFaces];

In this draw call, the sample sets the baseInstance parameter to the value of actorIdx * 5. This is important so the vertex function knows how to select the appropriate render target layer for each instance.

Rendering the Reflection Pass

In the vertexTransform vertex function, the instanceParams argument points to the buffer that contains the cubemap faces that each actor should be rendered to. The instanceId value is used to index into the instanceParams array.

vertex ColorInOut vertexTransform (         Vertex          in               [[ stage_in ]],
                                            uint            instanceId       [[ instance_id ]],
                                   device   InstanceParams* instanceParams   [[ buffer (BufferIndexInstanceParams) ]],
                                   device   ActorParams&    actorParams      [[ buffer (BufferIndexActorParams)    ]],
                                   constant ViewportParams* viewportParams   [[ buffer (BufferIndexViewportParams) ]] )

The output structure of the vertex function, ColorInOut, contains the face member which uses the [[render_target_array_index]] attribute qualifier. The return value of face determines the cubemap face that the render pipeline should render to.

typedef struct
{
    float4 position [[position]];
    float2 texCoord;

    half3  worldPos;
    half3  tangent;
    half3  bitangent;
    half3  normal;
    uint   face [[render_target_array_index]];
} ColorInOut;

Since the value of the draw call's baseInstance parameter was set to actorIdx * 5, the instanceId value of the first instance drawn in the draw call is equal to this value. Each subsequent rendering of an instance increments the instanceId value by 1. The layout of the instanceParams array is such that each actor has 5 slots in the array since there is a maximum of 5 cubemap faces in which an actor could be visible. The use of the baseInstance parameter and the layout of the instanceParams array means that the instanceParams[instanceId] element will always contain one of the face indices in which the actor is visible. Therefore, this value can be used to select a valid render target layer.

out.face = instanceParams[instanceId].viewportIndex;

In summary, to render each actor to the reflective cubemap, the sample issues an instanced draw a call for the actor. The vertex function uses the built-in instanceId variable to index into the instanceParams array which contains the index of the cubemap face to which the instance should be rendered to. Therefore, the vertex function sets this face index in the face return value member which uses the [[render_target_array_index]] attribute qualifier. This ensures that each actor is rendered to each cubemap face it should appear in.

Performing Culling Tests for the Final Pass

The sample performs similar view updates for the main camera in the final pass. At the start of every frame, a view matrix is calculated and the view's frustum is stored in the culler_final variable.

_cameraFinal.target   = SceneCenter;

_cameraFinal.rotation = fmod ((_cameraFinal.rotation + CameraRotationSpeed), M_PI*2.f);
matrix_float3x3 rotationMatrix = matrix3x3_rotation (_cameraFinal.rotation,  CameraRotationAxis);

_cameraFinal.position = SceneCenter;
_cameraFinal.position += matrix_multiply (rotationMatrix, CameraDistanceFromCenter);

const matrix_float4x4 viewMatrix       = _cameraFinal.GetViewMatrix();
const matrix_float4x4 projectionMatrix = _cameraFinal.GetProjectionMatrix_LH();

culler_final.Reset_LH (viewMatrix, _cameraFinal);

ViewportParams *viewportBuffer = (ViewportParams *)_viewportsParamsBuffers_final[_uniformBufferIndex].contents;
viewportBuffer[0].cameraPos            = _cameraFinal.position;
viewportBuffer[0].viewProjectionMatrix = matrix_multiply (projectionMatrix, viewMatrix);

This final probe is used to test the intersection between an actor and the viewing frustum of the camera. The test result simply determines whether or not each actor is visible in the final pass.

if (culler_final.Intersects (_actorData[actorIdx].modelPosition.xyz, _actorData[actorIdx].bSphere))
{
    _actorData[actorIdx].visibleInFinal = YES;
}
else
{
    _actorData[actorIdx].visibleInFinal = NO;
}

Configuring Render Targets for the Final Pass

The render target for the final pass is the view's drawable, a displayable resource obtained by accessing the view's currentRenderPassDescriptor property. However, you must not access this property prematurely because it implicitly retrieves a drawable. Drawables are expensive system resources created and maintained by the Core Animation framework; you should always hold a drawable as briefly as possible to avoid resource stalls. In this sample, a drawable is acquired just before encoding the final render pass.

MTLRenderPassDescriptor* finalPassDescriptor = view.currentRenderPassDescriptor;

finalPassDescriptor.renderTargetArrayLength = 1;
id  renderEncoder =
    [commandBuffer renderCommandEncoderWithDescriptor:finalPassDescriptor];
renderEncoder.label = @"FinalPass";

[self drawActors: renderEncoder pass: EPassFlags::Final];

[renderEncoder endEncoding];

Issuing Draw Calls for the Final Pass

The drawActors:pass: method sets up the graphics rendering state for each actor. Actors are only drawn if they are visible to the main camera, determined by the visibleVpCount value (accessed through the visibleInFinal property).

In the final pass, the instanceCount parameter is always set to 1 and the baseInstance parameter is always set to 0 since each actor is only drawn once in the final pass.

Rendering the Final Pass

The final pass renders the final frame directly to the view's drawable, which is then presented onscreen.

// Schedule a present once the framebuffer is complete using the current drawable
[commandBuffer presentDrawable:view.currentDrawable];

你可能感兴趣的:(Apple Metal 2 7.反射和层分割)