Can I use a PNG sequence as an animated material o

2019-05-10 15:17发布

I'd like to programmatically change the material of an object rendered using SceneKit in iOS. But I would like to use animated image files. PNG sequences work great in iOS for stuff like UIImageViews -- how do I use them as a material in SceneKit?

Something like this doesn't work:

floor.firstMaterial.diffuse.contents = [UIImage animatedImageNamed:@"ANIMATION_" duration:6.0f];

Thanks!

3条回答
我命由我不由天
2楼-- · 2019-05-10 15:28

Swift 4.1 / Xcode 9.3 / iOS 11.3

You can resolve this by rolling your own animation with CADisplayLink:

Load your images into an array of UIImage

var images : [UIImage] = [
    UIImage(named: "image1.png"),
    UIImage(named: "image2.png"),
    ...
]

Then, to start the animation:

var animationImageFrameIndex : Double = 0
var displayLink : CADisplayLink?

func startAnimation() {
    animationImageFrameIndex = 0
    displayLink = CADisplayLink(target: self, selector: #selector(animationStep(_:)))
    displayLink!.preferredFramesPerSecond = 60
    displayLink!.add(to: .current, forMode: .defaultRunLoopMode)
}

The preferredFramesPerSecond is the desired rate at which you'd like your animationStep method to be called. However, CADisplayLink does not guaranty that it will be called at the rate you request.

The animationStep function gets called periodically by CADisplayLink, which actually cycles through the images:

@objc func animationStep(_ displayLink: CADisplayLink) {
    let desiredFPS : Double = 24
    let realFPS = 1 / (displayLink.targetTimestamp - displayLink.timestamp)

    material.diffuse.contents = images[Int(animationImageFrameIndex)]
    animationImageFrameIndex += desiredFPS / realFPS
    if Int(animationImageFrameIndex) >= images.count {
        displayLink.remove(from: .current, forMode: .defaultRunLoopMode)
    }
}

In the method above, desiredFPS is the frame rate you actually want to use in the animation.

There is a full sample project on Bitbucket, here.

You SHOULD be able to animate this (a sequence of images) with a simple CAKeyframeAnimation, but after much testing, it's clear that doesn't work, which appears to be a bug on Apple's side. There's an open Radar here from someone else, and I also filed a bug report.

查看更多
我只想做你的唯一
3楼-- · 2019-05-10 15:39

there is nothing that you can use out of the box.

I would do this using shader modifiers. If you pack all your images in a single image, you can use a modifier (at the SCNShaderModifierEntryPointGeometry entry point) to update the object's texture coordinates according to u_time so that every t ms another image (or sub-image of the atlas) is used.

查看更多
可以哭但决不认输i
4楼-- · 2019-05-10 15:47

There is a much simpler solution (or at least I think it's simpler) though I can't attest to whether it's more efficient. I've tested this on an iPad mini (1st gen) and it works fine.

Rather than place a texture that is a spritesheet into the material's contents (and fiddle with the shader), you can place an SKScene object there instead.

That way you can just use SKSpriteNode objects within the SKScene and animate them using SCNAction objects. All of the animation can then be controlled easily and more powerfully without having to touch shader code.

    NSArray<SKTexture*> *cursorTextures =
    @[
      [SKTexture textureWithImageNamed:@"tilecursor1.png"],
      [SKTexture textureWithImageNamed:@"tilecursor2.png"],
      [SKTexture textureWithImageNamed:@"tilecursor3.png"],
      [SKTexture textureWithImageNamed:@"tilecursor4.png"],
      [SKTexture textureWithImageNamed:@"tilecursor5.png"]
      ];

    SKSpriteNode *cursorSprite = [SKSpriteNode spriteNodeWithTexture:cursorTextures[0]];
    cursorSprite.position = CGPointMake(cursorSprite.size.width/2.0, cursorSprite.size.height/2.0);
    [cursorSprite runAction:[SKAction repeatActionForever:[SKAction animateWithTextures:cursorTextures timePerFrame:0.1]]];

    SKScene *cursorScene = [SKScene sceneWithSize:cursorSprite.size];
    cursorScene.backgroundColor = [UIColor clearColor];
    [cursorScene addChild:cursorSprite];

    ...

    self.cursor.material.diffuse.contents = cursorScene;
查看更多
登录 后发表回答