前言

Scene Kit(SK)是WWDC12推出OS X平台的属于Cocoa类的3D渲染引擎框架。第一版已经有不错了并且后续又增加了一些强大的功能比如着色修改,限制。当苹果推出新系统Mavericks时又增加了骨架动画。今年,SK变得更加强大了,新增支持粒子效果,物理模拟,脚本事件,多程渲染,最重要得是支持iOS平台。

从一开始,我就发现SK最强大与区别是它与其他图形处理框架的整合,比如CI,CA还有SK。这意味着除了游戏工程师外一些有兴趣游戏开发的Cocoa开发也能对SK马上上手。

SK基础

SK以OpenGL为基石,对外开放高级API,你可以用OC或者swift创建出光,几何图形,物质,还有摄像机对象。如果你用过OpenGL早先没有着色器的版本,那估计会让你想起那段关于被系统限制而不能方便配置的不好的记忆。幸运的是,现在不会在这样了。高级配置对于大部分一般得任务已经足够了-甚至是更高级的东西比如动态着色还有层次效果。

甚至,SK还可以让你调用低级接口去调用OpenGL着色代码去进行渲染(GLSL)。

结点

除去光,几何体,物质还有相机,SK使用了节点层级来组织自身的内容。每个节点都包含位置,旋转度,还有相对父节点的缩放度,反过来也关联自身的父节点,如此特性递归到根节点。为了给其他对象中的某个一个3D坐标,每个节点被连接到节点群的某一个节点上。节点层级用如下的方法管理:

addChildNode(_:)
insertChildNode(_: atIndex:)
removeFromParentNode()

如同iOS以及OS X中管理视图层级的方式一样。

几何学对象

SK提出内置简单形状几何学对象比如像箱子,球体,飞机,圆锥体,对于游戏来说,大部分你要做的就是从一个文件中载入3D模型。你可以导入或者导出COLLADA文件采取关联文件名的方式引用文件:

let chessPieces = SCNScene(named: "chess pieces") // SCNScene?

若文件所包含得场景需要被完全展示,那它可以作为整个场景视图的场景。如果场景包含许多对象,并且这些对象只有部分是需要展示在屏幕上的,那么他们可以通过名字索引找到,可以将他们加入场景并且在场景视图中进行渲染。

if let knight = chessPieces.rootNode.childNodeWithName("Knight", recursively: true) {
    sceneView.scene?.rootNode.addChildNode(knight)
}

在导入文件的原始结点和包含着其他任一或者所有得子结点,包括附带了几何体对象,光,摄像机。搜寻同样名字的子结点会指向一个相对的对象。

这里有图。。

当一个场景有多个模型拷贝的时候,比如,为了展示两个其实在国际象棋的棋盘上,你会拷贝结点或者克隆(递归拷贝)它。这样这份结点的拷贝引用了同一个物质几何体对象。凉粉拷贝都指向了同一个几何体对象。所以改变一个物质,这个几何体对象将也不得不被拷贝并且拥有一个新的物质依附在它身上。拷贝一个几何体对象比较迅速并且代价较小,因为拷贝还是指向了同一份顶点数据。

根据操纵骨动画集合体对象导入的结点有一个皮肤对象用来提供访问骨架结点层级喝用来管理骨头喝几何体对象的关系。个别的骨头可以被移动和旋转;但是,更复杂的动画会改变多个骨头-举个栗子, a character’s walk cycle - 更可能是从一个文件加载并加到对象中去。

SK中的光是完全动态的。这使得他们更容易被获取并且更易于使用,但是同样意味着SK提供的光源跟一个完整功能的游戏引擎比起来不够先进。这里有四种不同的光:周围光,指向性光,点光,聚光。

在很多情况下,特定一个旋转坐标系和一个角度并不是最感知的方法去识别一个物体上的光。在这些情况下,这个给出光得结点的位置喝朝向能够通过另外一个结点进行限制。添加一个“look at”-约束一位置这束光线将会一直指向这个结点,及时结点移动了。

let spot = SCNLight()
spot.type = SCNLightTypeSpot
spot.castsShadow = true

let spotNode = SCNNode()
spotNode.light = spot
spotNode.position = SCNVector3(x: 4, y: 7, z: 6)

let lookAt = SCNLookAtConstraint(target: knight)
spotNode.constraints = [lookAt]

动画

SK中任意对象任意属性都是可动画的。就想Cocoa,这意味着你需要创建一个CAAnimation关键路径(哪怕是“position.x”)并添加到动画对象。类似的,你可以改变你动画相关得值在SCNTransaction动作“begin”与“commit”。上述两种方式十分相同,毫无二致。

let move = CABasicAnimation(keyPath: "position.x")
move.byValue  = 10
move.duration = 1.0
knight.addAnimation(move, forKey: "slide right")

SK同样支持Sprite Kit动作方式的动画API。这就允许你去创建一串动画然后调用自定义的block代码与其他动画同时运行,这些动作作为游戏循环的一部分,并且每一帧都进行更新数据层值,而不仅仅是表象层结点。

事实上,如果你曾经使用过Sprite Kit,那你对SK将有似曾相识的感觉,不同的是这次是3D世界。在iOS8和OS X 10.10,上述两个框架可以一起协作。在使用Sprite Kit的时候,2D可以混杂3D模型一起使用。在使用SK开发的时候,Sprite Kit的场景以及纹理可以被用作SK的纹理,并且Sprite Kit场景可以被用作一种2D覆盖在SK场景之上。

使用SK写游戏

动作跟纹理并不是Scene KitSprite Kit唯一共同的东西。当使用SK进行游戏编码的时候,这种感觉与2D框架是非常相似的。在上述两种开发框架下,游戏轮回有如下一样的步骤,一样的代理回调。

  1. 更新场景
  2. 运行动画/动作
  3. 仿真物理
  4. 执行约束
  5. 渲染

请看轮回方向 ->
图片描述
每一帧都有一个回调并且用来执行游戏运行相关的逻辑比如像输入源句柄,人工智能,或者是游戏剧本。

输入源句柄

SK使用相同的输入机制,例如键盘事件,鼠标事件,点击事件,还有Cocoa与Cocoa Touch产品中手势识别,主要的不同是这里只有一个视图那就是场景视图。对于键盘事件或者缩放,快滑,旋转手势,它可能仅仅是知道这些事件发生了,但是比如点击或者长按拖动,此类事件则需要更多的信息。

对于这些情况,场景视图需要使用-hitTest(_: options:)来进行点击测试。与通常视图以及图层不同的是(只返回点击的子视图或者子图层),SK中返回的是有交点几何体对象以及摄像机穿过该点的射线的数组。每一个点击结果包含着点击几何体归属结点,还有交点的详细信息(点击坐标,纹理坐标)。大多数情形,这些信息足够知道了所触碰的第一个结点是:

if let firstHit = sceneView.hitTest(tapLocation, options: nil)?.first as? SCNHitTestResult {
    let hitNode = firstHit.node
    // do something with the node that was hit...
}

默认渲染扩展

光,材质配置,虽然容易协同工作,但是也只能让你走这么远(限制你)。如果你已经用上了OpenGL的着色器,你可以用其进行渲染来配置材质,进而做到完全的自定义。然后,如果你只是想修改默认的渲染,SK剔除四点可以将着色器代码片段(GLSL)注入到默认的渲染代码中。在不同的切入点,SK提供了数据入口例如变换矩阵,几何体数据,纹理示例,还有渲染颜色输出。

举个栗子,在入口点的数行着色器代码片段可以被用来扭曲几何体对象X轴方向所有的点。可以通过定义一个方程去创建一个渲染变换并把这个变换应用到这些X轴点和几何体的正常化。同样也可以定义一个“不变”变量来决定物体的扭曲度:

// a function that creates a rotation transform matrix around X
mat4 rotationAroundX(float angle)
{
    return mat4(1.0,    0.0,         0.0,        0.0,
                0.0,    cos(angle), -sin(angle), 0.0,
                0.0,    sin(angle),  cos(angle), 0.0,
                0.0,    0.0,         0.0,        1.0);
}

#pragma body

uniform float twistFactor = 1.0;
float rotationAngle = _geometry.position.x * twistFactor;
mat4 rotationMatrix = rotationAroundX(rotationAngle);

// position is a vec4
_geometry.position *= rotationMatrix;

// normal is a vec3
vec4 twistedNormal = vec4(_geometry.normal, 1.0) * rotationMatrix;
_geometry.normal   = twistedNormal.xyz;

着色修改器可以被依附到任意个几何体对象上或是其下的人一个材质上。上述两个类都是支持KVC,这意味着你可以为任意的Keys设置值。在着色修改器中已经声明了“扭曲因子”使SK观察Key和重绑定的不变值任何时候只要值发生变化。即可以使用KVC进行简单得改变属性值:

torus.setValue(5.0, forKey: "twistFactor")

同样可以靠创建一个CAAnimation来获取关键路径:

let twist = CABasicAnimation(keyPath: "twistFactor")
twist.fromValue = 5
twist.toValue   = 0
twist.duration  = 2.0

torus.addAnimation(twist, forKey: "Twist the torus")

图片描述

延迟着色

有些图形效果不能在一次的渲染过程中达成,即使是纯OpenGL。解决方案是序列化不同的着色器操作来进行预处理操作或者延迟着色。SK在这种处理技术上使用的是SCNTechnique类。它创建一个字典来定义绘画过程,输入输出,着色文件,符号等等。

第一步通常是SK默认渲染,输出源是颜色和场景的层级深度。如果你不想颜色被着色,材质可以被设置常量光模型,或者场景中的所有光可以被替代成单一周围光。

举个栗子,通过SK初始值传参以及二次传参的自然值获取深度,同时在第三次传参的时候对二者进行边缘探测,你可以在沿着大纲略图和边缘画出清晰的轮廓:

图片描述


Cruise_Chan
729 声望71 粉丝

技能树点歪了...咋办