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

如何在ThreeJS场景中添加独立于模型的坐标指示器?

实现固定角落的可交互坐标指示器

要实现类似GID的固定坐标指示器,核心是用独立的UI场景+相机渲染指示器,完全脱离主场景的视角控制,再添加点击切换视角的逻辑。以下是具体实现步骤:

一、创建独立的UI场景与相机

在主场景代码外,新增专门渲染指示器的场景和正交相机(正交相机无透视变形,适配UI元素):

// 新增UI场景(用于坐标指示器)
const uiScene = new THREE.Scene();
uiScene.background = null; // 设为透明,不遮挡主场景

// 创建正交相机,适配容器尺寸
const uiCamera = new THREE.OrthographicCamera(
  -container.clientWidth / 2,
  container.clientWidth / 2,
  container.clientHeight / 2,
  -container.clientHeight / 2,
  0.1,
  100
);
uiCamera.position.set(0, 0, 10); // 相机位置,确保能看到指示器
uiCamera.lookAt(uiScene.position);

二、添加坐标指示器到UI场景

用AxesHelper作为基础指示器,调整位置让它固定在右下角:

// 创建轴指示器,调整尺寸和位置
const axesHelper = new THREE.AxesHelper(80); // 尺寸根据需求调整
// 把指示器移到右下角:x设为容器宽度的一半减去偏移,y设为负的容器高度一半加偏移
axesHelper.position.set(
  container.clientWidth / 2 - 100,
  -container.clientHeight / 2 + 100,
  0
);
uiScene.add(axesHelper);

// 给三个轴命名,方便后续点击检测
axesHelper.children[0].name = 'XAxis'; // X轴(红色)
axesHelper.children[1].name = 'YAxis'; // Y轴(绿色)
axesHelper.children[2].name = 'ZAxis'; // Z轴(蓝色)

三、修改渲染逻辑,叠加UI场景

在渲染循环里先渲染主场景,再渲染UI场景,实现指示器叠加效果:

function animate() {
  requestAnimationFrame(animate);
  controls.update();
  
  // 先渲染主场景
  renderer.render(scene, camera);
  // 再渲染UI场景(覆盖在主场景上)
  renderer.render(uiScene, uiCamera);
}
animate();

四、添加点击切换视角的交互

用Raycaster检测点击的轴,切换主相机到对应视角:

// 射线检测相关变量
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

// 监听画布点击事件
renderer.domElement.addEventListener('click', (event) => {
  // 计算鼠标在UI相机坐标系中的位置
  mouse.x = (event.clientX / container.clientWidth) * 2 - 1;
  mouse.y = -(event.clientY / container.clientHeight) * 2 + 1;
  
  // 用UI相机更新射线
  raycaster.setFromCamera(mouse, uiCamera);
  // 检测与轴指示器的交点
  const intersects = raycaster.intersectObject(axesHelper, true);
  
  if (intersects.length > 0) {
    const objName = intersects[0].object.name;
    // 根据点击的轴切换视角
    switch(objName) {
      case 'XAxis':
        camera.position.set(20, 0, 0);
        break;
      case 'YAxis':
        camera.position.set(0, 20, 0);
        break;
      case 'ZAxis':
        camera.position.set(0, 0, 20);
        break;
    }
    controls.target.set(0, 0, 0);
    camera.lookAt(controls.target);
    controls.update();
  }
});

五、处理窗口resize事件

窗口尺寸变化时,同步更新主相机、UI相机和指示器位置:

window.addEventListener('resize', () => {
  const newWidth = container.clientWidth;
  const newHeight = container.clientHeight;
  
  // 更新主相机
  camera.aspect = newWidth / newHeight;
  camera.updateProjectionMatrix();
  
  // 更新UI正交相机
  uiCamera.left = -newWidth / 2;
  uiCamera.right = newWidth / 2;
  uiCamera.top = newHeight / 2;
  uiCamera.bottom = -newHeight / 2;
  uiCamera.updateProjectionMatrix();
  
  // 更新指示器位置,保持在右下角
  axesHelper.position.set(newWidth / 2 - 100, -newHeight / 2 + 100, 0);
  
  renderer.setSize(newWidth, newHeight);
});

完整整合后的代码片段

把以上逻辑整合到你的现有代码中,替换原axesHelper相关部分:

import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.121.1/build/three.module.js";
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@0.121.1/examples/jsm/controls/OrbitControls.js";

ipcRenderer.on('file-data', (event, data) => {
  const lines = data.split('\n');
  console.log(lines);
  
  const scene = new THREE.Scene();  
  scene.background = new THREE.Color("#ffffff");  
  const container = document.querySelector('.design');   
  const width = container.clientWidth;
  const height = container.clientHeight;
  
  // 主相机与渲染器
  const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);  
  const renderer = new THREE.WebGLRenderer(); 
  renderer.setSize(width, height);    
  container.appendChild(renderer.domElement); 
  
  // OrbitControls
  const controls = new OrbitControls(camera, renderer.domElement);
  // 初始化主相机位置
  camera.position.set(5, 5, 5);
  controls.target.set(0, 0, 0);
  camera.lookAt(controls.target);
  
  // ------------------- 新增UI场景与坐标指示器 -------------------
  const uiScene = new THREE.Scene();
  uiScene.background = null;

  const uiCamera = new THREE.OrthographicCamera(
    -width / 2,
    width / 2,
    height / 2,
    -height / 2,
    0.1,
    100
  );
  uiCamera.position.set(0, 0, 10);
  uiCamera.lookAt(uiScene.position);

  const axesHelper = new THREE.AxesHelper(80);
  axesHelper.position.set(width / 2 - 100, -height / 2 + 100, 0);
  // 给轴命名
  axesHelper.children[0].name = 'XAxis';
  axesHelper.children[1].name = 'YAxis';
  axesHelper.children[2].name = 'ZAxis';
  uiScene.add(axesHelper);

  // 点击交互逻辑
  const raycaster = new THREE.Raycaster();
  const mouse = new THREE.Vector2();
  renderer.domElement.addEventListener('click', (event) => {
    mouse.x = (event.clientX / width) * 2 - 1;
    mouse.y = -(event.clientY / height) * 2 + 1;
    raycaster.setFromCamera(mouse, uiCamera);
    const intersects = raycaster.intersectObject(axesHelper, true);
    if (intersects.length > 0) {
      const objName = intersects[0].object.name;
      switch(objName) {
        case 'XAxis':
          camera.position.set(20, 0, 0);
          break;
        case 'YAxis':
          camera.position.set(0, 20, 0);
          break;
        case 'ZAxis':
          camera.position.set(0, 0, 20);
          break;
      }
      controls.target.set(0, 0, 0);
      camera.lookAt(controls.target);
      controls.update();
    }
  });

  // resize处理
  window.addEventListener('resize', () => {
    const newWidth = container.clientWidth;
    const newHeight = container.clientHeight;
    
    camera.aspect = newWidth / newHeight;
    camera.updateProjectionMatrix();
    
    uiCamera.left = -newWidth / 2;
    uiCamera.right = newWidth / 2;
    uiCamera.top = newHeight / 2;
    uiCamera.bottom = -newHeight / 2;
    uiCamera.updateProjectionMatrix();
    
    axesHelper.position.set(newWidth / 2 - 100, -newHeight / 2 + 100, 0);
    
    renderer.setSize(newWidth, newHeight);
  });

  // 渲染循环
  function animate() {
    requestAnimationFrame(animate);
    controls.update();
    renderer.render(scene, camera);
    renderer.render(uiScene, uiCamera);
  }
  animate();
});

这样实现的指示器会固定在右下角,不会随主场景视角变化,点击对应轴即可切换到该轴的正方向视角。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 22:44:55