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

ARKit SceneView中如何增加线条厚度并实现虚线效果?

解决方案:ARKit中实现加粗线条与虚线样式

我之前也踩过这个坑!glLineWidth()在SceneKit里基本起不到作用——一方面iOS默认用Metal渲染器,OpenGL的API不兼容;另一方面就算用OpenGL ES,它也是全局设置,没法单独控制某条线条的宽度,还受硬件限制(很多设备最大只能支持到10左右)。下面给你两种需求的可靠实现方案:

一、实现加粗线条:用几何体模拟

最稳妥的方式是用**SCNCylinder(圆柱体)**或者SCNPlane(平面)来模拟粗线条,这样不仅能自由控制宽度,还能在3D空间里正确显示。这里推荐用圆柱体,适合ARKit的3D场景:

代码实现:

func createThickLine(from start: SCNVector3, to end: SCNVector3, lineWidth: CGFloat = 0.01) -> SCNNode {
    // 计算两点间的向量和长度
    let vector = end - start
    let lineLength = vector.length()
    
    // 创建圆柱体:半径是线条宽度的一半,高度是两点间距离
    let cylinder = SCNCylinder(radius: lineWidth / 2, height: lineLength)
    cylinder.radialSegmentCount = 16 // 数值越高,圆柱越平滑(按需调整)
    
    // 创建线条节点
    let lineNode = SCNNode(geometry: cylinder)
    
    // 将节点定位在两点的中点
    lineNode.position = SCNVector3(
        (start.x + end.x) / 2,
        (start.y + end.y) / 2,
        (start.z + end.z) / 2
    )
    
    // 旋转圆柱,使其对齐两点的方向
    let direction = vector.normalized()
    let upVector = SCNVector3(0, 1, 0)
    let rotationAxis = upVector.cross(direction)
    let rotationAngle = acos(upVector.dot(direction))
    
    lineNode.transform = SCNMatrix4MakeRotation(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z)
    
    return lineNode
}

// 给SCNVector3扩展必要的计算方法
extension SCNVector3 {
    // 计算向量长度
    func length() -> CGFloat {
        return CGFloat(sqrt(x*x + y*y + z*z))
    }
    
    // 归一化向量(转为单位向量)
    func normalized() -> SCNVector3 {
        let len = length()
        return SCNVector3(x/CGFloat(len), y/CGFloat(len), z/CGFloat(len))
    }
    
    // 向量减法
    static func -(lhs: SCNVector3, rhs: SCNVector3) -> SCNVector3 {
        return SCNVector3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z)
    }
    
    // 叉乘(计算旋转轴)
    func cross(_ vector: SCNVector3) -> SCNVector3 {
        return SCNVector3(
            y * vector.z - z * vector.y,
            z * vector.x - x * vector.z,
            x * vector.y - y * vector.x
        )
    }
    
    // 点乘(计算旋转角度)
    func dot(_ vector: SCNVector3) -> Float {
        return x * vector.x + y * vector.y + z * vector.z
    }
}

使用时直接调用createThickLine(from: pos1, to: pos2, lineWidth: 0.02)就能得到你想要的粗线条了。

二、实现虚线样式

有两种常用方案,按需选择:

方案1:用纹理图片实现(简单易上手)

创建一张虚线纹理图,然后将其贴到我们刚才的圆柱体上,通过调整纹理重复参数实现虚线效果:

func createDashedLineWithTexture(from start: SCNVector3, to end: SCNVector3, lineWidth: CGFloat = 0.01, dashLength: CGFloat = 0.1, gapLength: CGFloat = 0.05) -> SCNNode {
    let lineNode = createThickLine(from: start, to: end, lineWidth: lineWidth)
    
    // 生成虚线纹理(也可以直接用本地图片)
    let textureSize = CGSize(width: Int((dashLength + gapLength) * 10), height: Int(lineWidth * 1000))
    UIGraphicsBeginImageContext(textureSize)
    defer { UIGraphicsEndImageContext() }
    
    let context = UIGraphicsGetCurrentContext()!
    // 透明背景
    context.setFillColor(UIColor.clear.cgColor)
    context.fill(CGRect(origin: .zero, size: textureSize))
    
    // 绘制虚线片段
    context.setStrokeColor(UIColor.black.cgColor)
    context.setLineWidth(lineWidth * 1000)
    context.move(to: CGPoint(x: 0, y: textureSize.height/2))
    context.addLine(to: CGPoint(x: dashLength * 10, y: textureSize.height/2))
    context.strokePath()
    
    let dashedImage = UIGraphicsGetImageFromCurrentImageContext()!
    
    // 设置材质
    let material = SCNMaterial()
    material.diffuse.contents = dashedImage
    // X方向重复纹理,Y方向固定
    material.diffuse.wrapS = .repeat
    material.diffuse.wrapT = .clamp
    // 调整纹理缩放,让虚线沿线条长度正确重复
    let lineLength = (end - start).length()
    material.diffuse.contentsTransform = SCNMatrix4MakeScale(Float(lineLength / (dashLength + gapLength)), 1, 1)
    
    lineNode.geometry?.materials = [material]
    
    return lineNode
}

方案2:用自定义着色器实现(灵活可控)

如果需要动态调整虚线的长度、间距,或者不想依赖图片,可以用SceneKit的ShaderModifier来实现:

func createDashedLineWithShader(from start: SCNVector3, to end: SCNVector3, lineWidth: CGFloat = 0.01, dashLength: Float = 0.1, gapLength: Float = 0.05) -> SCNNode {
    let lineNode = createThickLine(from: start, to: end, lineWidth: lineWidth)
    
    // 片段着色器修改器:计算虚线的透明度
    let shaderModifier = """
    #pragma transparent
    varying vec2 v_texCoord;
    
    void main() {
        float dashTotal = \(dashLength) + \(gapLength);
        float position = v_texCoord.x * dashTotal;
        // 计算当前位置是否在虚线片段内
        float alpha = step(fract(position / dashTotal), \(dashLength)/dashTotal);
        gl_FragColor = vec4(gl_FragColor.rgb, alpha);
    }
    """
    
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.black
    material.shaderModifiers = [.fragment: shaderModifier]
    // 开启透明混合,确保虚线间隙透明
    material.isDoubleSided = true
    material.blendMode = .alpha
    
    lineNode.geometry?.materials = [material]
    
    // 给圆柱体添加纹理坐标,让着色器能获取沿线条长度的位置信息
    let cylinder = lineNode.geometry as! SCNCylinder
    let texCoords = [
        SCNVector2(0, 0), SCNVector2(1, 0),
        SCNVector2(0, 1), SCNVector2(1, 1)
    ]
    let texSource = SCNGeometrySource(textureCoordinates: texCoords)
    cylinder.sources = cylinder.sources + [texSource]
    
    return lineNode
}

总结

  • 放弃glLineWidth(),用几何体模拟粗线条是最可靠的方案,适配所有SceneKit渲染环境。
  • 虚线样式可以根据需求选择纹理或着色器方案:纹理适合静态场景,着色器适合需要动态调整参数的场景。

内容的提问来源于stack exchange,提问作者Math_Enthusiast

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 09:07:15