如何修改代码消除SceneKit中3D挤压UIBezierPath的重复对象
问题描述
我将一系列UIBezierPath转换为SceneKit中的3D对象,它们构成一条带有间隙的不规则长折线(间隙用于分隔不同对象)。
正常效果:
问题:移动相机时,对象侧面的材质会出现异常闪烁和颜色变化。
推测原因:路径绘制代码存在错误,生成了位于现有对象内部的重复对象,闪烁的颜色实际是重复对象透显导致的。
闪烁效果:
可复现代码(粘贴到Xcode Game模板项目的GameViewController的viewDidLoad方法末尾):
var previousBezierPathPoint: CGPoint = CGPoint.zero let numOfPointsPerLine: Int = 8 var hugePath = UIBezierPath() var wasGap: Bool = false let gapWidth: CGFloat = 10.0 var currentZdepth: CGFloat = CGFloat.random(in: 5.0...30.0) for i in 0..<14 { let pp = wasGap ? CGPoint(x: CGFloat(previousBezierPathPoint.x) + gapWidth, y: CGFloat(previousBezierPathPoint.y)) : CGPoint(x: CGFloat(previousBezierPathPoint.x), y: CGFloat(previousBezierPathPoint.y)) let isGap: Bool = i > 1 && Float.random(in: 0...100) > 60.0 if !isGap { if wasGap || i == 0 { hugePath = UIBezierPath() hugePath.move(to: pp) currentZdepth = CGFloat.random(in: 5.0...30.0) } for j in 1..<numOfPointsPerLine { let point = CGPoint(x: pp.x + (CGFloat(j)*25.0), y: pp.y + CGFloat.random(in: -8.0...8.0)) hugePath.addLine(to: point) previousBezierPathPoint = point } let pathRef = hugePath.cgPath.copy(strokingWithWidth: 10.0, lineCap: CGLineCap.butt, lineJoin: CGLineJoin.miter, miterLimit: 1.0) let newPath = UIBezierPath(cgPath: pathRef.normalized()) let hugeShape = SCNShape(path: newPath, extrusionDepth: currentZdepth) let colors = [ UIColor.green, UIColor.yellow, UIColor.purple, UIColor.gray, UIColor.darkGray ] let tempMat = SCNMaterial() tempMat.diffuse.contents = colors[Int.random(in: 0..<colors.count)] let frontMat = SCNMaterial() frontMat.diffuse.contents = UIColor.red hugeShape.materials = [frontMat, tempMat, tempMat, tempMat, tempMat, tempMat] let hugeNode = SCNNode() hugeNode.geometry = hugeShape hugeNode.position.x = 0.0 hugeNode.position.z = Float(-50.0 + (currentZdepth*0.5)) hugeNode.position.y = 0.0 scnView.scene?.rootNode.addChildNode(hugeNode) } else { if hugePath.isEmpty == false { hugePath.close() } } wasGap = isGap }
核心疑问:如何修改代码以消除重复对象?
解决方案
问题根源在于:每次非间隙循环时,路径的起始基准点逻辑错误,导致新生成的路径与上一段路径重叠;同时未及时重置路径,使得旧路径被重复复用生成重叠的3D对象,相机移动时因深度测试冲突出现闪烁。
修改后的完整代码:
var previousBezierPathPoint: CGPoint = CGPoint.zero let numOfPointsPerLine: Int = 8 var hugePath = UIBezierPath() var wasGap: Bool = false let gapWidth: CGFloat = 10.0 var currentZdepth: CGFloat = CGFloat.random(in: 5.0...30.0) // 新增变量记录当前段的起始点 var currentSegmentStartPoint: CGPoint = CGPoint.zero for i in 0..<14 { let isGap: Bool = i > 1 && Float.random(in: 0...100) > 60.0 if !isGap { if wasGap || i == 0 { // 新段开始时,计算正确的起始点 let startX = wasGap ? previousBezierPathPoint.x + gapWidth : previousBezierPathPoint.x currentSegmentStartPoint = CGPoint(x: startX, y: previousBezierPathPoint.y) hugePath = UIBezierPath() hugePath.move(to: currentSegmentStartPoint) currentZdepth = CGFloat.random(in: 5.0...30.0) } // 基于当前段的起始点生成路径,避免与旧路径重叠 for j in 1..<numOfPointsPerLine { let point = CGPoint(x: currentSegmentStartPoint.x + (CGFloat(j)*25.0), y: currentSegmentStartPoint.y + CGFloat.random(in: -8.0...8.0)) hugePath.addLine(to: point) previousBezierPathPoint = point } let pathRef = hugePath.cgPath.copy(strokingWithWidth: 10.0, lineCap: CGLineCap.butt, lineJoin: CGLineJoin.miter, miterLimit: 1.0) let newPath = UIBezierPath(cgPath: pathRef.normalized()) let hugeShape = SCNShape(path: newPath, extrusionDepth: currentZdepth) let colors = [ UIColor.green, UIColor.yellow, UIColor.purple, UIColor.gray, UIColor.darkGray ] let tempMat = SCNMaterial() tempMat.diffuse.contents = colors[Int.random(in: 0..<colors.count)] let frontMat = SCNMaterial() frontMat.diffuse.contents = UIColor.red hugeShape.materials = [frontMat, tempMat, tempMat, tempMat, tempMat, tempMat] let hugeNode = SCNNode() hugeNode.geometry = hugeShape hugeNode.position.x = 0.0 hugeNode.position.z = Float(-50.0 + (currentZdepth*0.5)) hugeNode.position.y = 0.0 scnView.scene?.rootNode.addChildNode(hugeNode) // 生成节点后重置路径,避免后续复用旧路径 hugePath = UIBezierPath() } else { if hugePath.isEmpty == false { hugePath.close() } } wasGap = isGap }
修改说明
- 新增
currentSegmentStartPoint变量,专门记录每一段新路径的起始位置,彻底避免基于旧基准点生成重叠路径。 - 移除原代码中冗余的
pp变量,直接通过previousBezierPathPoint计算新段的起始点,简化逻辑。 - 每次生成并添加3D节点后,立即重置
hugePath为空路径,确保下一段路径不会追加到旧路径上,从根源消除重复对象的生成。
内容的提问来源于stack exchange,提问作者West1
相关产品推荐
相关产品推荐

