Atlaslog
Atlaslog / Examples / three.js / Geometry Dynamic
three.js · Runnable Example

Geometry Dynamic

Runnable three.js example: Geometry Dynamic. 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-geometry-dynamic.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/three-geometry-dynamic.html
<!--
  说明:每帧改写顶点高度,让网格像被水流推动一样起伏,演示动态几何更新
  核心逻辑:保留原始高度,逐帧写入 position.y 后重算法线并置 needsUpdate
  依赖: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:#08131a}
  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;font-variant-numeric:tabular-nums}
  .bar input[type=range]{width:130px;accent-color:#4f9ef8}
  .bar button{font:inherit;font-size:12px;padding:4px 10px;border-radius:5px;cursor:pointer;
       border:1px solid #303845;background:#1d2531;color:#c8d2e0}
  .bar button:hover{background:#253040}
  .bar .sp{flex:1 1 auto}
  #stage{flex:1 1 auto;min-height:0;position:relative}
  #stage canvas{display:block}
</style>
</head>
<body>
<div class="bar">
  <span>网格 <b>80 × 80</b></span>
  <label>振幅 <input type="range" id="amp" min="0.2" max="2.4" step="0.1" value="1"></label>
  <span><b id="ampv">1.0</b></span>
  <button id="pause">暂停</button>
  <span class="sp"></span>
  <span id="fps">— 帧/秒</span>
</div>
<div id="stage"></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;
stage.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x08131a);
scene.fog = new THREE.Fog(0x08131a, 26, 52);

const camera = new THREE.PerspectiveCamera(45, stage.clientWidth / stage.clientHeight, 0.1, 200);
camera.position.set(15, 11, 17);

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);
controls.enableDamping = true;
controls.maxPolarAngle = Math.PI * 0.47;
controls.update();

scene.add(new THREE.HemisphereLight(0x9ed4ff, 0x0a1a20, 1.3));
const sun = new THREE.DirectionalLight(0xffd9a0, 2.2);
sun.position.set(-12, 20, 10);
scene.add(sun);

const SEG = 80;
const SIZE = 26;

const geo = new THREE.PlaneGeometry(SIZE, SIZE, SEG, SEG);
geo.rotateX(-Math.PI / 2);

const pos = geo.attributes.position;
const baseY = new Float32Array(pos.count);
const baseX = new Float32Array(pos.count);
const baseZ = new Float32Array(pos.count);

for (let i = 0; i < pos.count; i++) {
  baseX[i] = pos.getX(i);
  baseZ[i] = pos.getZ(i);
  baseY[i] = Math.sin(baseX[i] * 0.3) * Math.cos(baseZ[i] * 0.28) * 0.8;
  pos.setY(i, baseY[i]);
}
geo.computeVertexNormals();

const colors = new Float32Array(pos.count * 3);
geo.setAttribute('color', new THREE.BufferAttribute(colors, 3));

const mat = new THREE.MeshStandardMaterial({
  vertexColors: true,
  roughness: 0.32,
  metalness: 0.42,
  side: THREE.DoubleSide
});

const water = new THREE.Mesh(geo, mat);
scene.add(water);

const c = new THREE.Color();

function paint() {
  for (let i = 0; i < pos.count; i++) {
    const h = pos.getY(i);
    const t = THREE.MathUtils.clamp(h / 2.4 * 0.5 + 0.5, 0, 1);
    c.setHSL(0.54 - 0.16 * t, 0.62, 0.16 + 0.44 * t);
    colors[i * 3] = c.r;
    colors[i * 3 + 1] = c.g;
    colors[i * 3 + 2] = c.b;
  }
  geo.getAttribute('color').needsUpdate = true;
}

let amp = 1;
let paused = false;

const ampEl = document.getElementById('amp');
const ampvEl = document.getElementById('ampv');
ampEl.addEventListener('input', function () {
  amp = Number(ampEl.value);
  ampvEl.textContent = amp.toFixed(1);
});

const pauseBtn = document.getElementById('pause');
pauseBtn.addEventListener('click', function () {
  paused = !paused;
  pauseBtn.textContent = paused ? '继续' : '暂停';
});

const fpsEl = document.getElementById('fps');
const clock = new THREE.Clock();
let frames = 0;
let acc = 0;
let t = 0;

renderer.setAnimationLoop(function () {
  const dt = Math.min(clock.getDelta(), 0.1);

  if (!paused) t += dt;

  for (let i = 0; i < pos.count; i++) {
    const x = baseX[i];
    const z = baseZ[i];
    const r = Math.sqrt(x * x + z * z);
    /* 中心向外的同心波 + 两组交叉波纹 */
    const wave = Math.sin(r * 1.35 - t * 2.4) * Math.exp(-r * 0.08);
    const cross = Math.sin(x * 0.55 + t * 1.1) * Math.cos(z * 0.5 - t * 0.9) * 0.5;
    pos.setY(i, baseY[i] + (wave * 1.6 + cross) * amp);
  }
  pos.needsUpdate = true;
  geo.computeVertexNormals();
  paint();

  frames++;
  acc += dt;
  if (acc >= 0.5) {
    fpsEl.textContent = Math.round(frames / acc) + ' 帧/秒';
    frames = 0;
    acc = 0;
  }

  controls.update();
  renderer.render(scene, camera);
});

window.addEventListener('resize', function () {
  const w = stage.clientWidth;
  const h = stage.clientHeight;
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
});
</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 · EXAMPLEGeometry Extrude three.js · EXAMPLEGeometry Lookup three.js · EXAMPLEGeometry Shapes