我一直试图让运动模糊与SCNTechnique一起工作几天,我就是 无处接近我想要的。我在Apple论坛上问了一个类似的问题,但他们已经死了。 所以我想我会用代码写一个更详尽的描述。
设置
我屏幕上有一些角色有一些敌人。角色是一个正方形,敌人 是圆圈 - 本例简化。
我正在使用SceneKit和Metal。相机是固定的。
目标
当圈子/敌人移动时,我希望它们具有运动模糊效果。当角色移动时 他不应该。
提出的想法
写一个可以处理这个的多通道SCN技术,我假设这是一种做我想做的事情。
传递一个:将敌人/圈子渲染到另一个缓冲区
传递两个:将动态模糊应用于此缓冲区
传递三次:使用原始场景渲染运动模糊缓冲区。
旁注:传递两个将是棘手的,因为我想每个对象都有它自己的方向和 模糊需要效仿。
所以,这是我在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移动它们并且它们正确移动。
现在我正在尝试运动模糊
首次通过
我想为这个传球提取敌人所以这部分技术如下所示,注意类别蒙版只为场景绘制敌人。
它将此输出到我的自定义“enemiesColor”目标缓冲区。 注意: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 :(得分:3)
此答案假设您的对象是嵌入SceneKit 3D世界中的2D对象(如说明中的'圆圈'和'正方形'所暗示的那样)。
我认为使用SCNTechnique的多次传递渲染可能不是您想要实现的目标的正确解决方案。
这就是我接近它的方式:
为圆形对象的纹理添加(相当宽的)透明边距,并使SceneKit圆形对象更大,以使不透明部分具有正确的大小。透明边距的宽度是对象可以具有的运动模糊轨迹的最大长度。因此,如果圆形敌人可以在一帧中移动很远,那么你将需要很大的余量。
使用自定义SCNProgram为圆形对象指定片段着色器。这是您实现运动模糊渲染的地方。 您需要将对象速度作为自定义变量传递到着色器(请参阅Tumblr API的“自定义变量”部分)。 此外,您需要将速度矢量转换/转换为圆形对象的2D纹理坐标系。
在片段着色器中,然后沿着速度矢量对纹理进行采样并对采样的颜色求平均值。您可能希望根据速度的大小选择样本数:对象移动得越快,您想要使用的样本越多。固定数量的样本也可能没问题,只要它足够高。
在上图中,小绿色方块显示了正在评估片段着色器的示例像素。并且4个黄点显示可以评估纹理的样本位置。在这种情况下,2个样本点击纹理中的透明边距,另外2个样本落入不透明部分。因此,在这种情况下,输出颜色的alpha值为0.5。
您还可以尝试使用样本权重,并根据您所使用的外观对当前位置(像素内部的样本)使用更高的权重。
答案 1 :(得分:2)
如果您可以等到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