SKEffectNode - Предел размера размытия CIFilter - Большой черный ящик
Я пытаюсь размыть несколько SKNode
объекты. Я делаю это, имея родителя SKEffectNode
с CIFilter
установлен в @"CIGaussianBlur"
, Вот так:
- (SKEffectNode *)createBlurNode
{
SKEffectNode *blurNode = [[SKEffectNode alloc] init];
blurNode.shouldRasterize = YES;
[blurNode setShouldEnableEffects:NO];
[blurNode setFilter:[CIFilter filterWithName:@"CIGaussianBlur"
keysAndValues:@"inputRadius", @10.0f, nil]];
return blurNode;
}
Это отлично работает для нескольких узлов, находящихся в данный момент на экране. Но когда я размещаю эти заметки далеко друг от друга (около 3000 пикселей), размытие больше не происходит, и я получаю большой черный ящик. Это происходит независимо от того, SKNodes
Я размываю SKShapeNodes
или же SKSpriteNodes
, Вот пример проекта с этой проблемой: Пример проекта. (Кстати, спасибо BobMoff за начальную версию, найденную здесь):
Вот счастливое размытие (когда узлы расположены на расстоянии менее 3000 пикселей друг от друга):
Грустное размытие (когда узлы находятся на расстоянии более 3000 пикселей друг от друга):
ОБНОВИТЬ
Такое поведение происходит всякий раз, когда SKEffectNode
это родитель. Неважно, активирует ли он эффекты, размытие и т. Д. Если родительский узел является SKNode, это нормально. т.е. даже если родительский размытый узел создан, как показано ниже, вы получите черноту:
- (SKEffectNode *)createBlurNode
{
SKEffectNode *blurNode = [[SKEffectNode alloc] init];
// blurNode.shouldRasterize = YES;
// [blurNode setShouldEnableEffects:NO];
// [blurNode setFilter:[CIFilter filterWithName:@"CIGaussianBlur"
// keysAndValues:@"inputRadius", @10.0f, nil]];
return blurNode;
}
4 ответа
У меня была похожая проблема с очень широкой панорамирующей сценой, которую я хотел размыть.
Чтобы эффект размытия работал, я удалил все узлы, которые торчали слишком далеко за пределы сцены:
// Property declarations, elsewhere in the class:
var blurNode: SKEffectNode
var mainScene: SKScene
var exParents: [SKNode : SKNode] = [:]
/**
* Remove outlying nodes from the scene and activate the SKEffectNode
*/
func blurScene() {
let FILTER_MARGIN: CGFloat = 100
let widthMax: CGFloat = mainScene.size.width + FILTER_MARGIN
let heightMax: CGFloat = mainScene.size.height + FILTER_MARGIN
// Recursively iterate through all blurNode's children
blurNode.enumerateChildNodesWithName(".//*", usingBlock: {
[unowned self]
node, stop in
if node.parent != nil && node.scene != nil { // Ignore nodes we already removed
if let sprite = node as? SKSpriteNode {
// Calculate sprite node position in scene coordinates
let sceneOrig = sprite.scene!.convertPoint(sprite.position, fromNode: sprite.parent!)
// Find left, right, bottom and top edges of sprite
let l = sceneOrig.x - sprite.size.width*sprite.anchorPoint.x
let r = l + sprite.size.width
let b = sceneOrig.y - sprite.size.height*sprite.anchorPoint.y
let t = b + sprite.size.height
if l < -FILTER_MARGIN || r > widthMax || b < -FILTER_MARGIN || t > heightMax {
self.exParents[sprite] = sprite.parent!
sprite.removeFromParent()
}
}
}
})
blurNode.shouldEnableEffects = true
}
/**
* Disable blur and reparent nodes we removed earlier
*/
func removeBlur() {
self.blurNode.shouldEnableEffects = false
for (kid, parent) in exParents {
parent.addChild(kid)
}
exParents = [:]
}
ЗАМЕТКИ:
Это действительно удаляет контент из вашего узла эффекта, поэтому чрезвычайно широкие узлы не будут отображаться в конечном результате:
Вы можете видеть, что гора, выделенная красным цветом, слишком вытянута и удалена от получающегося размытия.
Этот код учитывает только SKSpriteNodes
, пустой SKNodes
кажется, не нарушать узел эффекта, но если вы используете другие видимые узлы, такие как SKShapeNodes
или же SKLabelNodes
Вам придется изменить этот код, чтобы включить их.
Если у вас есть ignoreSiblingOrder = false
этот код может испортить ваш z-порядок, так как вы не можете гарантировать, в каком порядке узлы добавляются обратно на сцену.
Материал, который я попробовал, не сработало
Просто говоря node.hidden = true
Вместо того, чтобы использовать removeFromParent()
не работает Это было бы слишком легко;)
Используя SKCropNode
вырезать внешний контент не работал для меня. Я пытался иметь SKEffectNode
родитель SKCropNode
и наоборот, но черный квадрат появился независимо от того, насколько маленьким я сделал обрезанную область. Это все еще стоит изучить, если вы отчаянно нуждаетесь в более чистом решении.
Как отмечено здесь, SKScenes
тайно SKEffectNodes
и вы можете установить их фильтр так же, как наш blurNode
выше. SKScenes
не показывать черный экран, когда их содержание слишком велико. К сожалению, вместо этого они просто отключают фильтр. Опять же, я мог что-то пропустить, так что вы можете изучить эту опцию дальше, если вы пытаетесь применить эффект ко всей сцене.
Альтернативные решения
Вы можете захватить изображение всего экрана и применить к нему фильтр, как предлагается здесь. Я закончил тем, что пошел с еще более простым решением; Я сделал общий скриншот того, что хотел размыть, затем применил очень сильное размытие, чтобы вы не могли видеть точные детали. Я использовал это как размытый фон, и вы вряд ли можете сказать, что это не настоящая вещь;) Это также сохраняет здоровый кусок памяти и позволяет избежать небольшого сбоя пользовательского интерфейса.
Musings
Это довольно неприятная ошибка, и я надеюсь, что Apple скоро найдет решение. Вы можете щелкнуть эту симпатичную фотографию камеры, чтобы получить трассировку GPU и некоторое представление о том, что происходит:
Похоже, что устройство отбрасывает кадровый буфер для узла эффектов, потому что он занимает слишком много памяти. Это подтверждается тем фактом, что, когда на устройстве больше нагрузки памяти, легче получить "черный квадрат" для меньшего содержимого в SKEffectNode
,
Я использовал метод, который работал для моей игры, но он требует, чтобы размытая область была неподвижной без движения.
На iOS 10 с использованием Swift 3 я использовал SKSpriteNode, SKView, SKEffectNode, CIFilter. Я создал спрайт из текстуры, возвращенной методом SKView "текстура из узла", и передал текущую сцену в качестве параметра, поскольку она наследуется от SKNode. По сути, я взял "скриншот" сцены и создал из нее спрайт. Затем я помещаю его в SKEffectNode с фильтром размытия. (установите для параметра "следует растеризация" значение "истина" для повышения производительности, поскольку мне нужно было только размыть изображение). Наконец я добавил новый спрайт на сцену. Оттуда вы можете добавить спрайты на сцену и разместить их над новым размытым узлом.
let blurFilter = CIFilter(name: "CIGaussianBlur")!
let blurAmount = 15.0
blurFilter.setValue(blurAmount, forKey: kCIInputRadiusKey)
let blurEffect = SKEffectNode()
blurEffect.shouldRasterize = true
let screenshotNode = SKSpriteNode(texture: gameScene.view!.texture(from: gameScene))
blurEffect.addChild(screenshotNode)
blurEffect.filter = blurFilter
gameScene.addChild(blurEffect)
SKEffectNode рендерится в текстуру. В большинстве систем iOS максимальный размер текстуры составляет 2048x2048. Если SKEffectNode пытается отобразить содержимое большего размера, он просто будет использовать текстуру 2048x2048, и все, что находится за его пределами, просто не появится в текстуре. Это не даст вам никакой ошибки или предупреждения об этом; он просто делает это молча.
И нет, нет никакого способа сказать SKEffectNode использовать текстуру определенного размера, и панорамировать и фиксировать содержимое в ней. Он всегда использует текстуру, которая покрывает все дочерние узлы, и если текстура будет слишком большой, он просто использует эту текстуру 2048x2048.
Возможное решение проблемы:
Используйте камеру, WAY Out, чтобы вы могли видеть почти все, что на вашем фоне, сделайте скриншот рендеринга этого изображения. Обрезать его под свои нужды, а затем размыть его. Затем растеризуйте это.
Затем увеличьте масштаб изображения и нарежьте его, если необходимо, и разместите соответствующим образом.