web-dev-qa-db-ja.com

SKEffectNodeを介してスプライトの周囲にグローを作成するにはどうすればよいですか

強調表示の目的で、エッジの周りに青い輝きを持たせたいSKSpriteNodeがあります。スプライトをSKEffectNodeの子にしてから、ある種のフィルターを作成/適用する必要があると思います。

更新:選択した回答のアプローチでこれをかなり調査したところ、SKEffectNodeに設定し、「フィルターなし」を定義した場合でも、shouldRasterizeのパフォーマンスに大きな影響があることがわかりました。私の結論は、ゲームで一度に10を超える移動オブジェクトが必要な場合、ラスタライズされていてもSKEffectNodeを含めることはできないということです。

SKEffectNodeは私の要件に合わせてカットしないので、私のソリューションには、事前にレンダリングされたグロー画像/アニメーションが含まれる可能性があります。

誰かが私が欠けているものについて洞察を持っているなら、あなたが知っていることは何でも聞いていただければ幸いです!

私が求めていることを達成したので、私は回答を受け入れますが、SKEffectNodeの使用に関するいくつかの問題に気付くことができるように、このルートに行くことを検討している人にこれらのメモを追加したいと思います。

20
prototypical

@ricksterの答えは素晴らしいです。担当者が少ないため、このコードをコメントとしてコメントに追加することはできません。これが適切なスタックオーバーフローのルールに違反しないことを願っています。私は彼の担当者を決して使用しようとはしていません。

これは彼が彼の答えで説明していることを行うコードです:

ヘッダ:

//  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];
    }
}
27
Jonathan Saggau

複数の組み込みフィルターを構成するCIFilterサブクラスを作成することにより、CoreImageでグロー効果を作成できます。このようなフィルターには、次のような手順が含まれます。

  1. 青い輝きとして使用する画像を作成します。これを行うには、おそらくいくつかの適切な方法があります。 1つは、CIColorMatrixを使用して、モノクロバージョンの入力画像を作成することです。
  2. グロー画像を拡大してぼかします(CIAffineTransform + CIGaussianBlur)。
  3. 元の入力画像をグロー画像(CISourceOverCompositing)に合成します。

それをすべて行うCIFilterサブクラスができたら、それをSKEffectNodeと一緒に使用して、エフェクトノードの子の周りをリアルタイムでグローさせることができます。ここでは、iPad4の「SpriteKitGame」Xcodeテンプレートで実行されています。

Glowing spaceship in Sprite Kit

WWDC 2013からのシーンキットプレゼンテーションで同様の効果に使用されるカスタムフィルタークラスをクリブすることで、これを数分で起動して実行しました- developer.Apple.com/にあるWWDCサンプルコードパッケージから取得しますdownloads 、そしてASCGlowFilterクラスを探します。 (iOSでそのコードを使用する場合は、代わりにNSAffineTransform部分をCGAffineTransformを使用するように変更する必要があります。また、centerXプロパティとcenterYプロパティをinputCenterタイプのCIVectorパラメーターに置き換えて、スプライトキットが自動的にスプライト。)

「リアルタイム」の輝きを言ったか。うん!これは「CPU時間を本当に消費する」の略です。スクリーンショットでは、宇宙船が1つしかない場合でも60 fpsで固定されていないことに注意してください。iOSシミュレーターのソフトウェアOpenGL ESレンダラーを使用すると、スライドショーの速度で実行されます。 Macを使用している場合は、おそらく余裕のあるシリコンがありますが、ゲームでこれを実行する場合は、いくつかの点に注意してください。

  • フィルター自体のパフォーマンスを向上させる方法はおそらくいくつかあります。さまざまなCIフィルターを試してみると、いくつかの改善が見られる場合があります(Core Imageにはいくつかのぼかしフィルターがあり、そのいくつかは確かにガウスよりも高速です)。また、ぼかし効果はフラグメントシェーダーにバインドされる傾向があるため、画像が小さく、グロー半径が小さいほど良いことに注意してください。
  • シーンに複数のグローを使用する場合は、すべてのグロースプライトを同じエフェクトノードの子にすることを検討してください。すべてのスプライトを1つのイメージにレンダリングし、フィルターを1回適用します。
  • グローするスプライトがあまり変化しない場合(たとえば、宇宙船が回転していない場合)、エフェクトノードでshouldRasterizeYESに設定すると非常に役立ちます。 (実際には、この場合、スプライトの代わりにエフェクトノードを回転させることで改善が得られる可能性があります。)
  • realtimeグローが本当に必要ですか?ゲームの多くの気の利いたグラフィック効果と同様に、それを偽造すると、はるかに優れたパフォーマンスが得られます。お気に入りのグラフィックエディタでぼやけた青い宇宙船を作成し、別のスプライトとしてシーンに配置します。
22
rickster

スプライトの背後でSKShapeNodeを使用し、そのglowWidthプロパティとstrokeColorプロパティを使用してグローを定義できます。サイズと位置を正しく設定すると、輝きのようになります。これはカスタマイズのための多くのオプションを提供しませんが、CIFilterSKEffectNodeと一緒に使用するよりもはるかに簡単であろうと思います。

3
Matt