如何用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
相关产品推荐
相关产品推荐

