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

解决React Three Fiber中跟随鼠标的球体透视畸变问题

解决跟随鼠标球体的屏幕边缘畸变问题

看起来你遇到的是透视相机的典型透视畸变问题——当物体处于屏幕边缘时,透视投影会导致物体形状拉伸变形,尤其是在FOV设置较大或者物体离相机相对近的情况下。咱们一步步来解决这个问题:

问题根源

你当前使用的是透视相机(PerspectiveCamera),它的视锥体是梯形结构,屏幕边缘的物体因为投影关系会产生明显的透视变形。加上你是直接通过mouse.x * viewport.width / 2这种线性方式计算球体位置,没有考虑透视投影的非线性特性,所以边缘的畸变会被放大。

解决方案1:改用正交相机(彻底消除畸变)

如果你的场景不需要透视效果(比如这种跟随鼠标的UI元素),正交相机(OrthographicCamera)是最佳选择——它没有透视变形,屏幕上任何位置的物体大小、形状都保持一致。

修改Canvas组件的相机配置:

const Canvas: NextPage<CanvasProps> = ({ children }) => { 
  const { width, height } = useThree((state) => state.viewport);
  const aspectRatio = width / height;
  const cameraZoom = 2; // 调整这个值控制整体视野大小

  // 配置正交相机参数
  const camera = {
    type: 'orthographic',
    left: -aspectRatio * cameraZoom,
    right: aspectRatio * cameraZoom,
    top: cameraZoom,
    bottom: -cameraZoom,
    near: 0.1,
    far: 1000,
    position: [0, 0, 5]
  } 

  return ( 
    <ThreeCanvas className="bg-gray-900" camera={camera}>
      {children} 
      <Mouse /> 
    </ThreeCanvas> 
  ) 
}

简化Mouse组件的位置计算:

正交相机下,鼠标归一化坐标可以直接映射到世界坐标,无需复杂计算:

useFrame(({ mouse }) => {
  if (!ref.current) return;
  
  // 直接用鼠标归一化值乘以相机缩放系数
  const x = mouse.x * cameraZoom;
  const y = mouse.y * cameraZoom;
  
  ref.current.position.set(x, y, 0);
  // 如果不需要旋转效果可以直接移除这行
  ref.current.rotation.set(-y * 0.5, x * 0.5, 0);
})

注意:这里的cameraZoom要和Canvas组件里的保持一致,或者可以通过useThree获取相机实例的zoom属性来同步。

解决方案2:使用射线投射修正透视相机的位置计算

如果你需要保留透视效果,可以用Raycaster来计算鼠标对应的世界坐标——它会基于相机的投影矩阵,准确计算出鼠标射线与指定平面的交点,避免线性计算带来的畸变。

修改Mouse组件:

const Mouse: NextPage<MouseProps> = ({}) => { 
  const [active, setActive] = useState(false) 
  const { camera, mouse } = useThree() 
  const { scale } = useSpring({ scale: active ? 0.7 : 1 }) 
  
  useEffect(() => { 
    if (active) { 
      setTimeout(() => setActive(false), 200) 
    } 
  }, [active]) 
  
  const ref = useRef<THREE.Mesh>(null);
  const raycaster = useRef(new THREE.Raycaster());
  // 定义一个在相机前方的平面(这里选z=0的平面,你可以调整距离)
  const targetPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
  const intersectionPoint = new THREE.Vector3();

  useFrame(() => {
    if (!ref.current) return;
    
    // 更新射线指向当前鼠标位置
    raycaster.current.setFromCamera(mouse, camera);
    // 计算射线与平面的交点
    raycaster.current.ray.intersectPlane(targetPlane, intersectionPoint);
    
    // 将球体移动到交点位置
    ref.current.position.copy(intersectionPoint);
    ref.current.rotation.set(-intersectionPoint.y, intersectionPoint.x, 0);
  }) 

  return ( 
    <> 
      <ambientLight /> 
      <pointLight position={[10, 10, 10]} /> 
      <animated.mesh scale={scale} onClick={() => setActive(!active)} ref={ref}>
        <sphereGeometry args={[0.15, 32, 32]} /> 
        <meshStandardMaterial color="beige" transparent /> 
      </animated.mesh> 
    </> 
  ) 
}

额外优化建议

  • 如果你保留了旋转效果,可以适当降低旋转系数(比如-y * 0.5),避免边缘区域旋转幅度过大看起来奇怪。
  • 透视相机下,你也可以尝试降低FOV值(比如从60调到45),能一定程度减少边缘畸变,但不如前两种方案彻底。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 10:47:43