Three.js 一个会冒泡的杯子

文化   2024-10-02 10:01   河南  

使用 Three.js 的 WebGL 小实验。一个会冒泡的杯子。

实现代码

HTML:

<script type="importmap">  {    "imports": {      "three": "https://unpkg.com/three@0.162.0/build/three.module.js",      "three/addons/": "https://unpkg.com/three@0.162.0/examples/jsm/"    }  }</script>
CSS:
body{  overflow: hidden;  margin: 0;}

JAVASCRIPT:

import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";import { mergeGeometries, mergeVertices } from "three/addons/utils/BufferGeometryUtils.js";import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';

console.clear();

// load fontsawait (async function () { async function loadFont(fontface) { await fontface.load(); document.fonts.add(fontface); } let fonts = [ new FontFace( "UnifrakturMaguntia", "url(https://fonts.gstatic.com/s/unifrakturmaguntia/v20/WWXPlieVYwiGNomYU-ciRLRvEmK7oaVemGZM.woff2)" ) ]; for (let font in fonts) { //console.log(fonts[font]); await loadFont(fonts[font]); }})();

class Cup extends THREE.Mesh{ constructor(){ let profile = new THREE.Path() .moveTo(0, 0.1) .lineTo(4.4, 0.1) .lineTo(4.4, 0.1) .absarc(4.4, 0, 0.1, Math.PI * 0.5, 0, true) .lineTo(4.5, 0) .lineTo(4.75, 0) .absarc(4.75, 0.25, 0.25, Math.PI * 0.25, 0) .lineTo(5, 0.25) .lineTo(5, 10) .lineTo(5, 10) .absarc(4.75, 10, 0.25, 0, Math.PI) .lineTo(4.5, 10) .lineTo(4.5, 0.7) .lineTo(4.5, 0.7) .absarc(4.4, 0.7, 0.1, 0, -Math.PI * 0.5, true) .lineTo(4.4, 0.6) .lineTo(0, 0.6) .getPoints(200) let gCup = new THREE.LatheGeometry(profile, 200); // re-compute uvs let pos = gCup.attributes.position; let uvs = gCup.attributes.uv; let nor = gCup.attributes.normal; let n = new THREE.Vector3(); let p = new THREE.Vector3(); let v = new THREE.Vector2(); let t = new THREE.Vector2(); for(let i = 0; i < pos.count; i++){ p.fromBufferAttribute(pos, i); n.fromBufferAttribute(nor, i); v.set(p.x, p.z).normalize(); t.set(n.x, n.z).normalize(); if(v.dot(t) > 0.99){ let uvV = (p.y - 0.2) / 9.8; let uvVClamp = THREE.MathUtils.clamp(uvV, 0, 1); uvs.setY(i, uvV); if (uvV < 0 || uvV > 1) uvs.setXY(i, 0, 0); } else { uvs.setXY(i, 0, 0); } } gCup.rotateY(-Math.PI * 0.5); // handle let gHandle = new THREE.ExtrudeGeometry( new THREE.Shape().absellipse(0, 0, 0.75, 0.3, 0, Math.PI * 2), { steps: 100, bevelEnabled: true, bevelSize: 0, bevelThickness: 0.5, bevelSegments: 10, extrudePath: new THREE.CatmullRomCurve3([ [4.725, 8], [5.5, 9], [7, 9], [8, 5], [6, 3], [4.75, 2] ].map(p => {return new THREE.Vector3(p[0], p[1], 0)})) } ) gHandle.deleteAttribute("uv"); gHandle.deleteAttribute("normal"); gHandle = mergeVertices(gHandle); gHandle.computeVertexNormals(); gHandle.setAttribute("uv", new THREE.Float32BufferAttribute(new Array(gHandle.attributes.position.count * 2).fill(0), 2)); console.log(gCup, gHandle) let g = mergeGeometries([gCup, gHandle]); let m = new THREE.MeshPhysicalMaterial({ metalness: 0, roughness: 1, clearcoat: 0.5, // wireframe: true }); super(g, m); let canvas = document.createElement("canvas"); canvas.width = 512; canvas.height = 512; canvas.unit = value => 0.01 * canvas.height * value; this.ctx = canvas.getContext("2d"); this.texture = new THREE.CanvasTexture(canvas); m.map = this.texture; //m.map = new THREE.TextureLoader().load("https://threejs.org/examples/textures/uv_grid_opengl.jpg"); m.map.colorSpace = THREE.SRGBColorSpace; m.map.wrapS = THREE.RepeatWrapping; m.map.wrapT = THREE.RepeatWrapping; m.map.repeat.set( 2, 1 ); this.bubbles = Array.from({length:20}, () => { let pos = new THREE.Vector2().random().subScalar(0.5).multiplyScalar(100); return { position: new THREE.Vector3().copy(pos), posInit: new THREE.Vector3().copy(pos), size: Math.random() * 4 + 1, speed: Math.random() * 10 + 10 } }) //this.update(0); // cider let gCider = new THREE.LatheGeometry( new THREE.Path() .moveTo(0, 0) .lineTo(4.4) .absellipse(4.4, 0.05, 0.1, 0.05, -Math.PI * 0.5, 0) .getPoints(50).reverse(), 200).translate(0, 8.5, 0); let mCider = new THREE.MeshPhysicalMaterial({ color: "#da995f", transparent: true, metalness: 0, roughness: 0, ior: 1.25, transmission: 1, thickness: 0.05, clearcoat: 1 }); let cider = new THREE.Mesh(gCider, mCider); this.add(cider); } update(t){ let ctx = this.ctx; let unit = ctx.canvas.unit; ctx.fillStyle = "#006400"; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.save(); ctx.translate(unit(50), unit(50)); ctx.scale(1 / (Math.PI * 0.5), 1); ctx.rotate(Math.PI / -6); ctx.font = `${unit(50)}px UnifrakturMaguntia`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; let text = "Cidre"; ctx.fillStyle = "#f40"; ctx.fillText(text, unit(1), 0); ctx.fillStyle = "#fff"; ctx.fillText(text, 0, 0); ctx.restore(); ctx.save(); ctx.translate(unit(50), unit(50)); ctx.scale(1 / (Math.PI * 0.5), 1); this.bubbles.forEach(bubble => { let y = -50 + (bubble.posInit.y + t * bubble.speed + 50) % 100; ctx.lineWidth = unit(1); let a = THREE.MathUtils.smoothstep(y, -50, -30); ctx.strokeStyle = `rgba(0, 159, 0, ${a})`; ctx.beginPath(); ctx.arc(unit(bubble.position.x), unit( -y), unit(bubble.size), 0, Math.PI * 2); ctx.stroke(); }) ctx.restore(); ctx.strokeStyle = "#7EA94C"; ctx.lineWidth = unit(2); ctx.strokeRect(unit(0), unit(0), unit(100), unit(100)); this.texture.needsUpdate = true; }}

let scene = new THREE.Scene();let camera = new THREE.PerspectiveCamera(30, innerWidth / innerHeight, 1, 100);camera.position.set(0, 2, 8).setLength(30);let renderer = new THREE.WebGLRenderer({antialias: true});renderer.setSize(innerWidth, innerHeight);document.body.appendChild(renderer.domElement);

window.addEventListener("resize", event => { camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth, innerHeight);})

let camShift = new THREE.Vector3(0, 5, 0);camera.position.add(camShift);let controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.target.copy(camShift);

const pmremGenerator = new THREE.PMREMGenerator( renderer );scene.background = new THREE.Color( 0xFFE5B4 );scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;

let light = new THREE.DirectionalLight(0xffffff, Math.PI);light.position.setScalar(1);scene.add(light, new THREE.AmbientLight(0xffffff, Math.PI * 0.5));

let cup = new Cup();scene.add(cup);

let clock = new THREE.Clock();let t = 0;

renderer.setAnimationLoop(() => { let dt = Math.min(clock.getDelta(), 1 / 60); t += dt; controls.update(); cup.update(t); renderer.render(scene, camera);});



源码:

https://codepen.io/prisoner849/pen/poBwLRG


体验:

https://codepen.io/prisoner849/full/poBwLRG



感谢您的阅读      

在看点赞 好文不断  

初识Threejs
初识 Three.js 的奇妙世界,走进三维空间,与小编一起拥抱前端技术,涉及WebGL、WebGPU、Threejs、Shader、GIS、VR/AR、数字孪生、3D图形学等。
 最新文章