2016-06-15 5 views
3

Я пытаюсь получить размытие движения, работая с 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> 
+0

Поскольку вы говорите о кругах и квадратах, я предполагаю, что ваша сцена в основном 2D (или 2.5D, т. Е. Сложены двумерные слои)? – Hendrik

ответ

2

Если вы не можете ждать, пока Macos Sierra/прошивкой 10, новая камера SceneKit HDI поддерживает размытость.

Видео: https://developer.apple.com/videos/play/wwdc2016/609/

Источник; https://developer.apple.com/library/prerelease/content/samplecode/Badger/Introduction/Intro.html

+0

Моя камера исправлена ​​и не двигается, вы знаете, будет ли это работать с моими требованиями? – Chris

+1

SCNCamera motion blur предназначен для движения камеры, движения невидимого объекта. – rickster

+0

@ rickster Движение размытия объекта движется в iOS 11. – Robert

3

Этот ответ предполагает, что ваши объекты представляют собой 2D-объекты (как это подразумевается в «кругах» и «квадратах» в вашем описании), встроенных в 3D-мир SceneKit.

Я думаю, что многопроходное рендеринг с SCNTechnique, вероятно, не является правильным решением для того, чего вы пытаетесь достичь.

Это, как я хотел бы подойти к нему:

Добавить (довольно широкий) прозрачный край для вашей текстуры для объектов круга и сделать SceneKit круг объектов больше, так что непрозрачная часть имеет правильный размер. Ширина прозрачного поля - это максимальная длина следов движения, которые могут иметь объекты. Поэтому, если враги круга могут перемещаться довольно далеко в одном кадре, вам понадобится широкий запас.

Используйте специальную SCNProgram, чтобы указать фрагментарный шейдер для ваших объектов окружности. Здесь вы можете реализовать визуализацию размытия движения. Вам нужно будет передать скорость объекта в шейдер в качестве настраиваемой переменной (см. Раздел «Пользовательские переменные» в SCNProgram documentation). Кроме того, вам нужно будет преобразовать/преобразовать вектор скорости в двухмерную систему координат текстуры объекта окружности.

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

Illustration of fragment shader for motion blur

На рисунке выше небольшой зеленый квадрат показывает пример пикселя, для которого фрагмент шейдера оцениваемого. И 4 желтых точки показывают образцы позиций, на которых вы можете оценить текстуру. В этом случае 2 образца попадают в прозрачную область текстуры, а остальные 2 попадают в непрозрачную часть. Таким образом, выходной цвет будет иметь альфа 0,5 в этом случае.

Вы также можете поэкспериментировать с весами образцов и, возможно, использовать более высокий вес для текущей позиции (образец, который находится внутри пикселя) в зависимости от внешнего вида.