SceneKit:使块更逼真或类似3D

时间:2016-07-06 03:06:54

标签: ios scenekit voxel

下面的代码用于创建场景并在SceneKit中创建块。根据我们的用户,这些街区看起来平坦而不是“足够3D”。屏幕截图1-2显示了我们的应用程序。

屏幕截图3-5显示了用户对块的期望,它更像3D。

在与不同的人交谈之后,对于如何渲染看起来更像截图3-5的块有不同的看法。有人说使用环境遮挡,其他人说体素照明,有人说使用聚光灯和使用阴影,或定向照明。

我们之前尝试添加全向照明,但这不起作用,因此被删除了。正如您在代码中看到的那样,我们还尝试了环境光节点,但也没有产生正确的结果。

渲染我们的块并获得与截图3-5类似的外观的最佳方法是什么?

注意:我们理解代码未针对性能进行优化,即显示的多边形不应显示。好的,可以。重点不在于性能,而在于实现更多类似3D的渲染。您可以假设节点有一些硬限制,例如场景中不超过1K或10K。

代码:

func createScene() {
    // Set scene view
    let scene = SCNScene()
    sceneView.jitteringEnabled = true
    sceneView.scene = scene

    // Add camera node
    sceneView.pointOfView = cameraNode

    // Make delegate to capture screenshots
    sceneView.delegate = self

    // Set ambient lighting
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = SCNLightTypeAmbient
    ambientLightNode.light!.color = UIColor(white: 0.50, alpha: 1.0)
    //scene.rootNode.addChildNode(ambientLightNode)
    //sceneView.autoenablesDefaultLighting = true

    // Set floor
    setFloor()

    // Set sky
    setSky()

    // Set initial position for user node
    userNode.position = SCNVector3(x: 0, y: Float(CameraMinY), z: Float(CameraZoom))

    // Add user node
    scene.rootNode.addChildNode(userNode)

    // Add camera to user node
    // zNear fixes white triangle bug while zFar fixes white line bug
    cameraNode.camera = SCNCamera()
    cameraNode.camera!.zNear = Double(0.1)
    cameraNode.camera!.zFar = Double(Int.max)
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 0) //EB: Add some offset to represent the head
    userNode.addChildNode(cameraNode)
}


private func setFloor() {
    // Create floor geometry
    let floorImage = UIImage(named: "FloorBG")!
    let floor = SCNFloor()
    floor.reflectionFalloffEnd = 0
    floor.reflectivity = 0
    floor.firstMaterial!.diffuse.contents = floorImage
    floor.firstMaterial!.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(floorImage.size.width)/2, Float(floorImage.size.height)/2, 1)
    floor.firstMaterial!.locksAmbientWithDiffuse = true
    floor.firstMaterial!.diffuse.wrapS = .Repeat
    floor.firstMaterial!.diffuse.wrapT = .Repeat
    floor.firstMaterial!.diffuse.mipFilter = .Linear

    // Set node & physics
    // -- Must set y-position to 0.5 so blocks are flush with floor
    floorLayer = SCNNode(geometry: floor)
    floorLayer.position.y = -0.5
    let floorShape = SCNPhysicsShape(geometry: floor, options: nil)
    let floorBody = SCNPhysicsBody(type: .Static, shape: floorShape)
    floorLayer.physicsBody = floorBody
    floorLayer.physicsBody!.restitution = 1.0

    // Add to scene
    sceneView.scene!.rootNode.addChildNode(floorLayer)
}


private func setSky() {
    // Create sky geometry
    let sky = SCNFloor()
    sky.reflectionFalloffEnd = 0
    sky.reflectivity = 0
    sky.firstMaterial!.diffuse.contents = SkyColor
    sky.firstMaterial!.doubleSided = true
    sky.firstMaterial!.locksAmbientWithDiffuse = true
    sky.firstMaterial!.diffuse.wrapS = .Repeat
    sky.firstMaterial!.diffuse.wrapT = .Repeat
    sky.firstMaterial!.diffuse.mipFilter = .Linear
    sky.firstMaterial!.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(2), Float(2), 1);

    // Set node & physics
    skyLayer = SCNNode(geometry: sky)
    let skyShape = SCNPhysicsShape(geometry: sky, options: nil)
    let skyBody = SCNPhysicsBody(type: .Static, shape: skyShape)
    skyLayer.physicsBody = skyBody
    skyLayer.physicsBody!.restitution = 1.0

    // Set position
    skyLayer.position = SCNVector3(0, SkyPosY, 0)

    // Set fog
    /*sceneView.scene?.fogEndDistance = 60
     sceneView.scene?.fogStartDistance = 50
     sceneView.scene?.fogDensityExponent = 1.0
     sceneView.scene?.fogColor = SkyColor */

    // Add to scene
    sceneView.scene!.rootNode.addChildNode(skyLayer)
}


func createBlock(position: SCNVector3, animated: Bool) {
  ...

  // Create box geometry
  let box = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
  box.firstMaterial!.diffuse.contents = curStyle.getContents() // "curStyle.getContents()" either returns UIColor or UIImage
  box.firstMaterial!.specular.contents = UIColor.whiteColor()

  // Add new block
  let newBlock = SCNNode(geometry: box)
  newBlock.position = position
  blockLayer.addChildNode(newBlock)
}

截图1-2(我们的应用):

enter image description here enter image description here

截图3-5(块的理想直观表示):

enter image description here enter image description here enter image description here

3 个答案:

答案 0 :(得分:8)

我仍然认为你可以做的一些简单的事情会对场景的渲染方式产生重大影响。抱歉不使用你的代码,这个例子就是我所说的。

现在您的场景仅由环境光照亮。

let aLight = SCNLight()
aLight.type = SCNLightTypeAmbient
aLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
let aLightNode = SCNNode()
aLightNode.light = aLight
scene.rootNode.addChildNode(aLightNode)

如果我在场景中仅使用此灯,我会看到以下内容。请注意,无论面对的方向如何,所有面部都会被点亮。有些游戏确实很好地完善了这种美学。

enter image description here

以下代码块为此场景添加了方向光。在这种情况下应用的变换对于您的场景不会有效,根据您想要光线的来源定位光线非常重要。

let dLight = SCNLight()
dLight.type = SCNLightTypeDirectional
dLight.color = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1.0)

let dLightNode = SCNNode()
dLightNode.light = dLight
var dLightTransform = SCNMatrix4Identity
dLightTransform = SCNMatrix4Rotate(dLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
dLightTransform = SCNMatrix4Rotate(dLightTransform, 37 * Float(M_PI)/180, 0, 0, 1)
dLightTransform = SCNMatrix4Rotate(dLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
dLightNode.transform = dLightTransform
scene.rootNode.addChildNode(dLightNode)

现在我们根据每个面相对于光线方向的角度对每个面进行着色。

enter image description here

目前,如果您使用SCNLightTypeSpot,SceneKit仅支持阴影。使用聚光灯意味着我们需要定向(根据定向光)并定位它。我用这个作为定向灯的替代品。

let sLight = SCNLight()
sLight.castsShadow = true
sLight.type = SCNLightTypeSpot
sLight.zNear = 50
sLight.zFar = 120
sLight.spotInnerAngle = 60
sLight.spotOuterAngle = 90

let sLightNode = SCNNode()
sLightNode.light = sLight
var sLightTransform = SCNMatrix4Identity
sLightTransform = SCNMatrix4Rotate(sLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
sLightTransform = SCNMatrix4Rotate(sLightTransform, 65 * Float(M_PI)/180, 0, 0, 1)
sLightTransform = SCNMatrix4Rotate(sLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
sLightTransform = SCNMatrix4Translate(sLightTransform, -20, 50, -10)
sLightNode.transform = sLightTransform
scene.rootNode.addChildNode(sLightNode)

在上面的代码中,我们首先告诉聚光灯投射阴影,默认情况下,场景中的所有节点都会投射阴影(可以更改)。 zNearzFar设置也很重要,必须指定,以便投射阴影的节点在距光源的此距离范围内。超出此范围的节点不会投射阴影。

enter image description here

在着色/阴影之后,您可以轻松应用许多其他效果。景深效果为available for the camera。雾同样容易包括在内。

scene.fogColor = UIColor.blackColor()
scene.fogStartDistance = 10
scene.fogEndDistance = 110
scenekitView.backgroundColor = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)

enter image description here

<强>更新 事实证明,你可以从定向光线中获得阴影。通过更改其类型并设置orthographicScale来修改上面的聚光灯代码。 orthographicScale的默认值似乎为1.0,显然不适合大于1的场景。

let dLight = SCNLight()
dLight.castsShadow = true
dLight.type = SCNLightTypeDirectional
dLight.zNear = 50
dLight.zFar = 120
dLight.orthographicScale = 30

let dLightNode = SCNNode()
dLightNode.light = dLight
var dLightTransform = SCNMatrix4Identity
dLightTransform = SCNMatrix4Rotate(dLightTransform, -90 * Float(M_PI)/180, 1, 0, 0)
dLightTransform = SCNMatrix4Rotate(dLightTransform, 65 * Float(M_PI)/180, 0, 0, 1)
dLightTransform = SCNMatrix4Rotate(dLightTransform, -20 * Float(M_PI)/180, 0, 1, 0)
dLightTransform = SCNMatrix4Translate(dLightTransform, -20, 50, -10)
dLightNode.transform = dLightTransform
scene.rootNode.addChildNode(dLightNode)

生成以下图像。 Directional light with shadows

场景大小为60x60,因此在这种情况下,将正交比例设置为30会为靠近灯光的对象生成阴影。由于渲染阴影贴图时使用的投影(正交与透视)不同,定向光阴影与聚光灯看起来不同。

答案 1 :(得分:4)

环境遮挡计算将为您提供最佳效果,但价格非常昂贵,尤其是在动态变化的世界中,它看起来像是这样。

有几种方法可以作弊,并获得环境遮挡的外观。

这是一个: 在几何“标语牌”上放置透明的渐变阴影纹理,用于在所需的位置放置/呈现阴影。这将涉及在确定要放置哪些标牌之前检查新块周围的几何形状,以及用于阴影的所需纹理。但这可以看起来非常好,在多边形,绘制调用和滤液方面成本非常低。它可能是最便宜的方式,并且它看起来很好/很棒,并且只能在一个块的世界中真正完成(具有良好的外观)。一个更有机的世界统治着这种技术。请原谅双关语。

或者,另一种类似:将附加纹理放置到具有阴影的对象上/中,并将其与对象中的其他纹理/材质混合。这有点繁琐,而且我不是Scene Kit中材料的专家,所以不能肯定这是可能和/或容易的。

或者:使用纹理混合与顶点着色器,根据您确定阴影的内容和位置以及在多大程度上,根据触摸或以其他方式需要/需要阴影的边缘添加阴影。除非在平面内添加更多顶点以用于阴影的顶点着色,否则仍需要在地板/墙壁上使用标牌技巧。

这是我为朋友的CD封面做的事情......显示了阴影的力量。这是正交的,而不是真正的3D视角,但是阴影给人以深刻的印象并创造出空间的幻想:

enter image description here

答案 2 :(得分:1)

上面(或下面)的所有答案似乎都很好(在撰写本文时),

我使用的(仅用于设置一个简单的场景)是一个环境光(点亮所有方向的所有东西)以使事物可见。
然后一个全向光位于中间某处你的场景,全方位的光可以升起(我意味着Y)来照亮整个场景。全向光为用户提供了阴影感,环境光使其更像阳光。

例如:

想象一下,坐在客厅里(就像我现在一样),阳光照在你右边的窗户旁边。 你显然可以看到一个区域的阴影,沙发没有阳光,但你仍然可以看到阴影中的细节。



现在!突然之间,你的wold摆脱了环境光芒!阴影现在变成黑色,你不能再看到阴影中的东西了。

或者说环境光再次回来(这是一种解脱),但突然之间全向光停止了工作。 (可能是我的错:()现在一切都点亮了,没有阴影,没有区别,但是如果你在桌子上放一张纸,从上面看它,就没有阴影!所以你认为它是一部分桌子!在这样的世界里,你依靠东西的轮廓来看它 - 你必须从侧视图看桌子,看看纸张的厚度。
lighting example


希望这有助于(至少一点点)

注意: ambient lighting emissive material

产生类似的效果