Atlaslog
Atlaslog / Examples / three.js / Multiple Views
three.js · Runnable Example

Multiple Views

Runnable three.js example: Multiple Views. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.

Open standalone → Chinese notes All three.js examples
/demos/three-multiple-views.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/three-multiple-views.html
<!--
  说明:同一块画布切成四个视口,一个透视相机加三个正交相机,共用一次渲染循环
  核心逻辑:setScissorTest / setViewport / setScissor 按象限铺开,逐相机渲染
  依赖:three.js 0.160.0、OrbitControls · jsDelivr CDN(ESM + importmap)
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>四视图</title>
<style>
  html,body{height:100%;margin:0;background:#0a1016}
  body{display:flex;flex-direction:column;overflow:hidden;
       font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#e6e8ec}
  .bar{flex:0 0 auto;padding:8px 12px;background:#161b22;border-bottom:1px solid #252c36;
       display:flex;gap:14px;align-items:center;font-size:12px;color:#8b93a1;flex-wrap:wrap}
  .bar b{color:#58a6ff;font-weight:600}
  .bar .sp{flex:1 1 auto}
  #stage{flex:1 1 auto;min-height:0;position:relative}
  #stage canvas{display:block}
  #labels{position:absolute;inset:0;pointer-events:none}
  #labels span{position:absolute;padding:3px 8px;border-radius:4px;font-size:11px;
       background:rgba(10,16,22,.72);border:1px solid #223140;color:#9fb4c8}
</style>
</head>
<body>
<div class="bar">
  <span>视口 <b>4</b> 个</span>
  <span class="sp"></span>
  <span>拖拽旋转左上透视视图,三个正交视图同步更新</span>
</div>
<div id="stage">
  <div id="labels">
    <span style="left:10px;top:10px">透视 · 自由旋转</span>
    <span style="left:50%;top:10px;margin-left:10px">俯视 · 正交</span>
    <span style="left:10px;bottom:10px">正视 · 正交</span>
    <span style="left:50%;bottom:10px;margin-left:10px">侧视 · 正交</span>
  </div>
</div>

<script type="importmap">
{
  "imports": {
    "three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
    "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
  }
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

const stage = document.getElementById('stage');
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(stage.clientWidth, stage.clientHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.setScissorTest(true);
stage.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a1016);

scene.add(new THREE.HemisphereLight(0xb6d0ea, 0x101820, 1.4));
const sun = new THREE.DirectionalLight(0xffeccd, 2.1);
sun.position.set(-18, 26, 12);
scene.add(sun);

const W = stage.clientWidth;
const H = stage.clientHeight;
const ASPECT = W / H;

const persp = new THREE.PerspectiveCamera(45, ASPECT / 2, 0.1, 300);
persp.position.set(18, 15, 20);

const controls = new OrbitControls(persp, renderer.domElement);
controls.target.set(0, 2, 0);
controls.enableDamping = true;
controls.update();

/* 三个正交相机:俯、正、侧,视野大小按场景尺寸给定 */
const FRUSTUM = 11;
function makeOrtho(dir) {
  const cam = new THREE.OrthographicCamera(-FRUSTUM, FRUSTUM, FRUSTUM, -FRUSTUM, 0.1, 200);
  cam.position.copy(dir.clone().multiplyScalar(40));
  cam.lookAt(0, 2, 0);
  cam.updateProjectionMatrix();
  return cam;
}

const top = makeOrtho(new THREE.Vector3(0.001, 1, 0.001));
const front = makeOrtho(new THREE.Vector3(0, 0.15, 1));
const side = makeOrtho(new THREE.Vector3(1, 0.15, 0));

/* 场景内容:一片地形加几栋建筑,正交视图里能直接量出尺寸 */
function heightAt(x, z) {
  return 0.9 * Math.sin(x * 0.4) * Math.cos(z * 0.33) + 0.4 * Math.sin(x * 0.9 + z * 0.5);
}

const tGeo = new THREE.PlaneGeometry(22, 22, 60, 60);
tGeo.rotateX(-Math.PI / 2);
const tp = tGeo.attributes.position;
for (let i = 0; i < tp.count; i++) {
  tp.setY(i, heightAt(tp.getX(i), tp.getZ(i)));
}
tGeo.computeVertexNormals();
scene.add(new THREE.Mesh(
  tGeo,
  new THREE.MeshStandardMaterial({ color: 0x3d5a63, roughness: 0.9, flatShading: true })
));

const colors = [0xd98e5a, 0x8fb6c9, 0xc9b48f, 0x9a8fae];
for (let i = 0; i < 9; i++) {
  const gx = (i % 3 - 1) * 6;
  const gz = (((i / 3) | 0) - 1) * 6;
  const h = 2 + (i % 4) * 1.4;
  const b = new THREE.Mesh(
    new THREE.BoxGeometry(2.4, h, 2.4),
    new THREE.MeshStandardMaterial({ color: colors[i % 4], roughness: 0.6 })
  );
  b.position.set(gx, heightAt(gx, gz) + h / 2, gz);
  scene.add(b);
}

const axes = new THREE.AxesHelper(6);
scene.add(axes);

const cameras = [
  { cam: persp, x: 0, y: 0.5, w: 0.5, h: 0.5 },
  { cam: top, x: 0.5, y: 0.5, w: 0.5, h: 0.5 },
  { cam: front, x: 0, y: 0, w: 0.5, h: 0.5 },
  { cam: side, x: 0.5, y: 0, w: 0.5, h: 0.5 }
];

function layout() {
  const w = stage.clientWidth;
  const h = stage.clientHeight;

  persp.aspect = (w * 0.5) / (h * 0.5);
  persp.updateProjectionMatrix();

  cameras.forEach(function (v) {
    const vw = Math.floor(w * v.w);
    const vh = Math.floor(h * v.h);
    if (v.cam.isOrthographicCamera) {
      const a = vw / vh;
      v.cam.left = -FRUSTUM * a;
      v.cam.right = FRUSTUM * a;
      v.cam.top = FRUSTUM;
      v.cam.bottom = -FRUSTUM;
    }
    v.cam.aspect = vw / vh;
    v.cam.updateProjectionMatrix();
  });
}

renderer.setAnimationLoop(function () {
  controls.update();

  const w = stage.clientWidth;
  const h = stage.clientHeight;

  cameras.forEach(function (v) {
    const vw = Math.floor(w * v.w);
    const vh = Math.floor(h * v.h);
    const x = Math.floor(w * v.x);
    const y = Math.floor(h * v.y);

    renderer.setViewport(x, y, vw, vh);
    renderer.setScissor(x, y, vw, vh);
    renderer.render(scene, v.cam);
  });
});

layout();

window.addEventListener('resize', function () {
  const w = stage.clientWidth;
  const h = stage.clientHeight;
  renderer.setSize(w, h);
  layout();
});
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.

More three.js examples

three.js · EXAMPLEOrbit three.js · EXAMPLEParticles three.js · EXAMPLEPerformance