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

如何用WebGLRenderer在Three.js网格上嵌入HTML页面并保留原有场景?

在Three.js中同时保留WebGL场景并在立方体上显示可交互HTML

嘿,作为Three.js新手碰到这个需求太正常了!其实咱们可以**同时使用WebGLRenderer和CSS3DRenderer**来实现——WebGL负责渲染你的3D场景(显示器底座、支架这些实体模型),CSS3DRenderer负责把可交互的HTML内容转成3D元素,再让两者的相机、控制器完全同步,视觉上就像HTML嵌在WebGL的显示器表面一样。

下面是针对你现有代码的完整修改方案,一步步来:

1. 准备HTML内容容器

首先在页面里创建一个你要显示的HTML容器(比如放在body里),给它设置好样式,确保尺寸比例和你的WebGL显示器匹配:

<div id="htmlContent" style="width: 1200px; height: 700px; background: white; padding: 20px; box-sizing: border-box;">
  <!-- 这里放你要显示的HTML内容,比如: -->
  <h1>这是嵌入的网页</h1>
  <p>可以点击输入框试试:<input type="text" placeholder="输入点东西"></p>
  <a href="javascript:alert('点击有效!')">点击我弹出提示</a>
</div>

这里的宽高1200px/700px对应你WebGL显示器的monitorLength:12和monitorWidth:7,比例保持一致就行。

2. 导入CSS3D相关模块

确保你已经导入了Three.js的CSS3DRenderer相关组件(如果用的是ES6模块):

import { CSS3DRenderer, CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';

如果是用CDN引入,记得加上对应的script标签。

3. 修改你的Three.js代码

下面是修改后的完整代码,我会标注关键修改点:

// Canvas
const canvas = document.querySelector('canvas.webgl')

// --------------------------
// 新增:CSS3D场景和渲染器
// --------------------------
const cssScene = new THREE.Scene();
const cssRenderer = new CSS3DRenderer();
cssRenderer.setSize(window.innerWidth, window.innerHeight);
cssRenderer.domElement.style.position = 'absolute';
cssRenderer.domElement.style.top = '0';
cssRenderer.domElement.style.pointerEvents = 'none'; // 让鼠标事件能穿透到WebGL画布,除非HTML元素需要交互
document.body.appendChild(cssRenderer.domElement);

// 获取HTML内容容器
const htmlContent = document.getElementById('htmlContent');
const cssObject = new CSS3DObject(htmlContent);
// 调整位置和缩放,让HTML刚好贴在WebGL显示器的正面
cssObject.position.set(monitorX, monitorY, monitorZ + 0.13); // Z轴多移一点,避免和WebGL模型重叠
cssObject.scale.set(0.01, 0.01, 0.01); // 因为HTML是1200px宽,对应WebGL的12单位,所以缩放0.01
// 注意:如果你的显示器有旋转,这里也要同步旋转cssObject

// Scene
const scene = new THREE.Scene()

const monitorMaterial = new THREE.MeshPhongMaterial( {color: "darkgrey"} );
const monitorLength = 12
const monitorWidth = 7

const monitorX = 10
const monitorY = 5.5
const monitorZ = 10

const monitorGeometry = new THREE.BoxGeometry(monitorLength, monitorWidth, 0.25, 100, 50, 10);
const monitor = new THREE.Mesh( monitorGeometry, monitorMaterial );
monitor.position.x = monitorX
monitor.position.y = monitorY
monitor.position.z = monitorZ
scene.add(monitor); // 别忘了把monitor添加到场景里!

const baseGeometry = new THREE.CylinderGeometry(3, 3, 0.25, 100);
const base = new THREE.Mesh( baseGeometry, new THREE.MeshPhongMaterial({ color:  "black"}) );
base.position.x = monitorX
base.position.y = 0.5
base.position.z = monitorZ
scene.add(base);

const bracketGeometry = new RoundedBoxGeometry( 0.5, 4, 0.5, 1, 1 );
const bracket = new THREE.Mesh( bracketGeometry, new THREE.MeshPhongMaterial({ color:  "black"}) );
bracket.position.x = monitorX
bracket.position.y = 2
bracket.position.z = 9.2
bracket.rotation.x = -2.75
scene.add(bracket);

/**
 * Camera
 */
// Base camera
const camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100)
camera.position.x = 36
camera.position.y = 35
camera.position.z = 65
scene.add(camera)
cssScene.add(camera); // 把相机也添加到CSS3D场景

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enabled = true; // 这里打开控制方便测试
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas
})
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.setClearColor( 0xffffff, 1);

// --------------------------
// 新增:窗口resize事件处理,同步两个渲染器
// --------------------------
window.addEventListener('resize', () => {
  const width = window.innerWidth;
  const height = window.innerHeight;
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
  renderer.setSize(width, height);
  cssRenderer.setSize(width, height);
});

/**
 * Animate
 */
const clock = new THREE.Clock()

const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()

    // Update controls
    controls.update()

    // 同时渲染WebGL场景和CSS3D场景
    renderer.render(scene, camera)
    cssRenderer.render(cssScene, camera)

    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
   
}

tick();

关键细节说明

  • 两个渲染器的层级:CSS3DRenderer的domElement要放在WebGL canvas的上面,用绝对定位实现视觉重叠。
  • 鼠标事件处理:设置cssRenderer.domElement.style.pointerEvents = 'none',这样默认情况下鼠标事件会穿透到WebGL画布,只有当HTML元素需要交互时,该元素的pointerEvents会自动变成auto,不影响交互。
  • 位置和缩放对齐:CSS3DObject的缩放和位置要和WebGL的monitor网格严格对应,确保HTML刚好贴在显示器正面,不会偏移或大小不符。
  • 相机同步:两个场景共用同一个相机,控制器更新时,两个渲染器都会渲染最新的视角,保持视觉一致。

这样修改后,你就能同时看到原来的WebGL 3D场景,并且显示器上的HTML内容完全可以像普通网页一样交互啦!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 17:06:51