Я пытаюсь получить размытие движения, работая с SCNTechnique, в течение нескольких дней, и я нигде не приближается к тому, что я хочу. Я задал аналогичный вопрос на форумах Apple, но они мертвы. Так что я думал, что напишу более исчерпывающее описание того, чего я пытаюсь достичь с помощью кода.Motion Blur с SCNTechnique
Установка
У меня есть символ на экране с некоторыми врагами. Символ - это квадрат, враги - круги - упрощенные для этого примера.
Я использую SceneKit с металлом. Камера зафиксирована.
Цель
Когда круги/враги двигаться, я хочу, чтобы у них размытость. Когда персонаж перемещается , он не должен.
Предлагаемая идея
Написать многопроходной SCNTechnique, который может справиться с этим, я предполагаю, что это способ сделать то, что я хочу.
Pass один: Рендер только враги/круги в другой буфер
Pass два: Нанести размытость в этот буфер
Pass три: Рендер буфер размытия движения с оригиналом место действия.
Примечание стороны: Проходят два будет сложно, как я полагаю, каждый объект имеет собственное направление и размытость должны последовать их примеру.
Итак, вот как я настройка объектов в 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, и они двигаются правильно.
Теперь, как я пытаюсь размытостью
Первого проход
Я хочу, чтобы извлечь только враг это пройти так что эта часть техники выглядит следующим образом, обратите внимание, что маска категории рисует просто враги для сцены.
Он выводит это на свой пользовательский целевой буфер «enemyColor». Примечание: 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);
};
Второй перевал
Я хочу, чтобы размыть буфер "enemiesColor", на данный момент это очень грубо, поэтому я просто использую гауссовский хак на данный момент.
я беру в буфер «enemiesColor» в качестве входного сигнала и размыть его, я вывожу это как новый буфер: «enemyColor» Примечание: 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);
};
Третий проход
Если ситуация становится еще более беспощадной, я хочу, чтобы затем применить размытый буфер "enemyColor" с исходной сценой.
Моя первая мысль была, ну почему я не могу просто наложить результат. Я смотрел в режимы смешивания, но не нашел удачи.
Тогда я подумал, может быть, я могу просто повторно визуализировать сцену и добавить «enemyColor» и новый «цвет» буфер вместе (там, вероятно, оптимизаций где-то, если это даже отдаленно работает)
Примечание: 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>
Поскольку вы говорите о кругах и квадратах, я предполагаю, что ваша сцена в основном 2D (или 2.5D, т. Е. Сложены двумерные слои)? – Hendrik