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

Swift SceneView中如何增加线框/线条的厚度?

在Swift的SceneView中如何增加线框/线条的厚度?

在Swift中使用SceneView时,我想要增加线框/线条的厚度,但查阅Apple文档后,没找到任何和线框lineWidth或厚度相关的属性。我尝试用MetalMaterial和自定义Shader来实现,但代码抛出多个错误,至今没有效果。

我的SceneView示例代码如下:

// Create an array of SCNVector3 representing the points
var points = [SCNVector3]()

for vert in mesh.verticies
{
    if let vert = vert as? Point3D
    {
        points.append(SCNVector3(vert.x, vert.y, vert.z))
    }
}

// Create an array of UInt32 representing the face indices
var faceIndices = [UInt32]()

for face in mesh.faces
{
    if let face = face as? Face3D
    {
        faceIndices.append(UInt32(face.a))
        faceIndices.append(UInt32(face.b))
        faceIndices.append(UInt32(face.c))
    }
}

// Create a geometry source for the points
let pointsSource = SCNGeometrySource(vertices: points)

// Create a geometry element for the faces
let faceElement = SCNGeometryElement(indices: faceIndices, primitiveType: .triangles)

let geometry = SCNGeometry(sources: [pointsSource], elements: [faceElement])

let node = SCNNode(geometry: geometry)

node.name = "DebugNode"
let material = SCNMaterial()
material.isDoubleSided = true // Disable backface culling

// Set the material on your mesh
node.geometry?.materials = [material]
node.geometry?.firstMaterial?.diffuse.contents = mesh3D.colour;
node.geometry?.firstMaterial?.fillMode = .fill

我尝试了以下MetalMaterial代码,但抛出多个错误:

metalMaterial.shaderModifiers = [.geometry: """
    #pragma arguments
    float lineThickness;

    #pragma body
    _geometry.position = _geometry.position;
    _geometry.lineWidth = lineThickness;
"""
]

如前所述,我在SCNNode、SCNGeometry或SCNGeometryElement上找不到任何可直接设置的相关属性。


可行的解决方案

SceneKit原生并不支持直接修改线框模式下的线条厚度,因为底层渲染框架(OpenGL/Metal)的原生线框渲染线宽受平台限制(比如多数平台仅支持1像素线宽)。可以通过以下两种方式实现类似效果:

1. 生成边缘几何体(最可靠)

放弃原生线框,手动为网格的每条边缘创建细圆柱体或长方体,这样可以完全控制线条的厚度、颜色等属性:

// 示例:为两个点之间创建一个圆柱体线条
func createLineNode(from start: SCNVector3, to end: SCNVector3, radius: CGFloat, color: UIColor) -> SCNNode {
    let direction = end - start
    let length = direction.length()
    let lineGeometry = SCNCylinder(radius: radius, height: length)
    lineGeometry.firstMaterial?.diffuse.contents = color
    
    let lineNode = SCNNode(geometry: lineGeometry)
    // 旋转和定位圆柱体到两点连线
    lineNode.position = SCNVector3((start.x + end.x)/2, (start.y + end.y)/2, (start.z + end.z)/2)
    lineNode.eulerAngles = SCNVector3(
        Float.pi/2,
        atan2(direction.z, direction.x),
        atan2(direction.y, direction.x)
    )
    return lineNode
}

// 遍历网格的所有边缘,生成线条节点
// 需要注意去重边缘,避免重复创建

2. 使用自定义Metal着色器实现宽线框

原生Shader修改无法直接设置lineWidth,需要在片元着色器中通过重心坐标模拟宽线框效果:

let material = SCNMaterial()
material.shaderModifiers = [
    .fragment: """
    #pragma arguments
    float lineWidth;
    float lineSmooth;
    vec4 lineColor;

    #pragma body
    // 获取片元在三角形内的重心坐标
    vec3 barycentric = v_barycentricCoord;
    vec3 d = fwidth(barycentric);
    vec3 a3 = smoothstep(d * (lineWidth - lineSmooth), d * (lineWidth + lineSmooth), barycentric);
    float edgeFactor = min(min(a3.x, a3.y), a3.z);
    
    // 混合线框颜色和原颜色
    _output.color = mix(lineColor, _output.color, edgeFactor);
    """
]
// 设置参数
material.setValue(2.0, forKey: "lineWidth")
material.setValue(0.5, forKey: "lineSmooth")
material.setValue(UIColor.red, forKey: "lineColor")
// 设置填充模式为填充,因为我们在片元着色器中模拟线框
material.fillMode = .fill

注意:这种方法依赖SceneKit提供的v_barycentricCoord变量,部分自定义几何体可能需要手动添加重心坐标源才能正常工作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 03:28:09