ARKit教程

ARKit教程12_第九章:几何,纹理和灯光

2019-08-16  本文已影响3人  张芳涛

前言

ARKit感兴趣的同学,可以订阅ARKit教程专题
源代码地址在这里

正文

这一章,我们会想办法创建一个逼真的房子:有墙友地板还有天花板的那种。

相关概念介绍

SceneKit坐标系统

SceneKit可用于向视图添加虚拟3D对象。 SceneKit内容视图由节点的分层树结构组成,也称为场景图。一个场景由根节点和其他节点组成,根节点定义场景世界的坐标空间,其他节点用可见内容填充世界。在屏幕上呈现的每个节点或3D对象都是SCNNode类型的对象。 SCNNode对象定义了相对于其父节点的坐标空间变换(位置,方向和比例)。其实它本身没有任何可见内容。

场景中的rootNode对象定义了SceneKit渲染的世界的坐标系。添加到此根节点的每个子节点都会创建自己的坐标系,而该坐标系又由其自己的子节点继承。

SceneKit使用右手坐标系,其中(默认情况下)视图方向沿负Z轴,如下图所示:

SCNNode对象的位置是使用SCNVector3定义的,SCNVector3将其定位在其父级的坐标系内。默认位置是零向量,表示该节点位于父节点坐标系的原点。在这种情况下,SCNVector3是一个三分量向量,其中每个分量都是一个Float类型的数值,表示每个轴上的坐标。

SCNNode对象的方向 - 表示为俯仰,偏航和滚转角度 - 由其eulerAngles属性定义。这也由SCNVector3结构表示,其中每个矢量分量是以弧度表示的角度。

纹理

SCNNode对象本身没有任何可见内容。通过将SCNGeometry对象附加到节点,可以将2D3D对象添加到场景中。几何图形附加了确定其外观的SCNMaterial对象。

SCNMaterial具有多个可视属性。每个可视属性都是SCNMaterialProperty类的一个实例,它提供纯色,纹理或其他2D内容。基本着色,基于物理的着色以及可用于使材质看起来更逼真的特殊效果有多种视觉属性。

使用SceneKit,还可以使用附加了SCNLight对象的节点来遮挡场景中具有光照和阴影效果的几何体。

创建门户

让我们直接创建门户的内容。打开SCNNodeHelpers.swift文件并添加以下内容。

let surfaceLength: CGFloat = 3.0
let surfaceHeight: CGFloat = 0.1
let surfaceWidth: CGFloat = 3.0

let scaleX: Float = 2.0
let scaleY: Float = 2.0

let wallWidth: CGFloat = 0.1
let wallHeight: CGFloat = 3.0
let wallLength: CGFloat = 3.0

上面的代码作用如下:

之后我们再添加如下代码:

func repeatTextures(geometry: SCNGeometry, scaleX: Float, scaleY: Float){
let firstMaterial = geometry.firstMaterial
let modeRepeat = SCNWrapMode.repeat
firstMaterial?.diffuse.wrapS = modeRepeat
firstMaterial?.selfIllumination.wrapS = modeRepeat
firstMaterial?.normal.wrapS = modeRepeat
firstMaterial?.specular.wrapS = modeRepeat
firstMaterial?.emission.wrapS = modeRepeat
firstMaterial?.roughness.wrapS = modeRepeat

firstMaterial?.diffuse.wrapT = modeRepeat
firstMaterial?.selfIllumination.wrapT = modeRepeat
firstMaterial?.normal.wrapT = modeRepeat
firstMaterial?.specular.wrapT = modeRepeat
firstMaterial?.emission.wrapT = modeRepeat
firstMaterial?.roughness.wrapT = modeRepeat

firstMaterial?.diffuse.contentsTransform = SCNMatrix4MakeScale(scaleX, scaleY, 0)
firstMaterial?.selfIllumination.contentsTransform = SCNMatrix4MakeScale(scaleX, scaleY, 0)
firstMaterial?.normal.contentsTransform = SCNMatrix4MakeScale(scaleX, scaleY, 0)
firstMaterial?.specular.contentsTransform = SCNMatrix4MakeScale(scaleX, scaleY, 0)
firstMaterial?.emission.contentsTransform = SCNMatrix4MakeScale(scaleX, scaleY, 0)
firstMaterial?.roughness.contentsTransform = SCNMatrix4MakeScale(scaleX, scaleY, 0)
}

这定义了一种在XY维度上在表面上重复纹理图像的方法。

上面的代码作用如下:

我们只想在用户进入门户时显示floor(地板)和ceiling(天花板)节点;任何其他时间,你需要隐藏它们。要实现此功能,需要在SCNNodeHelpers文件中添加如下代码:

func makeOuterSurfaceNode(width: CGFloat, height: CGFloat, length: CGFloat) -> SCNNode{
    let outerSurface = SCNBox(width: surfaceWidth, height: surfaceHeight, length: surfaceLength, chamferRadius: 0)
    outerSurface.firstMaterial?.diffuse.contents = UIColor.white
    outerSurface.firstMaterial?.transparency = 0.000001
    let outerSurfaceNode = SCNNode(geometry: outerSurface)
   outerSurfaceNode.renderingOrder = 10
    return outerSurfaceNode
}

上面的代码作用如下:

创建地板节点的代码如下:

func makeFloorNode() -> SCNNode{
    let outerFloorNode = makeOuterSurfaceNode(width: surfaceWidth, height: surfaceHeight, length: surfaceLength)
    outerFloorNode.position = SCNVector3(surfaceHeight * 0.5, -surfaceHeight, 0)

    let floorNode = SCNNode()
    floorNode.addChildNode(outerFloorNode)

    let innerFloor = SCNBox(width: surfaceWidth, height: surfaceHeight, length: surfaceLength, chamferRadius: 0)
    innerFloor.firstMaterial?.lightingModel = .physicallyBased
    innerFloor.firstMaterial?.diffuse.contents = UIImage(named: "ARResource.scnassets/floor/textures/FloorDiffuse.png")
    innerFloor.firstMaterial?.normal.contents = UIImage(named: "ARResource.scnassets/floor/textures/FloorNormal.png")
    innerFloor.firstMaterial?.roughness.contents = UIImage(named: "ARResource.scnassets/floor/textures/FloorRoughness.png")
    innerFloor.firstMaterial?.specular.contents = UIImage(named: "ARResource.scnassets/floor/textures/FloorSpecular.png")
    innerFloor.firstMaterial?.selfIllumination.contents = UIImage(named: "ARResource.scnassets/floor/textures/FloorGloss.png")

    repeatTextures(geometry: innerFloor, scaleX: scaleX, scaleY: scaleY)

    let innerFloorNode = SCNNode(geometry: innerFloor)
    innerFloorNode.renderingOrder = 100
    innerFloorNode.position = SCNVector3(surfaceHeight * 0.5, 0, 0)
    floorNode.addChildNode(innerFloorNode)
    return floorNode
   }

上面的代码作用如下:

打开ViewController.swift并且添加如下代码:

let positionY: CGFloat = -WALL_HEIGHT*0.5 
let positionZ: CGFloat = -SURFACE_LENGTH*0.5

这些常数表示YZ维度中节点的位置偏移。通过替换makePortal()floor节点添加到门户中。

func makePortal() -> SCNNode { 
    // 1 
    let portal = SCNNode()

    // 2 
    let floorNode = makeFloorNode() 
    floorNode.position = SCNVector3(0, POSITION_Y, POSITION_Z)

    // 3 
    portal.addChildNode(floorNode) 
    return portal

}

上面的代码作用如下:

运行程序效果如下:

你会注意到floor节点是黑暗的。那是因为我们还没有添加光源。

打开SCNNodeHelpers.swift,添加如下代码:

func makeCeilingNode() -> SCNNode{
    let outerCeilingNode = makeOuterSurfaceNode(width: surfaceWidth, height: surfaceHeight, length: surfaceLength)
    outerCeilingNode.position = SCNVector3(surfaceWidth * 0.5, surfaceHeight, 0)
    let ceilingNode = SCNNode()
    ceilingNode.addChildNode(outerCeilingNode)
    let innerCeiling = SCNBox(width: surfaceWidth, height: surfaceHeight, length: surfaceLength, chamferRadius: 0)
    innerCeiling.firstMaterial?.lightingModel = .physicallyBased
    innerCeiling.firstMaterial?.diffuse.contents = UIImage(named: "ARResource.scnassets/ceiling/textures/CeilingDiffuse.png")
    innerCeiling.firstMaterial?.emission.contents = UIImage(named: "ARResource.scnassets/ceiling/textures/CeilingEmis.png")
   innerCeiling.firstMaterial?.normal.contents = UIImage(named: "ARResource.scnassets/ceiling/textures/CeilingNormal.png")
    innerCeiling.firstMaterial?.specular.contents = UIImage(named: "ARResource.scnassets/ceiling/textures/CeilingSpecular.png")
    innerCeiling.firstMaterial?.selfIllumination.contents = UIImage(named: "ARResource.scnassets/ceiling/textures/CeilingGloss.png")
    repeatTextures(geometry: innerCeiling, scaleX: scaleX, scaleY: scaleY)

    let innerCeilingNode = SCNNode(geometry: innerCeiling)
    innerCeilingNode.renderingOrder = 100
    innerCeilingNode.position = SCNVector3(surfaceHeight * 0.5, 0, 0)
    ceilingNode.addChildNode(innerCeilingNode)
    return ceilingNode
}

上面的代码作用如下:

现在从某个地方调用它。打开PortalViewController.swift并在return语句之前将以下代码块添加到makePortal():

// 1 
let ceilingNode = makeCeilingNode() 
ceilingNode.position = SCNVector3(0, POSITION_Y+WALL_HEIGHT, POSITION_Z) 
// 2 
portal.addChildNode(ceilingNode)

上面的代码作用如下:

运行程序,显示效果如下:

往下看是地板,上面是天花板,就差墙了。接下来,砌墙!

打开SCNNodeHelpers.swift文件,添加以下代码:

func makeWallNode(length: CGFloat = wallLength, height: CGFloat = wallHeight, maskLowerSide: Bool = false) -> SCNNode{
    let outerWall = SCNBox(width: wallWidth, height: height, length: length, chamferRadius: 0)
    outerWall.firstMaterial?.diffuse.contents = UIColor.white
    outerWall.firstMaterial?.transparency = 0.000001
    let outerWallNode = SCNNode(geometry: outerWall)
    let multiplier: CGFloat = maskLowerSide ? -1 : 1
outerWallNode.position = SCNVector3(wallWidth * multiplier, 0, 0)
    outerWallNode.renderingOrder = 10
    let wallNode = SCNNode()
    wallNode.addChildNode(outerWallNode)
    let innerWall = SCNBox(width: wallWidth, height: height, length: length, chamferRadius: 0)
    innerWall.firstMaterial?.lightingModel = .physicallyBased
    innerWall.firstMaterial?.diffuse.contents = UIImage(named: "ARResource.scnassets/wall/textures/WallsDiffuse.png")
    innerWall.firstMaterial?.metalness.contents = UIImage(named: "ARResource.scnassets/wall/textures/WallsMetalness.png")
    innerWall.firstMaterial?.roughness.contents = UIImage(named: "ARResource.scnassets/wall/textures/WallsRoughness.png")
    innerWall.firstMaterial?.normal.contents = UIImage(named: "ARResource.scnassets/wall/textures/WallsNormal.png")
    innerWall.firstMaterial?.specular.contents = UIImage(named: "ARResource.scnassets/wall/textures/WallsSpec.png")
    innerWall.firstMaterial?.selfIllumination.contents = UIImage(named: "ARResource.scnassets/wall/textures/WallsGloss.png")
    let innerWallNode = SCNNode(geometry: innerWall)
    innerWallNode.renderingOrder = 100
    wallNode.addChildNode(innerWallNode)
    return wallNode
}

上面的代码作用如下:

现在,打开ViewController.swift,之后在makePortal()函数中添加如下代码:

现在只有一面墙,我们需要把其他几面墙也添加上:

    let rightSideWallNode = makeWallNode(maskLowerSide: true)
    rightSideWallNode.eulerAngles = SCNVector3(0, 180.0.degreeToRadians, 0)
    rightSideWallNode.position = SCNVector3(wallLength*0.5,
                                            positionY+wallHeight*0.5,
                                            positionZ)
    portal.addChildNode(rightSideWallNode)
    
    let leftSideWallNode = makeWallNode(maskLowerSide: true)
    leftSideWallNode.position = SCNVector3(-wallLength*0.5,
                                           positionY+wallHeight*0.5,
                                           positionZ)
    portal.addChildNode(leftSideWallNode)

上面代码作用如下:

运行程序,效果如下:

添加门口

现在三面墙都有了,最后一面墙应该留出来点空间,充当一个门。我们需要先定义一个门的宽度和高度:

let doorWidth: CGFloat = 1.0
let doorHeight: CGFloat = 2.4

然后添加一个addDoorway(node: SCNNode)方法:

func addDoorway(node: SCNNode){
    let halfWalllength: CGFloat = wallLength * 0.5
    let frontHalfWallLength: CGFloat = (wallLength - doorWidth) * 0.5
    let rightDoorSideNode = makeWallNode(length: frontHalfWallLength)
    rightDoorSideNode.eulerAngles = SCNVector3(0, 270.0.degreeToRadians, 0)
    rightDoorSideNode.position = SCNVector3(halfWalllength - 0.5 * doorWidth, positionY + wallLength * 0.5, positionZ + surfaceLength * 0.5)
    node.addChildNode(rightDoorSideNode)
    
    let leftDoorSideNode = makeWallNode(length: frontHalfWallLength)
    leftDoorSideNode.eulerAngles = SCNVector3(0, 270.0.degreeToRadians, 0)
    leftDoorSideNode.position = SCNVector3(-halfWalllength + 0.5 * frontHalfWallLength, positionY + wallHeight * 0.5, positionZ + surfaceLength * 0.5)
    node.addChildNode(leftDoorSideNode)
    
    let aboveDoorNode = makeWallNode(length: doorWidth, height: wallHeight - doorHeight)
    aboveDoorNode.eulerAngles = SCNVector3(0, 270.0.degreeToRadians, 0)
    aboveDoorNode.position = SCNVector3(0, positionY + (wallHeight - doorHeight) * 0.5 + doorHeight, positionZ + surfaceLength * 0.5)
    node.addChildNode(aboveDoorNode)
}

上面的代码作用如下:

之后在makePortal()中添加下面的代码:

addDoorway(node: portal)

构建并运行应用程序。你会看到门口的门口,但门的顶部正在接触天花板。我们需要添加另一块墙,以使门口跨越预先设定的doorHeight

我们在addDoorway(node:)函数中添加如下代码:

let aboveDoorNode = makeWallNode(length: doorWidth, height: wallHeight - doorHeight)
aboveDoorNode.eulerAngles = SCNVector3(0, 270.0.degreeToRadians, 0)
aboveDoorNode.position = SCNVector3(0, positionY + (wallHeight - doorHeight) * 0.5 + doorHeight, positionZ + surfaceLength * 0.5)
node.addChildNode(aboveDoorNode)

上面的代码作用如下:

运行程序效果如下:

添加灯光效果

添加灯光代码如下:

func placeLightSource(rootNode: SCNNode){
    let light = SCNLight()
    light.intensity = 10
    light.type = .omni
    let lightNode = SCNNode()
    lightNode.light = light
    lightNode.position = SCNVector3(0, positionY + wallHeight, positionZ)
    rootNode.addChildNode(lightNode)
}

上面的代码作用如下:

makePortal()方法中添加如下代码:

placeLightSource(rootNode: portal)

运行程序,效果如下:

这下灯光也有了😋。

上一章 目录 下一章
上一篇下一篇

猜你喜欢

热点阅读