You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何修改SceneKit中绘制线条的粗细?

如何修改SceneKit中绘制线条的粗细?

嘿,这个问题我太熟悉啦!SceneKit里用原生SCNGeometryElement创建的线条,确实没法直接调整粗细——这是因为它依赖底层的OpenGL/Metal原生线渲染,默认线宽通常是1,而且很多平台(比如iOS)根本不支持修改原生线宽。不过别担心,我们有几个靠谱的替代方案能实现你要的效果:

方案1:用几何体模拟粗线条(最推荐,兼容性拉满)

这个方法是用一个薄的长方体(或平面)来代替线条,通过调整几何体的宽度来控制线条粗细,在所有SceneKit支持的平台上都能正常工作。

修改你的lineFrom函数,换成下面的实现:

private func thickLine(from vector1: SCNVector3, to vector2: SCNVector3, lineWidth: CGFloat = 0.05) -> SCNGeometry {
    // 计算两点间的方向向量和线条长度
    let direction = vector2 - vector1
    let length = direction.length()
    
    // 创建一个长方体:宽度=线粗,高度=线粗(保证是“线”而不是面),长度=两点间距
    let lineBox = SCNBox(width: lineWidth, height: lineWidth, length: length, chamferRadius: 0)
    
    // 计算旋转和位移,让长方体精准对齐两点的连线
    // 先把长方体的中心点移到起点,再旋转朝向终点
    let pivot = SCNMatrix4MakeTranslation(0, 0, -length/2)
    let lookAtMatrix = SCNMatrix4LookAt(vector1, vector2, SCNVector3(0, 1, 0))
    lineBox.transform = SCNMatrix4Mult(lookAtMatrix, pivot)
    
    return lineBox
}

使用的时候,直接调用thickLine(from:to:lineWidth:),传入你需要的线粗数值就行,比如lineWidth: 0.1就能得到更粗的线条。

方案2:用Shader Modifier自定义线渲染(适合线框风格需求)

如果需要保留原生线框的风格,可以尝试用Shader Modifier来扩展线条,但这个方法有局限性:iOS上的Metal默认不支持修改原生线宽,需要在片段着色器里做额外处理,macOS上相对容易实现。

示例代码如下:

private func customLine(from vector1: SCNVector3, to vector2: SCNVector3, lineWidth: CGFloat = 2.0) -> SCNGeometry {
    let indices: [Int32] = [0, 1]
    let source = SCNGeometrySource(vertices: [vector1, vector2])
    let element = SCNGeometryElement(indices: indices, primitiveType: .line)
    let lineGeometry = SCNGeometry(sources: [source], elements: [element])
    
    // 添加Shader Modifier来模拟粗线(这里是片段着色器的实现思路)
    let fragmentShader = """
    #pragma body
    // 通过扩展线条的像素范围来模拟粗线
    float width = \(lineWidth);
    vec2 dFdx = fwidth(gl_FragCoord.xy);
    vec2 dFdy = fwidth(gl_FragCoord.xy);
    float lineEdge = smoothstep(width - 1.0, width, length(vec2(dFdx, dFdy)));
    gl_FragColor.a *= lineEdge;
    """
    lineGeometry.shaderModifiers = [.fragment: fragmentShader]
    
    return lineGeometry
}

注意:这个方法的效果可能因平台和设备而异,需要根据实际场景调整Shader代码。

方案3:用贝塞尔曲线转SCNShape(适合2D转3D的线条)

如果你的线条主要在某个平面内,可以用UIBezierPath绘制2D线条,再转成SCNShape来设置线宽:

private func bezierLine(from vector1: SCNVector3, to vector2: SCNVector3, lineWidth: CGFloat = 0.1) -> SCNGeometry {
    // 将3D点转换为2D贝塞尔路径(这里假设在XY平面,根据你的坐标系调整)
    let path = UIBezierPath()
    path.move(to: CGPoint(x: vector1.x, y: vector1.y))
    path.addLine(to: CGPoint(x: vector2.x, y: vector2.y))
    
    // 创建SCNShape,设置线宽和厚度(厚度越小越像线条)
    let shape = SCNShape(path: path, extrusionDepth: 0.01)
    shape.lineWidth = lineWidth
    
    // 将形状移动到两点的中间位置
    shape.position = SCNVector3(
        (vector1.x + vector2.x)/2,
        (vector1.y + vector2.y)/2,
        (vector1.z + vector2.z)/2
    )
    
    return shape
}

总的来说,方案1是最稳妥的选择,不仅兼容性好,还能轻松调整线条的颜色、材质等属性,完全满足大多数场景的需求。

内容的提问来源于stack exchange,提问作者함혜진

火山引擎 最新活动