如何将一个SKNode渲染成UIImage

13

我正在使用SpriteKit进行实验,并尝试找出如何将SKNode的“抓取”捕获到UIImage中。

对于UIView(或UIView子类),我已经使用了视图的layer属性来渲染到图形上下文中。

例如:

#import <QuartzCore/QuartzCore.h>
+ (UIImage *)imageOfView:(UIView *)view {
    UIGraphicsBeginImageContextWithOptions(view.frame.size, YES, 0.0f);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [view.layer renderInContext:context];
    UIImage *viewShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return viewShot;
}

SKNode不是UIView的子类,因此似乎没有与图层相关联。

您有什么想法可以将给定的SKNode呈现为UIImage吗?

2个回答

17

这将捕捉整个场景:

CGRect bounds = self.scene.view.bounds;
UIGraphicsBeginImageContextWithOptions(bounds.size, NO, [UIScreen mainScreen].scale);
[self drawViewHierarchyInRect:bounds afterScreenUpdates:YES];
UIImage* screenshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

如果您只想要一个特定的节点分支,您可以在截图之前隐藏所有不需要捕获的节点。您也可以使用转换为 UIKit 坐标的累积帧来仅捕获所需节点及其子级的区域。

或者,您可以从节点层次结构的特定部分获取 SKTexture

SKTexture* tex = [self.scene.view textureFromNode:yourNode];

iOS 9之前,没有办法将SKTexture转换为UIImage。但是现在非常容易:

UIImage *image = [UIImage imageWithCGImage:tex.CGImage];

1
谢谢。结果将再次用于Sprite Kit - 所以textureFromNode:非常好用。干杯! - So Over It
很遗憾,是的。 - Coldsteel48
1
有没有一种方法可以捕捉带有透明背景的精灵? - GeneCode

6
对于任何在七年后查看此线程的人,我尝试使用上述方法,但效果相对较慢。我想要将整个SKScene渲染到屏幕外,因为我正在以帧为单位生成视频中的图像。这意味着我不能使用LearnCocos2D建议的第一种方法,因为它需要在屏幕上绘制视图,而第二种方法需要很长时间将SKTexture转换为UIImage。您可以使用iOS 11.0中引入的新的SKRenderer类将整个场景渲染到UIImage,并利用Metal使渲染速度相对较快。我能够在大约0.013秒内呈现1920x1080的SKScene!
您可以使用以下扩展:
- 确保导入MetalKit - ignoreScreenScale参数指定输出图像是否应该是像素精确的。通常,如果您将在屏幕上显示图像,则希望其值为false。当ignoreScreenScale为false时,输出图像的大小按设备比例进行缩放,使场景上的每个“点”在图像上占据的像素数与在屏幕上一样。当ignoreScreenScale为true时,输出图像的像素大小等于点数大小的SKScene。
干杯!
extension SKScene {
    func toImage(ignoreScreenScale: Bool = false) -> UIImage? {
        guard let device = MTLCreateSystemDefaultDevice(),
              let commandQueue = device.makeCommandQueue(),
              let commandBuffer = commandQueue.makeCommandBuffer() else { return nil }

        let scale = ignoreScreenScale ? 1 : UIScreen.main.scale
        let size = self.size.applying(CGAffineTransform(scaleX: scale, y: scale))
        let renderer = SKRenderer(device: device)
        let renderPassDescriptor = MTLRenderPassDescriptor()

        var r = CGFloat.zero, g = CGFloat.zero, b = CGFloat.zero, a = CGFloat.zero
        backgroundColor.getRed(&r, green: &g, blue: &b, alpha: &a)

        let textureDescriptor = MTLTextureDescriptor()
        textureDescriptor.usage = [.renderTarget, .shaderRead]
        textureDescriptor.width = Int(size.width)
        textureDescriptor.height = Int(size.height)
        let texture = device.makeTexture(descriptor: textureDescriptor)

        renderPassDescriptor.colorAttachments[0].loadAction = .clear
        renderPassDescriptor.colorAttachments[0].texture = texture
        renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(
            red: Double(r),
            green: Double(g),
            blue: Double(b),
            alpha:Double(a)
        )

        renderer.scene = self
        renderer.render(withViewport: CGRect(origin: .zero, size: size), commandBuffer: commandBuffer, renderPassDescriptor: renderPassDescriptor)
        commandBuffer.commit()

        let image = CIImage(mtlTexture: texture!, options: nil)!
        let transformed = image.transformed(by: CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -image.extent.size.height))
        return UIImage(ciImage: transformed)
    }
}

听起来是一个很棒的扩展,但我无法让它正常工作,但我真的需要这个结果!请帮我解决这个错误: -[MTLDebugRenderCommandEncoder validateCommonDrawErrors:]:5252: 失败的断言 `绘制错误验证 MTLDepthStencilDescriptor使用frontFaceStencil,但MTLRenderPassDescriptor具有nil stencilAttachment纹理 MTLDepthStencilDescriptor使用backFaceStencil,但MTLRenderPassDescriptor具有nil stencilAttachment纹理。 - Johan Albrectsen
您可能需要将模板纹理添加到模板附件中。通过将renderPassDescriptor.stencilAttachment.texture设置为与主渲染目标纹理具有相同尺寸的某些纹理来实现此操作。 - CentrumGuy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接