You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修改代码消除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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 00:16:08