本文主要通过自己对 Metal By Example 理解编写,这一篇文章讲解 Metal 中实例渲染和渲染文本。

Metal By Example Cover

实例渲染

绘制地形

通过中点位移(Midpoint Displacement)算法来绘制地形,它是一种近似的分形布朗运动生成算法,它是利用细分过程中在两点和或多个点之间进行插值的方法来进行地形建模的。

Terrain generation with the diamond square algorithm

绘制奶牛

每一个奶牛的朝向和位置是不同的,所以这里需要针对每一个奶牛传入不同的 Buffer,也就是下面的 self.cowUniformBuffer:

[commandEncoder setVertexBuffer:self.cowMesh.vertexBuffer offset:0 atIndex:0];
[commandEncoder setVertexBuffer:self.sharedUniformBuffer offset:0 atIndex:1];
[commandEncoder setVertexBuffer:self.cowUniformBuffer offset:0 atIndex:2];

将 modelMatrix 从 modelViewProjectionMatrix 中分离出来,才可以针对每一个奶牛进行设置:

typedef struct
{
    matrix_float4x4 viewProjectionMatrix;
} Uniforms;

typedef struct
{
    matrix_float4x4 modelMatrix;
    matrix_float3x3 normalMatrix;
} PerInstanceUniforms;

需要将每一个奶牛的 PerInstanceUniforms 存储起来:

_cowUniformBuffer = [_device newBufferWithLength:sizeof(PerInstanceUniforms) * MBECowCount
                                         options:MTLResourceOptionCPUCacheModeDefault];

计算每一个奶牛的 PerInstanceUniforms:

PerInstanceUniforms uniforms;
uniforms.modelMatrix = matrix_multiply(translation, rotation);
uniforms.normalMatrix = matrix_upper_left3x3(uniforms.modelMatrix);
memcpy([self.cowUniformBuffer contents] + sizeof(PerInstanceUniforms) * i, &uniforms, sizeof(PerInstanceUniforms));

一次性绘制所有奶牛,注意这里通过传入 instanceCount 表明有多少个实例:

[commandEncoder drawIndexedPrimitives:MTLPrimitiveTypeTriangle
                           indexCount:[self.cowMesh.indexBuffer length] / sizeof(MBEIndex)
                            indexType:MTLIndexTypeUInt16
                          indexBuffer:self.cowMesh.indexBuffer
                    indexBufferOffset:0
                        instanceCount:MBECowCount];

Vertex Shader 函数中 ushort iid [[instance_id]] 表明当前被绘制的实例的下标:

vertex ProjectedVertex vertex_project(InVertex vertexIn [[stage_in]],
                                      constant Uniforms &uniforms [[buffer(1)]],
                                      constant PerInstanceUniforms *perInstanceUniforms [[buffer(2)]],
                                      ushort vid [[vertex_id]],
                                      ushort iid [[instance_id]])
{
    float4x4 instanceModelMatrix = perInstanceUniforms[iid].modelMatrix;
    float3x3 instanceNormalMatrix = perInstanceUniforms[iid].normalMatrix;

示例代码:

danjiang / mbe-sample-code / InstancedDrawing

渲染文本

动态栅格化(Dynamic Rasterization)

将字符串在 CPU 上绘制成位图,再将位图传给 GPU 绘制,字符串有任何变化就需要重新绘制位图,CPU 的压力很大,放大已绘制在屏幕上的字符串就会看起来模糊。

字体地图册(Font Atlases)

将所有可能出现的字符先绘制成一张图,通过坐标可以找到确定的字符,在 GPU 绘制字符时,一个字符可由两个组合三角形(也就是长方形)设定其网格,再将字体地图册做为纹理图像贴到长方形上。

Signed Distance Fields

这个算法可以优化采用字体地图册绘制字符,其原理可参考文章:Drawing Text with Signed Distance Fields in Mapbox GL

正交投射(The Orthographic Projection)

绘制文字在屏幕上,采用正交投射的方式,可以理解会从正面俯视一本书的视角:

matrix_float4x4 projectionMatrix = matrix_orthographic_projection(0, drawableSize.width, 0, drawableSize.height);
uniforms.viewProjectionMatrix = projectionMatrix;

示例代码:

danjiang / mbe-sample-code / TextRendering