如何以编程方式切换到Sprite Kit中的openGL以采用iPad2等旧设备

Jac*_*rse 5 shader opengl-es ipad-2 sprite-kit metal

我正在使用Sprite Kit开发游戏.由于iOS9 SpriteKit使用Metal作为着色器后端.SK和着色器效果很好.但如果我在iPad2上进行测试,它就不再适用了.我已经读过这个问题并且知道iPad2不支持Metal.现在我想回到开放GL以在这种情况下提供GLES着色器.

我可以通过编程方式测试"Metal"是否可用(Swift 2):

    /**
     Returns true if the executing device supports metal.
     */
    var metalAvailable:Bool {

        get {

            struct Static {

                static var metalAvailable : Bool = false
                static var metalNeedsToBeTested : Bool = true
            }

            if Static.metalNeedsToBeTested {

                let device = MTLCreateSystemDefaultDevice()
                Static.metalAvailable = (device != nil)
            }
            return Static.metalAvailable
        }
    }
Run Code Online (Sandbox Code Playgroud)

我知道可以在应用程序的plist中设置兼容模式:

  • 编辑应用的Info.plist
  • 添加PrefersOpenGL键,bool值为YES

在这种情况下,SpriteKit总是使用openGL.这不是我想要使用的.我希望我的应用程序始终使用金属,如果没有检测到金属设备,则回退到openGL.

SpriteKit或UIKit中的任何选项,还是API中的某个地方,我可以通过编程方式切换到"PrefersOpenGL"选项?

提前致谢,

插口

Jac*_*rse 6

摘要

我找到了解决方案.最后这是我的错.SpriteKit肯定会自动回退到openGL.GLES着色器语言比Metal更不宽容.这是问题的来源.在openGL着色器中,您必须在每个数字中设置小数点.不幸的是,着色器编译器在编译后没有告诉我.另一个问题是有时旧的着色器构建坚持捆绑.在测试着色器之前执行"清理".

这就是如何处理这两种着色器和检测Metal/openGL:

检测金属是否可用

这个小助手可以放在代码中的任何位置.它可以帮助您在第一次使用时检测Metal,并让您有机会根据配置执行一次自定义代码.

头:

#import SpriteKit
#import Metal
Run Code Online (Sandbox Code Playgroud)

码:

/**
 Detect Metal. Returns true if the device supports metal.
 */
var metalAvailable:Bool {

    get {

        struct Static {

            static var metalAvailable : Bool = false
            static var metalNeedsToBeTested : Bool = true
        }

        if Static.metalNeedsToBeTested {

            Static.metalNeedsToBeTested = false
            let device = MTLCreateSystemDefaultDevice()
            Static.metalAvailable = (device != nil)
            if Static.metalAvailable {

               // Do sth. to init Metal code, if needed
            } else {

               // Do sth. to init openGL code, if needed
            }
        }
        return Static.metalAvailable
    }
}
Run Code Online (Sandbox Code Playgroud)

在Sprite Kit中创建着色器

像往常一样使用sprite工具包创建着色器.

let shaderContainer = SKSpriteNode()
shaderContainer.position = CGPoint(x:self.frame.size.width/2, y:self.frame.size.height/2)
shaderContainer.size = CGSize(width:self.frame.size.width, height:self.frame.size.height)
self.backgroundNode.addChild(shaderContainer)

let bgShader:SKShader
// Test if metal is available
if self.metalAvailable {

  bgShader = SKShader(fileNamed:"plasma.fsh")
} else {

  NSLog("Falling back to openGL")
  bgShader = SKShader(fileNamed:"plasmaGL.fsh")
}

// Add your uniforms. OpenGL needs the size of the frame to normalize
// The coordinates. This is why we always use the size uniform        
bgShader.uniforms = [
   SKUniform(name: "size", floatVector2:GLKVector2Make(1920.0, 1024.0))
]

shaderContainer.shader = bgShader
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,根据检测到的配置,正在加载另一个着色器文件.openGL着色器需要额外的大小统一,因为符号v_tex_coord在openGL中不可用.如果你没有在Metal中使用size uniform,你可以将uniforms语句移动到if块中或者忽略它.如果你不使用金属,金属不会抱怨.

金属着色器:plasma.fsh

#define  M_PI 3.1415926535897932384626433832795
#define frequency  1 // Metal is less sensitive to number types. 
#define colorDepth 2 // Numbers without decimal point make problems with openGL
void main(void) {

    vec2 uv = v_tex_coord; // Normalized coordinates in Metal shaders

    float red = ((sin((uv.x + u_time * 0.01) * M_PI * frequency) * cos((uv.y + u_time * 0.03) * M_PI * frequency) + 1) / colorDepth) + (colorDepth / 2.75) - (2 / 2.75);

    gl_FragColor = vec4(red, uv.x, u_time, 1.0);
}
Run Code Online (Sandbox Code Playgroud)

在金属着色器中,您可以简单地读取标准化坐标.如果您愿意,可以使用尺寸重建图像坐标.然而,金属更加宽容小数点.如您所见,某些数字在此处没有小数点.

打开GL着色器:plasmaGL.fsh

// OPEN GL shaders NEED the decimal point in numbers. so never use 1 but 1. or 1.0
#define  M_PI 3.1415926535897932384626433832795
#define frequency  1.0  // This number must have a decimal point
#define colorDepth 2.0  // Same here.
void main(void) {

    vec2 uv = gl_FragCoord.xy / size.xy; // Frame coordinates in openGL

    // This formula is always using numbers with decimal points. 
    // Compare it to the metal shader. Two numbers of the metal
    // have no decimal point. If you cut copy paste the metal shader 
    // formula to the GL shader it will not work!
    float red = ((sin((uv.x + u_time * 0.01) * M_PI * frequency) * cos((uv.y + u_time * 0.03) * M_PI * frequency) + 1.0) / colorDepth) + (colorDepth / 2.75) - (2.0 / 2.75);

    gl_FragColor = vec4(red, uv.x, u_time, 1.0);
}
Run Code Online (Sandbox Code Playgroud)

外表

测试两个系统和创建两个着色器是更多的工作.但只要我们从GL转换为Metal,这就是测试应该使用哪种着色器的好方法.iOS模拟器也不支持Metal.这意味着您可以使用iOS和tvOS模拟器测试openGL行为.

如果您为AppleTV开发,那么这种方法非常方便,因为openGL着色器始终与Metal配合使用.你只需要更换gl_FragCoord.xy/size.xyv_tex_coord.如果您在模拟器上运行代码,您将看到openGL代码,如果您在AppleTV目标上运行它,您将看到平滑的金属着色器.

所有快速开发人员的另一个暗示:永远不要忘记着色器末尾的分号;-)

另一个陷阱是铸造.

Metal:int intVal =(int)uv.x; float a =(float)intVal;

Open GL:int intVal = int(uv.x); float a = float(intVal);

我希望我可以帮助任何人.

干杯,

插口