Three.js 之 JS 啤酒 - 万圣节版

文化   2024-11-05 07:02   日本  

使用 Three.js 的 WebGL 小实验。JS 啤酒 - 万圣节版。


实现代码

HTML:

<iframe id="player" style="border: 0; width: 300px; height: 42px;" src="https://bandcamp.com/EmbeddedPlayer/album=996940111/size=small/bgcol=ffffff/linkcol=0687f5/track=2161815634/transparent=true/" seamless><a href="https://dbfiechter.bandcamp.com/album/night-at-the-carnival-ii">Night at the Carnival II by Derek &amp; Brandon Fiechter</a></iframe><script type="importmap">  {    "imports": {      "three": "https://unpkg.com/three@0.169.0/build/three.webgpu.js",      "three/addons/": "https://unpkg.com/three@0.169.0/examples/jsm/"    }  }</script><!--canvas id="cnv"></anvas-->
CSS:
body{  overflow: hidden;  margin: 0;}

#player { /*display: none;*/ position: absolute; bottom: 0; margin: 10px; border: 1px solid #080; border-radius: 20px; opacity: 0.1;}#player:hover{ opacity: 1;}

JAVASCRIPT:

import * as THREE from "three";import {  color,  pmremTexture,  mul, div, sub, add, floor,  mix, smoothstep, abs, max, step,  pow, clamp, normalize, negate,  oneMinus, dot} from "three";import { texture, uv, vec2, vec3, vec4, assign } from "three";import { pass, mrt, output, bloom, emissive } from "three";import {  Fn,  mx_noise_float,  mx_noise_vec3,  mx_fractal_noise_float} from "three";import {  normalWorld,  normalView,  positionView,  positionGeometry,  positionLocal,  normalGeometry,  timerLocal} from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";

import { RGBELoader } from "three/addons/loaders/RGBELoader.js";

console.clear();

// load fontsawait (async function () { async function loadFont(fontface) { await fontface.load(); document.fonts.add(fontface); } let fonts = [ new FontFace( "Sancreek", "url(https://fonts.gstatic.com/s/sancreek/v25/pxiHypAnsdxUm159X4D5V14.woff2) format('woff2')" ) ]; for (let font in fonts) { await loadFont(fonts[font]); }})();

class Postprocessing extends THREE.PostProcessing { constructor(renderer) { const scenePass = pass(scene, camera); scenePass.setMRT( mrt({ output, emissive }) );

const outputPass = scenePass.getTextureNode(); const emissivePass = scenePass.getTextureNode("emissive");

const bloomPass = bloom(emissivePass, 0.1, 0);

super(renderer); this.outputNode = outputPass.add(bloomPass); }}

class Bottle extends THREE.Mesh { constructor() { let bottlePath = new THREE.Path() .moveTo(0, 0) .lineTo(2, 0) .absarc(2, 0.5, 0.5, Math.PI * 1.5, 0)

.lineTo(2.5, 5) .bezierCurveTo(2.5, 7.5, 0.5, 5.5, 0.5, 10) .absarc(0.5, 10.25, 0.125, Math.PI * 1.5, Math.PI * 3)

.absarc(0, 6.5, 0.5, 0, Math.PI * 1.5, true);

let g = new THREE.LatheGeometry(bottlePath.getPoints(100), 144).rotateY( Math.PI * -0.5 ); // re-compute uvs let pos = g.attributes.position; for (let i = 0; i < pos.count; i++) { let y = pos.getY(i) - 0.5; let v = y / 5; g.attributes.uv.setY(i, v); }

let beerJSTex = (() => { let c = document.createElement("canvas"); c.width = c.height = 1024; let u = (val) => c.height * 0.01 * val;

let ctx = c.getContext("2d"); ctx.textAlign = "center"; ctx.textBaseline = "middle";

ctx.fillStyle = "rgba(255, 255, 255, 1)"; ctx.font = `${u(15)}px Sancreek`; ctx.fillText("JS", u(50), u(45)); ctx.font = `${u(30)}px Sancreek`; ctx.fillText("BEER", u(50), u(75));

// eyes let eye = (flip) => { ctx.save(); ctx.translate(u(50), u(50)); ctx.scale(flip, 1); ctx.translate(u(10), u(-22)); ctx.beginPath(); ctx.moveTo(0, 0); ctx.quadraticCurveTo(u(10), -u(5), u(25), u(-24)); ctx.quadraticCurveTo(u(25), u(25), 0, 0); ctx.fill(); ctx.restore(); }; eye(1); eye(-1);

let tex = new THREE.CanvasTexture(c); tex.colorSpace = "srgb"; tex.anisotropy = 8; return tex; })(); let beerJSBackTex = (() => { let c = document.createElement("canvas"); c.width = c.height = 1024; let u = (val) => c.height * 0.01 * val;

let ctx = c.getContext("2d"); ctx.textAlign = "center"; ctx.textBaseline = "middle";

ctx.fillStyle = "rgba(255, 255, 255, 1)"; ctx.font = `${u(5)}px Sancreek`; ctx.fillText("Shake thoroughly to blast!", u(50), u(10)); ctx.fillText("Ingredients:", u(50), u(25)); ctx.font = `${u(15)}px Sancreek`; ctx.fillText("☢ ☣ ⚠", u(50), u(40)); ctx.fillText("♡ ☠", u(50), u(55)); ctx.font = `${u(5)}px Sancreek`; ctx.fillText("♤ Drink at your own risk ♤", u(50), u(75)); ctx.font = `${u(3)}px Sancreek`; ctx.fillText("BeerJS Brewery", u(50), u(90)); let tex = new THREE.CanvasTexture(c); tex.colorSpace = "srgb"; tex.anisotropy = 8; return tex; })(); let m = new THREE.MeshStandardNodeMaterial({ roughness: 0.4, metalness: 0.9 }); // TSL let fillLevel = smoothstep(1.25, 1.75, uv().y).toVar(); m.colorNode = mix(color(0x00aa44), color(0x006600), fillLevel); let texFront = texture( beerJSTex, uv().sub(vec2(0.25, 0.5)).mul(vec2(Math.PI, 1)).add(0.5)).a.toVar(); let texBackUV = uv().sub(vec2(0.75, 0.5)).mul(vec2(Math.PI, 1)).add(0.5).toVar(); let texBack = texture( beerJSBackTex, texBackUV).a.toVar(); let finalTexVal = max(texFront, texBack).toVar(); m.emissiveNode = finalTexVal.mul(color(0xff6600).mul(5)); /////

super(g, m);

// vapour let vapourG = new THREE.LatheGeometry( new THREE.Path() .moveTo(2.25, 5.5) .splineThru([ [2, 6.5], [1.5, 8], [1, 10], [0, 11] ].map(p => new THREE.Vector2(...p))) .getSpacedPoints(100), 72).rotateY(Math.PI); let vapourM = new THREE.MeshBasicNodeMaterial({ //wireframe: true, side: THREE.DoubleSide, depthWrite: false, transparent: true }); // TSL let timer = timerLocal(); vapourM.positionNode = Fn(() => { let posNoise = positionGeometry.add(vec3(0, timer, 0)).mul(0.5).toVar(); let noise = mx_noise_float(posNoise).mul(0.5).add(0.5);

return positionGeometry.add(normalGeometry.mul(noise).mul(0.75)); })(); vapourM.colorNode = Fn(() => { let alpha = smoothstep(0, 0.9, uv().y) .pow(2) .toVar();

let colorNoiseFade = smoothstep(0, 0.9, uv().y).toVar(); let colorNoise = mx_fractal_noise_float( vec3( uv() .mul(vec2(5, 1.5)) .add(vec2(0, timer.mul(0.5))), timer.mul(0.1))) .abs() .oneMinus() .pow(8) .mul(colorNoiseFade) .toVar(); alpha.assign(clamp(alpha.add(colorNoise), 0, 1).mul(smoothstep(0.8, 1, uv().y).oneMinus())); let smoothing = smoothstep(0., 1, dot(abs(normalView), normalize(positionView).negate())).toVar();

return vec4(color(0x008800), alpha.mul(0.75).mul(smoothing)); })(); ///// let vapour = new THREE.Mesh(vapourG, vapourM); this.add(vapour); }}

let scene = new THREE.Scene();scene.backgroundNode = color(0x000000);let camera = new THREE.PerspectiveCamera(30, innerWidth / innerHeight, 1, 1000);camera.position.set(0, 0.35, 1).setLength(22.5);let renderer = new THREE.WebGPURenderer({ antialias: true });renderer.setPixelRatio(devicePixelRatio);renderer.setSize(innerWidth, innerHeight);renderer.toneMapping = THREE.ACESFilmicToneMapping;document.body.appendChild(renderer.domElement);

let postprocessing = new Postprocessing(renderer);

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

let camShift = new THREE.Vector3(0, 5.25, 0);camera.position.add(camShift);let controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.autoRotate = true;controls.autoRotateSpeed *= -1;controls.minPolarAngle = Math.PI * 0.25;controls.maxPolarAngle = Math.PI * 0.75;controls.target.copy(camShift);

let bgTexture = new RGBELoader() .setPath("https://threejs.org/examples/textures/equirectangular/") .load("royal_esplanade_1k.hdr", function (tex) { tex.mapping = THREE.EquirectangularReflectionMapping; scene.environment = tex; scene.backgroundNode = pmremTexture(tex, normalWorld, 0.5).mul(0.1); });

let light = new THREE.AmbientLight(0xffffff, Math.PI);//scene.add(light);

let bottle = new Bottle();bottle.rotation.y = Math.PI * 0.25;scene.add(bottle);

renderer.setAnimationLoop(() => { controls.update(); postprocessing.render();});

源码:

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


体验:

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



感谢您的阅读      

在看点赞 好文不断  

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