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

如何在Swift的SceneKit中为对象添加内边距

SceneKit对象始终占满容器,如何添加内边距或调整大小?

我在用SceneKit开发功能,但不管怎么设置,场景里的对象总会占满容器全部尺寸。我想通过添加四周内边距或动态调整对象大小来改变这个情况,目前用圆形图片测试,但对象始终适配SceneKit容器的尺寸。


SceneKitView代码

struct SceneKitView: UIViewRepresentable {
    let imageName: String

    func makeUIView(context: Context) -> SCNView {
        let sceneView = SCNView()
        sceneView.scene = makeScene()
        sceneView.allowsCameraControl = true
        sceneView.backgroundColor = UIColor.black
        return sceneView
    }

    func updateUIView(_ uiView: SCNView, context: Context) {
        // No dynamic updates needed yet
    }

    private func makeScene() -> SCNScene {
        let scene = SCNScene()
        let badgeNode = createBadgeNode(from: imageName)
        scene.rootNode.addChildNode(badgeNode)

        return scene
    }

    private func createBadgeNode(from imageName: String) -> SCNNode {
        guard let image = UIImage(named: imageName) else {
            fatalError("Image \(imageName) not found in assets.")
        }

        let screenWidth = UIScreen.main.bounds.width
        let screenHeight = UIScreen.main.bounds.height
        let horizontalPadding: CGFloat = 40 // <-- 无效果
        let maxBadgeWidth = screenWidth - horizontalPadding
        let aspectRatio = image.size.width / image.size.height

        let badgeWidth = Float(min(maxBadgeWidth, screenHeight * aspectRatio)) / 1000
        let badgeHeight = badgeWidth / Float(aspectRatio)

        let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth), height: CGFloat(badgeHeight))
        let badgeMaterial = SCNMaterial()
        badgeMaterial.diffuse.contents = image
        badgeMaterial.isDoubleSided = true
        badgeGeometry.materials = [badgeMaterial]

        let badgeNode = SCNNode(geometry: badgeGeometry)
        badgeNode.position = SCNVector3(0, 0, 0)

        return badgeNode
    }
}

ContentView代码

struct ContentView: View {
    @State private var selectedImage: UIImage?
    var body: some View {
        VStack {
            SceneKitView(imageName: "Test1")
                .frame(width: 300, height: 300)
                //.edgesIgnoringSafeArea(.all)
        }
    }
}

尝试的调整及效果

  • 默认代码效果:圆形图片完全填满300x300的容器
  • 仅修改width参数:
    let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth) + 0.1, height: CGFloat(badgeHeight))
    // 其余代码不变
    
    效果:图片高度被拉伸,形状变形
  • 同时修改宽高参数:
    let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth) + 0.1, height: CGFloat(badgeHeight) + 0.1)
    // 其余代码不变
    
    效果:图片整体被拉伸,严重扭曲
  • 进一步调整height参数:
    let badgeGeometry = SCNPlane(width: CGFloat(badgeWidth) + 0.1, height: CGFloat(badgeHeight) + 0.5)
    // 其余代码不变
    
    效果:图片高度被过度拉伸,形状完全变形

解决方案

问题核心是SceneKit默认相机会自动适配场景内容,导致对象填满视图。以下是两种有效解决方式:

1. 手动设置相机参数,预留内边距

在makeScene()中添加自定义相机,通过控制相机位置和视野,让对象周围留出空间:

private func makeScene() -> SCNScene {
    let scene = SCNScene()
    let badgeNode = createBadgeNode(from: imageName)
    scene.rootNode.addChildNode(badgeNode)
    
    // 添加自定义相机
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    // 预留20%内边距,根据对象大小调整相机z轴位置
    let paddingFactor: Float = 1.2
    cameraNode.position = SCNVector3(0, 0, badgeNode.boundingSphere.radius * paddingFactor)
    // 让相机始终对准对象
    cameraNode.look(at: badgeNode.position)
    scene.rootNode.addChildNode(cameraNode)
    
    return scene
}

如果需要保留用户相机控制,可调整相机控制的限制范围;不需要则直接关闭allowsCameraControl。

2. 基于容器大小计算对象尺寸,保持比例

修正createBadgeNode的尺寸计算逻辑,以容器大小为基准,而非屏幕大小,同时严格保持图片原始比例:

// 新增containerSize参数,传递容器尺寸
private func createBadgeNode(from imageName: String, containerSize: CGSize) -> SCNNode {
    guard let image = UIImage(named: imageName) else {
        fatalError("Image \(imageName) not found in assets.")
    }
    
    let padding: CGFloat = 40
    let maxAvailableSize = CGSize(
        width: containerSize.width - padding*2,
        height: containerSize.height - padding*2
    )
    let aspectRatio = image.size.width / image.size.height
    
    // 计算符合比例的最大尺寸
    var badgeSize = CGSize()
    if image.size.width > image.size.height {
        badgeSize.width = maxAvailableSize.width
        badgeSize.height = maxAvailableSize.width / aspectRatio
    } else {
        badgeSize.height = maxAvailableSize.height
        badgeSize.width = maxAvailableSize.height * aspectRatio
    }
    
    // 转换为SceneKit合理单位(1单位≈1米,缩小尺寸适配场景)
    let scnWidth = Float(badgeSize.width) / 100
    let scnHeight = Float(badgeSize.height) / 100
    
    let badgeGeometry = SCNPlane(width: CGFloat(scnWidth), height: CGFloat(scnHeight))
    let badgeMaterial = SCNMaterial()
    badgeMaterial.diffuse.contents = image
    badgeMaterial.isDoubleSided = true
    badgeGeometry.materials = [badgeMaterial]
    
    let badgeNode = SCNNode(geometry: badgeGeometry)
    badgeNode.position = SCNVector3(0, 0, 0)
    
    return badgeNode
}

然后在makeScene和makeUIView中传递容器尺寸:

func makeUIView(context: Context) -> SCNView {
    let sceneView = SCNView()
    // 传递容器尺寸(也可通过参数动态传入)
    sceneView.scene = makeScene(containerSize: CGSize(width: 300, height: 300))
    sceneView.allowsCameraControl = true
    sceneView.backgroundColor = UIColor.black
    return sceneView
}

private func makeScene(containerSize: CGSize) -> SCNScene {
    let scene = SCNScene()
    let badgeNode = createBadgeNode(from: imageName, containerSize: containerSize)
    scene.rootNode.addChildNode(badgeNode)
    
    // 添加相机确保对象显示在视野内
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.position = SCNVector3(0, 0, badgeNode.boundingSphere.radius * 1.2)
    cameraNode.look(at: badgeNode.position)
    scene.rootNode.addChildNode(cameraNode)
    
    return scene
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:24:52