“使用 Three.js 的 WebGL 小实验。Three.js 莫比乌斯带。”
HTML:
<canvas id="mobius"></canvas>
<script type="importmap">{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.151.0/three.module.min.js",
"three/addons/": "https://unpkg.com/three@0.151.0/examples/jsm/"
}
}
</script>
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
html, body {
padding: 0;
margin: 0;
}
body {
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: rgb(255, 255, 242);
}
#mobius {
cursor: pointer;
padding-top: 75px; /* for codepen preview */
}
.lil-gui {
--width: 450px;
max-width: 90%;
--widget-height: 20px;
font-size: 15px;
--input-font-size: 15px;
--padding: 10px;
--spacing: 10px;
--slider-knob-width: 5px;
--background-color: rgba(5, 0, 15, .8);
--widget-color: rgba(255, 255, 255, .3);
--focus-color: rgba(255, 255, 255, .4);
--hover-color: rgba(255, 255, 255, .5);
--font-family: monospace;
}
JAVASCRIPT:
import * as THREE from "three";
import {OrbitControls} from "three/addons/controls/OrbitControls.js";
import {GUI} from "https://cdn.skypack.dev/lil-gui@0.17.0";
const canvasEl = document.querySelector("#mobius");
let renderer, scene, camera, clock, orbit, strip;
const params = {
stripeWidth: 1,
canvasSize: 250,
segments: 20,
color: {
r: 2, g: .5, b: -.4
},
}
initScene();
render();
createControls();
function initScene() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(40, 1, 1, 50);
camera.position.set(0, 1, 5);
camera.lookAt(0, 0, 0);
clock = new THREE.Clock();
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
canvas: canvasEl
});
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(params.canvasSize, params.canvasSize);
const material = new THREE.MeshNormalMaterial({
side: THREE.DoubleSide
});
material.userData.color_addition = {value: new THREE.Vector3(params.color.r, params.color.g, params.color.b)};
material.onBeforeCompile = (shader) => {
shader.uniforms.color_addition = material.userData.color_addition;
shader.fragmentShader = 'uniform vec3 color_addition;\n' + shader.fragmentShader;
shader.fragmentShader = shader.fragmentShader.replace(
' normal ',
' normal + color_addition '
);
material.userData.shader = shader;
};
orbit = new OrbitControls(camera, renderer.domElement);
orbit.enablePan = false;
orbit.enableZoom = false;
orbit.minPolarAngle = .2 * Math.PI;
orbit.maxPolarAngle = .7 * Math.PI;
orbit.autoRotate = true;
orbit.enableDamping = true;
strip = new THREE.Mesh(getStripGeometry(), material);
scene.add(strip);
}
function getStripGeometry() {
const geometry = new THREE.PlaneGeometry(1, 1, 5 * params.segments, params.segments);
const positionAttr = geometry.attributes.position;
const positions = positionAttr.array;
for (let i = 0; i < positions.length; i += 3) {
const mobiusCoords = mobiusStripPoint(positions[i], positions[i + 1]);
positions[i] = mobiusCoords[0];
positions[i + 1] = mobiusCoords[1];
positions[i + 2] = mobiusCoords[2];
}
positionAttr.copyArray(positions);
geometry.rotateX(.5 * Math.PI)
geometry.rotateZ(.1 * Math.PI)
geometry.computeVertexNormals();
return geometry;
}
function mobiusStripPoint(x, y) {
const yy = y * params.stripeWidth;
const angle = 2 * Math.PI * x;
const r = 1 + yy * Math.cos(angle);
const x1 = Math.cos(angle) * r;
const y1 = Math.sin(angle) * r;
const z1 = yy * Math.sin(angle);
return [x1, y1, z1];
}
function render(t) {
orbit.update();
renderer.render(scene, camera);
requestAnimationFrame(render);
}
function createControls() {
const gui = new GUI();
gui.add(params, "stripeWidth", .5, 1.5).onChange(() => {
strip.geometry.dispose();
strip.geometry = getStripGeometry();
}).name("width");
gui.add(params.color, "r", -1, 3).onChange(v => {
strip.material.userData.shader.uniforms.color_addition.value.x = v;
}).name("R addition");
gui.add(params.color, "g", -1, 3).onChange(v => {
strip.material.userData.shader.uniforms.color_addition.value.y = v;
}).name("G addition");
gui.add(params.color, "b", -1, 3).onChange(v => {
strip.material.userData.shader.uniforms.color_addition.value.z = v;
}).name("B addition");
}
源码:
https://codepen.io/ksenia-k/pen/NWJaBZb
体验:
https://codepen.io/ksenia-k/full/NWJaBZb