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

如何基于鼠标手势在VisionOS中实现Entity的切口动画?

实现RealityKit中基于鼠标拖动路径的模型切口效果

一、捕获鼠标拖动的3D路径

首先要把屏幕鼠标坐标转换成RealityKit场景内的3D空间点,射线检测(Raycasting)是最直接的方式:

  • 在ARView或RealityView中监听mouseDown、mouseDragged、mouseUp事件
  • 每次事件触发时,用raycast(from:allowing:alignmentTarget:)获取与皮肤模型的交点,将这些交点收集成拖动路径的点数组

示例代码:

var dragPath: [SIMD3<Float>] = []

func handleMouseDown(at point: CGPoint) {
    guard let hitResult = arView.raycast(from: point, allowing: .specific(skinEntity), alignmentTarget: .any).first else { return }
    dragPath = [hitResult.worldTransform.position]
}

func handleMouseDragged(at point: CGPoint) {
    guard let hitResult = arView.raycast(from: point, allowing: .specific(skinEntity), alignmentTarget: .any).first else { return }
    dragPath.append(hitResult.worldTransform.position)
    updateCutEffect(with: dragPath)
}

func handleMouseUp() {
    finalizeCutEffect(with: dragPath)
    dragPath.removeAll()
}

二、实现切口效果的两种方案

方案1:几何变形(适合低面数棱柱模型)

如果你的矩形棱柱是简单Mesh,可以直接修改顶点位置实现沿路径的缩进:

  • 提取模型MeshResource的顶点数据
  • 遍历顶点,计算每个顶点到拖动路径的最短距离,距离小于阈值的顶点向模型内部偏移
  • 将修改后的顶点重新生成MeshResource,替换原模型的mesh,并添加平滑过渡动画

示例代码片段:

func updateCutEffect(with path: [SIMD3<Float>]) {
    guard let mesh = skinEntity.model?.mesh as? MeshResource,
          var vertices = mesh.vertices else { return }
    
    let indentDepth: Float = 0.05 // 切口缩进深度
    let influenceRange: Float = 0.1 // 路径影响范围
    
    for i in 0..<vertices.count {
        let vertexPos = vertices[i].position
        guard let minDist = path.map({ distance(vertexPos, $0) }).min() else { continue }
        
        if minDist < influenceRange {
            // 计算偏移方向(这里简化为指向模型中心,实际可根据路径切线优化)
            let offsetDir = normalize(skinEntity.position - vertexPos)
            let offsetFactor = 1 - (minDist / influenceRange)
            vertices[i].position = vertexPos + offsetDir * indentDepth * offsetFactor
        }
    }
    
    let newMesh = MeshResource.generate(from: vertices, indices: mesh.indices!)
    // 平滑过渡到新形状
    skinEntity.move(to: ModelComponent(mesh: newMesh, materials: skinEntity.model!.materials), relativeTo: skinEntity.parent, duration: 0.1)
}

方案2:自定义Shader渲染(适合真实皮肤质感)

要实现更逼真的皮肤切口效果,用RealityKit自定义Shader是最优解:

  • 创建Metal Shader文件(.metal),在片元着色器中根据拖动路径计算像素的变形和颜色变化
  • 为模型材质设置自定义Shader,将拖动路径数据作为uniform变量传入
  • 拖动过程中实时更新uniform变量,Shader会自动计算每个像素的切口效果

简化版Shader示例:

#include <metal_stdlib>
#include <RealityKit/RealityKit.h>

struct VertexInput {
    float3 position [[attribute(RKVertexAttributePosition)]];
    float3 normal [[attribute(RKVertexAttributeNormal)]];
};

struct VertexOutput {
    float4 position [[position]];
    float3 worldPos;
    float3 normal;
};

vertex VertexOutput vertexShader(VertexInput in [[stage_in]],
                                 constant RKSceneUniforms& sceneUniforms [[buffer(RKBufferIndexSceneUniforms)]]) {
    VertexOutput out;
    out.worldPos = (sceneUniforms.modelTransform * float4(in.position, 1.0)).xyz;
    out.position = sceneUniforms.viewProjectionTransform * float4(out.worldPos, 1.0);
    out.normal = normalize((sceneUniforms.modelTransform * float4(in.normal, 0.0)).xyz);
    return out;
}

fragment half4 fragmentShader(VertexOutput in [[stage_in]],
                              constant float3* dragPath [[buffer(1)]],
                              constant int pathLength [[buffer(2)]]) {
    half4 skinColor = half4(0.82, 0.61, 0.52, 1.0);
    float indentDepth = 0.02;
    float influenceRadius = 0.1;
    
    float minDist = 1e9;
    for (int i=0; i<pathLength; i++) {
        float dist = distance(in.worldPos, dragPath[i]);
        minDist = min(minDist, dist);
    }
    
    if (minDist < influenceRadius) {
        float factor = 1.0 - (minDist / influenceRadius);
        // 切口处变暗模拟阴影
        skinColor.rgb *= 0.7 + (factor * 0.3);
        // 位移映射实现凹陷效果
        in.position.z += indentDepth * factor;
    }
    
    return skinColor;
}

Swift中配置Shader的代码:

// 加载自定义Shader
let skinShader = CustomMaterial.Shader(named: "SkinCutShader", in: .main)
var skinMaterial = CustomMaterial(shader: skinShader, lightingModel: .lit)

// 初始化Shader参数
skinMaterial.set(dragPath, for: "dragPath")
skinMaterial.set(dragPath.count, for: "pathLength")
skinEntity.model?.materials = [skinMaterial]

// 拖动时更新Shader参数
func handleMouseDragged(at point: CGPoint) {
    // ... 省略获取hitResult更新dragPath的代码 ...
    withAnimation(.easeInOut(duration: 0.1)) {
        skinMaterial.set(dragPath, for: "dragPath")
        skinMaterial.set(dragPath.count, for: "pathLength")
    }
}

三、优化与注意事项

  • 射线检测时用.specific(skinEntity)过滤,确保只检测目标模型
  • 几何变形方案适合低面数模型,高面数场景优先用Shader方案,避免性能问题
  • 可以对拖动路径做采样优化,比如只保留间隔大于0.05米的点,减少计算量

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 04:57:28