У меня есть массив с SCNVector3, по одному на каждую вершину.

var terrainArray = [SCNVector3]()

Мне нужно предоставить эти данные для каждой вершины в моем фрагментном шейдере. Что-то вроде этого:

struct TerrainVertexInput
{
    float3 position [[attribute(SCNVertexSemanticPosition)]];
    float4 color [[attribute(SCNVertexSemanticColor)]];
};

struct TerrainVertexOutput
{
    float4 position [[position]];
    float3 terrain;
    float4 color;
};

vertex TerrainVertexOutput terrainVertex(TerrainVertexInput in [[stage_in]],
                                         constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                                         constant MyNodeBuffer& scn_node [[buffer(1)]],
                                         constant float3 terrain [[buffer(2)]])
{
    TerrainVertexOutput v;

    v.position = scn_node.modelViewProjectionTransform * float4(in.position, 1.0);
    v.terrain = terrain;
    v.color = in.color;

    return v;
}

Насколько я понимаю, мне нужно создать объект Data с данными массива и предоставить его программе с setValue(_:forKey:), но я не уверен, получит ли функция вершины правильный элемент для вершины.

Как это сделать правильно?

0
John Tracid 3 Дек 2018 в 23:35

1 ответ

Лучший ответ

Вы на правильном пути, но не хотите использовать SCNVector3 для данных, которые вы передаете в шейдер Metal. Векторные типы SceneKit имеют компоненты типа CGFloat, размер которых зависит от платформы.

Вместо этого ваши данные должны использовать один из векторных типов simd. В Swift and Metal это означает float3 или float4. Обратите внимание, что float3 фактически занимает 16 байт пространства; в конце есть фиктивный элемент для выравнивания. Если вы хотите плотно упаковать данные, используя ровно 3 числа с плавающей запятой на вершину, вы можете ввести свой буфер в Metal как packed_float3 и записать 3 смежных числа с плавающей запятой в буфер данных для каждой вершины. В Swift нет трехэлементного упакованного векторного типа с плавающей запятой.

Есть много способов скопировать массив SCNVector3 в буфер данных с соответствующей типизацией. Вот один:

// Allocate enough memory to store three floats per vertex, ensuring we free it later
let terrainBuffer = UnsafeMutableBufferPointer<Float>.allocate(capacity: terrainArray.count * 3)
defer {
    terrainBuffer.deallocate()
}

// Copy each element of each vector into the buffer
terrainArray.enumerated().forEach { i, v in
    terrainBuffer[i * 3 + 0] = Float(v.x)
    terrainBuffer[i * 3 + 1] = Float(v.y)
    terrainBuffer[i * 3 + 2] = Float(v.z)
}

// Copy the buffer data into a Data object, as expected by SceneKit
let terrainData = Data(buffer: terrainBuffer)

Затем вы можете использовать setValue(:forKey:) для своей геометрии или материала:

material.setValue(terrainData, forKey: "terrain")

Вместо того, чтобы использовать один float3 в качестве параметра в вашей вершинной функции, вместо этого возьмите указатель на packed_float3 и проиндексируйте его в соответствии с идентификатором вершины:

vertex TerrainVertexOutput terrainVertex(TerrainVertexInput in              [[stage_in]],
                                         constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                                         constant MyNodeBuffer& scn_node    [[buffer(1)]],
                                         constant packed_float3 *terrain    [[buffer(2)]],
                                         uint vid                           [[vertex_id]]) {
    // ...
    v.terrain = terrain[vid];
    // ...
}

Это предполагает точное соответствие между вершинами в вашей геометрии и точками данных ландшафта. Вместо того, чтобы напрямую использовать идентификатор вершины, вы, конечно, можете выполнить любую причудливую индексацию, которую захотите, чтобы найти данные ландшафта для данной вершины.

2
warrenm 4 Дек 2018 в 02:28