Размытие в движении с помощью SCNTechnique
Я пытаюсь заставить Motion Blur работать со SCNTechnique уже несколько дней, и я не близок к тому, чего хочу. Я задал похожий вопрос на форумах Apple, но они мертвы. Поэтому я решил написать более исчерпывающее описание того, чего я пытаюсь достичь с помощью кода.
Настройка
У меня есть персонаж на экране с некоторыми врагами. Персонаж - квадрат, враги - круги, упрощенно для этого примера.
Я использую SceneKit с металлом. Камера исправлена.
Цель
Когда круги / враги двигаются, я хочу, чтобы у них было размытие движения. Когда персонаж движется, он не должен.
Предлагаемая идея
Напишите многопроходный SCNTechnique, который справится с этим, я предполагаю, что это способ делать то, что я хочу.
Пройдите один: визуализируйте только врагов / круги в другой буфер
Пройдите два: примените Motion Blur к этому буферу
Проход третий: визуализация буфера размытия в движении с исходной сценой.
Примечание: Проход второй будет сложным, так как я представляю, что у каждого объекта есть свое направление, и размытие должно последовать его примеру.
Итак, вот как я настраиваю объекты в SceneKit
class Character : SCNNode
{
override init() {
super.init()
let img = UIImage(named: "texture1")!
material = SCNMaterial()
material.diffuse.contents = img
material.ambient.contents = img
let geometry = SCNSphere(radius: 40)
geometry.materials = [material]
self.categoryBitMask = 1
}
}
class Enemy : SCNNode
{
override init() {
super.init()
let img = UIImage(named: "texture2")!
material = SCNMaterial()
material.diffuse.contents = img
material.ambient.contents = img
let geometry = SCNSphere(radius: 40)
geometry.materials = [material]
self.categoryBitMask = 2
}
}
Я могу добавить их на сцену, и они выглядят хорошо. Я могу переместить их обоих с помощью SCNActions, и они будут двигаться правильно.
Теперь о том, как я пытаюсь размыть изображение
Первый проход
Я хочу выделить только врагов для этого прохода, чтобы эта часть техники выглядела следующим образом, обратите внимание, что маска категории рисует только врагов для сцены.
Это выводит это в мой пользовательский целевой буфер "врагиКолора". Примечание: DRAW_SCENE
<key>drawEnemies</key>
<dict>
<key>draw</key>
<string>DRAW_SCENE</string>
<key>includeCategoryMask</key>
<integer>2</integer>
<key>excludeCategoryMask</key>
<integer>1</integer>
<key>program</key>
<string>doesntexist</string>
<key>metalVertexShader</key>
<string>multi_vertex</string>
<key>metalFragmentShader</key>
<string>multi_fragment_vert</string>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
<key>aPos</key>
<string>vertexSymbol</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>enemiesColor</string>
</dict>
</dict>
Металлический шейдер для этого:
#include <metal_stdlib>
using namespace metal;
#include <SceneKit/scn_metal>
struct custom_node_t3 {
float4x4 modelTransform;
float4x4 modelViewTransform;
float4x4 normalTransform;
float4x4 modelViewProjectionTransform;
};
struct custom_vertex_t
{
float4 position [[attribute(SCNVertexSemanticPosition)]];
float2 a_texcoord [[ attribute(SCNVertexSemanticTexcoord0) ]];
//SCNGeometrySourceSemanticTexcoord
};
constexpr sampler s = sampler(coord::normalized,
address::repeat,
filter::linear);
struct out_vertex_t
{
float4 position [[position]];
float2 texcoord;
};
vertex out_vertex_t multi_vertex(custom_vertex_t in [[stage_in]],
constant custom_node_t3& scn_node [[buffer(0)]])
{
out_vertex_t out;
out.texcoord = in.a_texcoord;
out.position = scn_node.modelViewProjectionTransform * float4(in.position.xyz, 1.0);
return out;
};
fragment half4 multi_fragment_vert(out_vertex_t vert [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
texture2d<float, access::sample> colorSampler [[texture(0)]])
{
float4 FragmentColor = colorSampler.sample( s, vert.texcoord);
return half4(FragmentColor);
};
Второй проход
Я хочу размывать буфер "враги-цвета", на данный момент он очень грубый, поэтому сейчас я использую гауссовский хак.
Я беру в качестве буфера "врагиСолора" в качестве входных данных и размываю их, я выводю это как новый буфер: "врагаКолора" Примечание: DRAW_QUAD
Техника для этого прохода выглядит следующим образом:
<key>blurEnemies</key>
<dict>
<key>draw</key>
<string>DRAW_QUAD</string>
<key>program</key>
<string>doesntexist</string>
<key>metalVertexShader</key>
<string>blur_vertex</string>
<key>metalFragmentShader</key>
<string>blur_fragment_vert</string>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
<key>enemyColor</key>
<string>enemiesColor</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>chrisColor</string>
</dict>
</dict>
и шейдер это:
// http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
constant float offset[] = { 0.0, 1.0, 2.0, 3.0, 4.0 };
constant float weight[] = { 0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162 };
vertex out_vertex_t blur_vertex(custom_vertex_t in [[stage_in]],
constant custom_node_t3& scn_node [[buffer(0)]])
{
out_vertex_t out;
out.position = in.position;
out.texcoord = float2((in.position.x + 1.0) * 0.5 , (in.position.y + 1.0) * -0.5);
return out;
};
fragment half4 blur_fragment_vert(out_vertex_t vert [[stage_in]],
texture2d<float, access::sample> colorSampler [[texture(0)]],
texture2d<float, access::sample> enemyColor [[texture(1)]])
{
float4 enemySample = enemyColor.sample(s, vert.texcoord);
if (enemySample.a == 0)
{
//gl_LastFragData[0]
return half4(0.0 ,1.0 ,0.0, 0.5);
}
float4 FragmentColor = colorSampler.sample( s, vert.texcoord) * weight[0];
for (int i=1; i<5; i++) {
FragmentColor += colorSampler.sample( s, ( vert.texcoord + float2(0.0, offset[i])/224.0 ) ) * weight[i];
FragmentColor += colorSampler.sample( s, ( vert.texcoord - float2(0.0, offset[i])/224.0 ) ) * weight[i];
}
return half4(FragmentColor);
};
Третий проход
Там, где все становится еще более запутанным, я хочу применить размытый буфер врага-цвета к исходной сцене.
Моя первая мысль была, ну почему я не могу просто наложить результат. Я посмотрел в режимах смешивания, но не нашел удачи.
Тогда я подумал, может быть, я смогу просто перерисовать сцену и добавить вместе "врага-цвет" и новый "цветной" буфер (возможно, где-то есть оптимизация, если это даже работает удаленно)
Примечание: DRAW_SCENE
Итак, техника такова:
<key>blendTogether</key>
<dict>
<key>draw</key>
<string>DRAW_SCENE</string>
<key>program</key>
<string>doesntexist</string>
<key>metalVertexShader</key>
<string>plain_vertex</string>
<key>metalFragmentShader</key>
<string>plain_fragment_vert</string>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
<key>aPos</key>
<string>vertexSymbol</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>COLOR</string>
</dict>
</dict>
и шейдер:
vertex out_vertex_t plain_vertex(custom_vertex_t in [[stage_in]],
constant SCNSceneBuffer& scn_frame [[buffer(0)]],
constant custom_node_t3& scn_node [[buffer(1)]])
{
out_vertex_t out;
out.position = scn_node.modelViewProjectionTransform * float4(in.position.xyz, 1.0);
out.texcoord = in.a_texcoord;
return out;
};
fragment half4 plain_fragment_vert(out_vertex_t vert [[stage_in]],
texture2d<float, access::sample> colorSampler [[texture(0)]])
{
float4 FragmentColor = colorSampler.sample( s, vert.texcoord);
return half4(FragmentColor);
};
В конце этого моя сцена рендерится, но я просто не получаю желаемых эффектов.
Первый вопрос был бы... возможно ли размытие в движении с такой системой, я не хочу больше преследовать это, если это не так.
Во-вторых, где я иду не так?
Полная техника для завершения:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>passes</key>
<dict>
<key>drawEnemies</key>
<dict>
<key>draw</key>
<string>DRAW_SCENE</string>
<key>includeCategoryMask</key>
<integer>2</integer>
<key>excludeCategoryMask</key>
<integer>1</integer>
<key>program</key>
<string>doesntexist</string>
<key>metalVertexShader</key>
<string>multi_vertex</string>
<key>metalFragmentShader</key>
<string>multi_fragment_vert</string>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
<key>aPos</key>
<string>vertexSymbol</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>enemiesColor</string>
</dict>
</dict>
<key>blurEnemies</key>
<dict>
<key>draw</key>
<string>DRAW_QUAD</string>
<key>program</key>
<string>doesntexist</string>
<key>metalVertexShader</key>
<string>blur_vertex</string>
<key>metalFragmentShader</key>
<string>blur_fragment_vert</string>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
<key>enemyColor</key>
<string>enemiesColor</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>chrisColor</string>
</dict>
</dict>
<key>blendTogether</key>
<dict>
<key>draw</key>
<string>DRAW_SCENE</string>
<key>program</key>
<string>doesntexist</string>
<key>metalVertexShader</key>
<string>plain_vertex</string>
<key>metalFragmentShader</key>
<string>plain_fragment_vert</string>
<key>inputs</key>
<dict>
<key>colorSampler</key>
<string>COLOR</string>
<key>aPos</key>
<string>vertexSymbol</string>
<key>a_texcoord</key>
<string>a_texcoord-symbol</string>
</dict>
<key>outputs</key>
<dict>
<key>color</key>
<string>COLOR</string>
</dict>
</dict>
</dict>
<key>sequence</key>
<array>
<string>blendTogether</string>
</array>
<key>targets</key>
<dict>
<key>enemiesColor</key>
<dict>
<key>type</key>
<string>color</string>
</dict>
<key>chrisColor</key>
<dict>
<key>type</key>
<string>color</string>
</dict>
</dict>
<key>symbols</key>
<dict>
<key>a_texcoord-symbol</key>
<dict>
<key>semantic</key>
<string>texcoord</string>
</dict>
<key>vertexSymbol</key>
<dict>
<key>semantic</key>
<string>vertex</string>
</dict>
</dict>
</dict>
</plist>
2 ответа
В этом ответе предполагается, что ваши объекты являются двумерными объектами (что подразумевается под "кругами" и "квадратами" в вашем описании), встроенными в трехмерный мир SceneKit.
Я думаю, что многопроходный рендеринг с SCNTechnique, вероятно, не является правильным решением для того, чего вы пытаетесь достичь.
Вот как я бы подошел к этому:
Добавьте (довольно широкое) прозрачное поле к вашей текстуре для объектов круга и увеличьте размер объектов круга SceneKit, чтобы непрозрачная часть имела правильный размер. Ширина прозрачного поля - это максимальная длина следов размытия движения, которые могут иметь объекты. Так что если круг врагов может продвинуться довольно далеко за один кадр, вам понадобится широкий запас.
Используйте пользовательскую SCNProgram, чтобы указать фрагментный шейдер для ваших объектов круга. Это где вы могли бы реализовать рендеринг размытия движения. Вам нужно будет передать скорость объекта в шейдер как пользовательскую переменную (см. Раздел "Пользовательские переменные" документации SCNProgram). Также вам нужно будет преобразовать / преобразовать вектор скорости в 2D-систему координат текстуры объекта круга.
Затем в фрагментном шейдере вы производите выборку текстуры по вектору скорости и усредняете выборочные цвета. Возможно, вы захотите выбрать количество выборок в зависимости от величины скорости: чем быстрее движется объект, тем больше выборок вы захотите использовать. Фиксированное количество сэмплов тоже может быть хорошим, если оно достаточно высокое.
На иллюстрации выше маленький зеленый квадрат показывает пример пикселя, для которого оценивается фрагментный шейдер. А 4 желтые точки показывают примерные положения, в которых вы можете оценить текстуру. В этом случае 2 образца попадают на прозрачное поле в текстуре, а 2 других попадают в непрозрачную часть. Таким образом, выходной цвет будет иметь альфа 0,5 в этом случае.
Вы также можете поэкспериментировать с весами сэмплов и, возможно, использовать больший вес для текущей позиции (сэмпла, который находится внутри пикселя) в зависимости от того, какой вы ищете.
Если вы можете подождать до MacOS Sierra/iOS 10, новая камера SceneKit HDI поддерживает размытие в движении.
Видео: https://developer.apple.com/videos/play/wwdc2016/609/
Источник; https://developer.apple.com/library/prerelease/content/samplecode/Badger/Introduction/Intro.html