Подтвердить что ты не робот

Как создать свечение вокруг спрайта через SKEffectNode

У меня есть SKSpriteNode, что я хотел бы иметь синее свечение вокруг его краев для выделения целей. Я предполагаю, что мне нужно сделать мой спрайт дочерним элементом SKEffectNode, а затем создать/применить какой-либо фильтр.

UPDATE: я достаточно тщательно изучил это с помощью выбранного метода ответа и обнаружил, что SKEffectNode имеет значительное влияние на производительность, даже если у вас установлено значение shouldRasterize и "no filter". Я пришел к выводу, что если ваша игра требует более 10 движущихся объектов за один раз, они не могут включать SKEffectNode, даже если растрированы.

Мое решение, скорее всего, будет включать в себя предварительные визуализации свечения/анимации, поскольку SKEffectNode не собирается сокращать его для моих требований.

Если у кого-то есть представление о том, чего я не пропущу, я был бы признателен за то, что вы знаете!

Я принимаю ответ, потому что он достигает того, что я просил, но хотел добавить эти примечания всем, кто хочет пойти по этому маршруту, поэтому вы можете знать о некоторых проблемах с использованием SKEffectNode.

4b9b3361

Ответ 1

@rickster ответ велик. Поскольку у меня низкая репутация, мне, видимо, не разрешено добавлять этот код в качестве комментария к нему. Надеюсь, это не нарушит правила stackoverflow правильности. Я никоим образом не пытаюсь использовать его репорт.

Здесь код, который делает то, что он описывает в своем ответе:

Заголовок:

//  ENHGlowFilter.h
#import <CoreImage/CoreImage.h>

@interface ENHGlowFilter : CIFilter

@property (strong, nonatomic) UIColor *glowColor;
@property (strong, nonatomic) CIImage *inputImage;
@property (strong, nonatomic) NSNumber *inputRadius;
@property (strong, nonatomic) CIVector *inputCenter;

@end

//Based on ASCGLowFilter from Apple

Реализация:

#import "ENHGlowFilter.h"

@implementation ENHGlowFilter

-(id)init
{
    self = [super init];
    if (self)
    {
        _glowColor = [UIColor whiteColor];
    }
    return self;
}

- (NSArray *)attributeKeys {
    return @[@"inputRadius", @"inputCenter"];
}

- (CIImage *)outputImage {
    CIImage *inputImage = [self valueForKey:@"inputImage"];
    if (!inputImage)
        return nil;

    // Monochrome
    CIFilter *monochromeFilter = [CIFilter filterWithName:@"CIColorMatrix"];
    CGFloat red = 0.0;
    CGFloat green = 0.0;
    CGFloat blue = 0.0;
    CGFloat alpha = 0.0;
    [self.glowColor getRed:&red green:&green blue:&blue alpha:&alpha];
    [monochromeFilter setDefaults];
    [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:red] forKey:@"inputRVector"];
    [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:green] forKey:@"inputGVector"];
    [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:blue] forKey:@"inputBVector"];
    [monochromeFilter setValue:[CIVector vectorWithX:0 Y:0 Z:0 W:alpha] forKey:@"inputAVector"];
    [monochromeFilter setValue:inputImage forKey:@"inputImage"];
    CIImage *glowImage = [monochromeFilter valueForKey:@"outputImage"];

    // Scale
    float centerX = [self.inputCenter X];
    float centerY = [self.inputCenter Y];
    if (centerX > 0) {
        CGAffineTransform transform = CGAffineTransformIdentity;
        transform = CGAffineTransformTranslate(transform, centerX, centerY);
        transform = CGAffineTransformScale(transform, 1.2, 1.2);
        transform = CGAffineTransformTranslate(transform, -centerX, -centerY);

        CIFilter *affineTransformFilter = [CIFilter filterWithName:@"CIAffineTransform"];
        [affineTransformFilter setDefaults];
        [affineTransformFilter setValue:[NSValue valueWithCGAffineTransform:transform] forKey:@"inputTransform"];
        [affineTransformFilter setValue:glowImage forKey:@"inputImage"];
        glowImage = [affineTransformFilter valueForKey:@"outputImage"];
    }

    // Blur
    CIFilter *gaussianBlurFilter = [CIFilter filterWithName:@"CIGaussianBlur"];
    [gaussianBlurFilter setDefaults];
    [gaussianBlurFilter setValue:glowImage forKey:@"inputImage"];
    [gaussianBlurFilter setValue:self.inputRadius ?: @10.0 forKey:@"inputRadius"];
    glowImage = [gaussianBlurFilter valueForKey:@"outputImage"];

    // Blend
    CIFilter *blendFilter = [CIFilter filterWithName:@"CISourceOverCompositing"];
    [blendFilter setDefaults];
    [blendFilter setValue:glowImage forKey:@"inputBackgroundImage"];
    [blendFilter setValue:inputImage forKey:@"inputImage"];
    glowImage = [blendFilter valueForKey:@"outputImage"];

    return glowImage;
}


@end

При использовании:

@implementation ENHMyScene //SKScene subclass

-(id)initWithSize:(CGSize)size {    
    if (self = [super initWithSize:size]) {
        /* Setup your scene here */
        [self setAnchorPoint:(CGPoint){0.5, 0.5}];
        self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];

        SKEffectNode *effectNode = [[SKEffectNode alloc] init];
        ENHGlowFilter *glowFilter = [[ENHGlowFilter alloc] init];
        [glowFilter setGlowColor:[[UIColor redColor] colorWithAlphaComponent:0.5]];
        [effectNode setShouldRasterize:YES];
        [effectNode setFilter:glowFilter];
        [self addChild:effectNode];
        _effectNode = effectNode;
    }
    return self;
}

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    /* Called when a touch begins */

    for (UITouch *touch in touches) {
        CGPoint location = [touch locationInNode:self];
        SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:@"Spaceship"];
        sprite.position = location;
        [self.effectNode addChild:sprite];
    }
}

Ответ 2

Вы можете создать эффект свечения в Core Image, создав подкласс CIFilter, который содержит несколько встроенных фильтров. Такой фильтр будет включать такие шаги:

  • Создайте изображение, которое будет использоваться в качестве синего свечения. Вероятно, есть несколько достойных способов сделать это; один - использовать CIColorMatrix для создания монохромной версии входного изображения.
  • Увеличьте и размывайте изображение свечения (CIAffineTransform + CIGaussianBlur).
  • Составить исходное входное изображение по изображению свечения (CISourceOverCompositing).

Как только у вас есть подкласс CIFilter, который делает все это, вы можете использовать его с SKEffectNode, чтобы получить свечение в реальном времени вокруг эффектов node. Здесь он работает в шаблоне Xcode "Sprite Kit Game" на iPad 4:

Glowing spaceship in Sprite Kit

Я запустил это и запустил через несколько минут, обрезая класс пользовательских фильтров, используемый для аналогичного эффекта в презентации Scene Kit из WWDC 2013 - возьмите его из пакета образцов кода WWDC в developer.apple.com/downloads и найдите класс ASCGlowFilter. (Если вы хотите использовать этот код в iOS, вам нужно будет изменить часть NSAffineTransform, чтобы использовать CGAffineTransform. Я также заменил свойства centerX и centerY параметром inputCenter типа CIVector поэтому Sprite Kit может автоматически центрировать эффект на спрайт.)

Я сказал "свет в реальном времени"? Ага! Это сокращение от "действительно ест процессорное время". Обратите внимание, что на скриншоте он больше не привязан к скорости 60 кадров в секунду, даже с одним космическим кораблем, и с программным обеспечением OpenGL ES на iOS Simulator он работает со скоростью слайд-шоу. Если вы находитесь на Mac, у вас, вероятно, есть кремний, чтобы сэкономить... но если вы хотите сделать это в своей игре, помните о некоторых вещах:

  • Возможно, есть некоторые способы повысить производительность самого фильтра. Играйте с различными фильтрами CI, и вы можете увидеть некоторое улучшение (в Core Image есть несколько фильтров размытия, некоторые из которых, безусловно, будут быстрее, чем гауссовские). Также обратите внимание, что эффекты размытия имеют тенденцию быть привязаны к фрагментарным шейдерам, поэтому чем меньше изображение и тем меньше радиус свечения, тем лучше.
  • Если вы хотите иметь несколько свечений в сцене, подумайте о том, чтобы сделать все светящиеся спрайты детьми с таким же эффектом node - это отобразит их все на одно изображение, а затем применит фильтр один раз.
  • Если спрайты, которые будут светиться, не сильно меняются (например, если наш космический корабль не вращается), настройка shouldRasterize на YES на эффект node должна много помочь. (На самом деле, в этом случае вы можете получить некоторое улучшение, повернув эффект node вместо спрайта внутри него.)
  • Вам действительно нужно сияние в реальном времени? Как и в случае с множеством шикарных графических эффектов в играх, вы получите гораздо лучшую производительность, если будете подделывать его. Сделайте размытый, синий космический корабль в своем любимом графическом редакторе и поместите его в сцену в качестве другого спрайта.

Ответ 3

Вы можете использовать SKShapeNode за спрайтом и определить свечение, используя его glowWidth и strokeColor свойства. Если вы по размеру и правильно позиционируете, это должно дать вам вид свечения. Это не дает вам много вариантов настройки, но я предполагаю, что это намного проще, чем использование CIFilter с SKEffectNode, который, скорее всего, является другим логическим вариантом, который у вас есть для этого.