有没有想过,游戏里的镜子、传送门、或者屏幕上播放的实时3D动画是怎么实现的?
答案就是一项黑科技——渲染目标(Render Targets)。它允许我们不直接渲染到屏幕,而是“偷偷地”渲染到一张幕后的贴图上,然后再把这张包含了另一个世界画面的贴图,应用到我们主场景的任意物体上。
🖼️ 核心思想:先画到“小画板”上
这个概念听起来很绕,我们用一个简单的比喻来理解:
想象一下,你不是直接在房间的墙壁上作画,而是:
- 先拿出一块小画板(这就是
WebGLRenderTarget
)。 - 在这块小画板上,精心画了一幅动态的画(比如一个独立旋转的3D物体)。
- 最后,你把这块已经画好了动态内容的小画板,像一张照片一样,挂到墙上(应用为主场景里某个物体的贴图)。
于是,你就实现了“画中画”的神奇效果!
✨ 无穷的可能性
掌握了这项技术,你就能解锁无数种高级特效:
- 实时监控画面 📹: 在一面墙上显示另一个房间的实时3D影像。
- 神奇的传送门 🌀: 门的那一头是另一个动态的世界。
- 车内后视镜 🚗: 镜子里实时反射出身后的场景。
- 后期处理滤镜 🎞️: 实现电影级别的调色、模糊、辉光等屏幕特效。
🛠️ 盗梦空间:完整代码
下面的代码将完整演示这个“盗梦空间”的过程:我们将创建一个主场景,里面有一个立方体;同时,我们还会创建另一个独立的“内部”场景,里面有一个旋转的环面纽结。然后,我们会把“内部”场景实时渲染的结果,作为贴图应用到主场景的立方体上。
代码亮点:
- 创建了
mainScene
和rtScene
(Render Target Scene) 两个独立的场景。 - 在动画循环中,通过
renderer.setRenderTarget()
来回切换渲染目标,实现“先画到小画板,再画到主屏幕”的流程。
<!-- end list -->
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Three.js Render Targets</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.165.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.165.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 1. 主场景设置
const mainScene = new THREE.Scene();
mainScene.background = new THREE.Color(0x333333);
const mainCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
mainCamera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
new OrbitControls(mainCamera, renderer.domElement);
// 2. 创建一个 Render Target (我们的小画板)
const rt = new THREE.WebGLRenderTarget(512, 512);
// 3. 创建 "内部" 场景 (画板上的内容)
const rtScene = new THREE.Scene();
rtScene.background = new THREE.Color(0x87CEEB);
const rtCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 100); // 宽高比为1,匹配RT尺寸
rtCamera.position.z = 3;
// 为内部场景添加自己的灯光和物体
rtScene.add(new THREE.AmbientLight(0xffffff, 0.5));
rtScene.add(new THREE.DirectionalLight(0xffffff, 1));
const rtKnot = new THREE.Mesh(
new THREE.TorusKnotGeometry(1, 0.3, 100, 16),
new THREE.MeshStandardMaterial({ color: 0xff0088, roughness: 0.2 })
);
rtScene.add(rtKnot);
// 4. 在主场景创建一个立方体,并使用Render Target的纹理
const cubeMaterial = new THREE.MeshBasicMaterial({ map: rt.texture });
const mainCube = new THREE.Mesh(new THREE.BoxGeometry(2, 2, 2), cubeMaterial);
mainScene.add(mainCube);
// 5. 动画循环
function animate(time) {
time *= 0.001;
// 让两个场景的物体都动起来
rtKnot.rotation.x = time;
rtKnot.rotation.y = time;
mainCube.rotation.x = -time * 0.2;
mainCube.rotation.y = -time * 0.2;
// 关键步骤:先渲染到 Render Target
renderer.setRenderTarget(rt);
renderer.render(rtScene, rtCamera);
// 然后再渲染到主屏幕
renderer.setRenderTarget(null);
renderer.render(mainScene, mainCamera);
requestAnimationFrame(animate);
}
animate();
</script>
</body>
</html>
🎬 “盒中世界”诞生
打开页面,你会看到一个正在旋转的立方体。但神奇的是,立方体的表面并非纯色或普通贴图,而是一个实时播放的、完全不同的3D动画(一个旋转的彩色纽结)!
你正在从主世界,窥探一个被实时渲染出来的“盒中世界”。
渲染目标是Three.js中一个从“会用”到“精通”的分水岭。它将渲染过程本身变成了创造工具,是实现高级视觉特效的基础。掌握了它,你的创意将不再受限于单一的场景!
#ThreeJS #特效 #渲染 #WebGPU #Shader #游戏开发 #前端开发 #JavaScript #技术干货