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 <MTLRenderCommandEncoder> 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];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容