Измените штриховку металлического фрагмента на основе координаты Y положения вершины в мире
Я пытаюсь использовать шейдер металлического фрагмента с SCNTechnique, чтобы изменить цвет фрагмента в зависимости от положения вершины Y в мире.
Мое понимание до сих пор
SCNTechnique можно настроить с последовательностью проходов рендеринга. Проход рендеринга позволяет внедрить вершину и фрагментный шейдер. Эти шейдеры написаны на Metal. Спецификация металла Shading Language описывает то, что входы / выходы поддерживаются для этих двух. Вершинный шейдер вызывается для каждой визуализируемой вершины. Мы можем передать дополнительную информацию из вершинного шейдера во фрагментный шейдер (например, положение в трехмерном пространстве, см. Раздел 5.2 MSLS). Фрагментный шейдер находится ближе всего к пикселю, и его можно назвать кратным по времени для одного "пикселя", если есть несколько треугольников, которые "подходят" для этого пикселя. (Обычно) после затенения фрагмента фрагмент может быть отброшен, если он не прошел проверку глубины или трафарета.
Что я пытался
Это то, что я пытался. (Надеюсь, это проясняет, чего мне не хватает).
struct VertexOut {
float4 position [[position]];
};
vertex VertexOut innerVertexShader(VertexIn in [[stage_in]]) {
VertexOut out;
out.position = in.position;
return out;
};
fragment half4 innerFragmentShader(VertexOut in [[stage_in]],
half4 color [[color(0)]]) {
half4 output;
output = color; // test to see if getting rendered color works
output.g = in.position.y; // test to see if getting y works
return output;
}
На эти шейдеры есть ссылки в словаре SCNTechnique.
[
"passes": [
"innerPass: [
"draw": "DRAW_NODE",
"node": "inner",
"metalVertexShader": "innerVertexShader",
"metalFragmentShader": "innerFragmentShader"
]
],
"sequence": ["innerPass"],
"symbols": [:],
"targets": [:],
]
// ...
let technique = SCNTechnique(dictionary: techniqueDictionary)
Это делает следующее: техника создается правильно и прикрепляется к сцене (потому что она влияет на рендеринг). Но, похоже, не применяется преобразование камеры или преобразование положения узла к вершинам. И вместо этого отображает каждый узел как видимый из (0,0,1) в позиции (0,0,0). Цвета неправильные. Если я удалю шейдеры из SCNTechnique, все будут рендерить, как я ожидал.
Как я могу использовать обычное поведение SceneKit (преобразование камеры и т. Д.) И изменять только вывод цвета в зависимости от положения фрагментов в мире по оси y? Я ожидал, что это должно произойти на уровне фрагментов, используя положение в мире, каким-то образом полученное в вершинном шейдере. Я искал такие вещи, как "Металлический базовый вершинный шейдер", и ничего не нашел. Я видел подобные шейдеры, но я убежден, что могу полагаться на рендеринг SceneKit для таких вещей, как освещение, материалы PBR, трансформации камеры и т. Д. На этом этапе я чувствую, что всякий раз, когда я ищу какую-то тему Metal, я в конечном итоге на тех же сайтах, которые еще не смогли вывести мое понимание на новый уровень. Таким образом, любые новые / дополнительные ресурсы также приветствуются.
Задний план
Последние два месяца я работал над собственным игровым проектом, который использует SceneKit в качестве основного графического фреймворка. Я обратился к шейдерам SCNTechnique и Metal для создания собственных эффектов. Эти последние два, в частности, доставили мне серьезную головную боль, как из-за отсутствия примеров кода / документации / обратной связи во время выполнения. Я подумывал о переходе на Unity/Unreal или даже об отмене этого проекта из-за этого. Но поскольку я упрямый, а также потому, что я действительно не хочу переносить свой Swift-код на C#/C++, я еще не отказался от SceneKit.
1 ответ
Потратив последние пару дней на изучение этой темы, мое понимание вершинного и фрагментного затенения и того, как SceneKit решает эти задачи, значительно улучшилось.
Как отметил @mnuages в комментарии, для этого варианта использования модификаторы шейдера - это путь. Они используют затенение SceneKit по умолчанию (по запросу OP) и позволяют вводить код шейдера.
Дополнительная информация
Чтобы компенсировать некоторые ограничения документации SceneKit, я немного поясню для других людей, изучающих эту тему.
Для получения дополнительной информации о том, как модификаторы шейдера связаны с шейдерами вершин / фрагментов по умолчанию SceneKit, см. Мой ответ на связанный вопрос или шейдеры по умолчанию SceneKit. Вторая ссылка демонстрирует степень логики рендеринга SceneKit, которую вы получаете бесплатно при использовании модификаторов шейдера вместо написания собственного шейдера.
Эта страница помогла мне понять различные этапы векторных преобразований от вершины к фрагменту (пространство модели, мировое пространство, пространство камеры, пространство проекции).
Альтернативный подход (пользовательский шейдер)
Если вы хотите использовать полностью настраиваемый шейдер за один проход, это простой пример. Он передает мировую позицию по оси Y из вершинного шейдера во фрагментный шейдер.
// Shaders.metal file in your Xcode project
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
typedef struct {
float4x4 modelTransform;
float4x4 modelViewTransform;
} commonprofile_node;
struct VertexIn {
float3 position [[attribute(SCNVertexSemanticPosition)]];
};
struct VertexOut {
float4 fragmentPosition [[position]];
float height;
};
vertex VertexOut myVertex(
VertexIn in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant commonprofile_node& scn_node [[buffer(1)]]
) {
VertexOut out;
float4 position = float4(in.position, 1.f);
out.fragmentPosition = scn_frame.viewProjectionTransform * scn_node.modelTransform * position;
// store world position for fragment shading
out.height = (scn_node.modelTransform * position).y;
return out;
}
fragment half4 myFragment(VertexOut in [[stage_in]]) {
return half4(in.height);
}
let dictionary: [String: Any] = [
"passes" : [
"y" : [
"draw" : "DRAW_SCENE",
"inputs" : [:],
"outputs" : [
"color" : "COLOR"
],
"metalVertexShader": "myVertex",
"metalFragmentShader": "myFragment",
]
],
"sequence" : ["y"],
"symbols" : [:]
]
let technique = SCNTechnique(dictionary: dictionary)
scnView.technique = technique
Вы можете комбинировать этот проход рендеринга с другими проходами (см. SCNTechnique).