Как предоставить пользовательские данные для SCNProgram? - PullRequest
0 голосов
/ 03 декабря 2018

У меня есть массив с 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:), но я не уверен, что вершинная функция получит правильный элемент длявершина.

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

1 Ответ

0 голосов
/ 04 декабря 2018

Вы на правильном пути, но вы не хотите использовать SCNVector3 для данных, которые вы передаете в металлический шейдер.Векторные типы 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];
    // ...
}

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

...