Three.js 数字时钟

文化   2024-10-06 09:02   河南  

使用 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><script>  let snoise2d = `// Simplex 2D noise//vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); }

float snoise(vec2 v){ const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); vec2 i = floor(v + dot(v, C.yy) ); vec2 x0 = v - i + dot(i, C.xx); vec2 i1; i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1; i = mod(i, 289.0); vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) + i.x + vec3(0.0, i1.x, 1.0 )); vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); m = m*m ; m = m*m ; vec3 x = 2.0 * fract(p * C.www) - 1.0; vec3 h = abs(x) - 0.5; vec3 ox = floor(x + 0.5); vec3 a0 = x - ox; m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); vec3 g; g.x = a0.x * x0.x + h.x * x0.y; g.yz = a0.yz * x12.xz + h.yz * x12.yw; return 130.0 * dot(m, g);}

float udSegment( in vec2 p, in vec2 a, in vec2 b ){ vec2 ba = b-a; vec2 pa = p-a; float h =clamp( dot(pa,ba)/dot(ba,ba), 0.0, 1.0 ); return length(pa-h*ba);}`;</script>
CSS:
body{  overflow: hidden;  margin: 0;}

JAVASCRIPT:

import * as THREE from "three";import { OrbitControls } from "three/addons/controls/OrbitControls.js";import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";

import { EffectComposer } from "three/addons/postprocessing/EffectComposer.js";import { RenderPass } from "three/addons/postprocessing/RenderPass.js";import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js";import { OutputPass } from "three/addons/postprocessing/OutputPass.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( "KodeMono", "url(https://fonts.gstatic.com/s/kodemono/v1/A2BYn5pb0QgtVEPFnlYOnYLw.woff2) format('woff2')" ) ]; for (let font in fonts) { //console.log(fonts[font]); await loadFont(fonts[font]); }})();

class GlowLayer extends EffectComposer { constructor(renderer) { const target = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, { type: THREE.HalfFloatType, format: THREE.RGBAFormat, colorSpace: THREE.SRGBColorSpace, samples: 8 } ); super(renderer, target); const renderScene = new RenderPass(scene, camera);

const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 0.1, 0, 5 );

const outputPass = new OutputPass();

this.uniforms = { aspect: { value: camera.aspect } }; outputPass.material.onBeforeCompile = (shader) => { shader.uniforms.aspect = this.uniforms.aspect; shader.uniforms.watermark = {value: (function () { let c = document.createElement("canvas"); c.width = 1024; c.height = 128; let $ = c.getContext("2d"); $.clearRect(0, 0, c.width, c.height); $.font = `bold ${c.height * 0.5}px KodeMono`; $.textAlign = "left"; $.textBaseline = "middle";

$.fillStyle = "#fff"; $.fillText("849 # Die Digitaluhr", c.width * 0.01, c.height * 0.5);

let cTex = new THREE.CanvasTexture(c); cTex.colorSpace = THREE.SRGBColorSpace; return cTex; })() } shader.fragmentShader = ` ${shader.fragmentShader} ` .replace( `precision highp float;`, `precision highp float; uniform float aspect; uniform sampler2D watermark; ` ) .replace( `}`, ` // signature vec2 sUv = vUv * 14. * vec2(aspect / 8., 1.); sUv -= vec2(0.05, 0.05); float sig = texture2D(watermark, sUv).r; vec3 sigColor = vec3(0.75); //////////// gl_FragColor.rgb = mix(gl_FragColor.rgb, sigColor, sig); } ` ); //console.log(shader.fragmentShader) }; this.addPass(renderScene); this.addPass(bloomPass); this.addPass(outputPass);

//console.log(outputPass); }}

class Logo extends THREE.BufferGeometry { constructor() { super();

let baseVector = new THREE.Vector2(0, 1); let center = new THREE.Vector2(); let shift = new THREE.Vector2(0, -0.25); let a = 3 / Math.sqrt(3); let hA = a * 0.5; let hStep = 1.5; let hHeight = 0.75; let steps = 4; let scale = 0.85; let baseTri = [ baseVector.clone().multiplyScalar(scale).add(shift), baseVector .clone() .rotateAround(center, (-Math.PI * 2) / 3) .multiplyScalar(scale) .add(shift), baseVector .clone() .rotateAround(center, (Math.PI * 2) / 3) .multiplyScalar(scale) .add(shift) ];

let baseTriFlip = [ baseVector .clone() .rotateAround(center, Math.PI) .multiplyScalar(scale) .sub(shift), baseVector .clone() .rotateAround(center, Math.PI / 3) .multiplyScalar(scale) .sub(shift), baseVector .clone() .rotateAround(center, -Math.PI / 3) .multiplyScalar(scale) .sub(shift) ];

let holes = [];

for (let rows = 0; rows < steps; rows++) { let items = 1 + rows * 2; // arithmetic progression

let h = hStep * 1.5 - rows * hStep; console.log(h); let w = -((items - 1) / 2) * hA;

for (let item = 0; item < items; item++) { let shiftX = w + hA * item; let shiftY = h;

let tri = (item % 2 == 0 ? baseTri : baseTriFlip).map((p) => { let pt = p.clone(); pt.x += shiftX; pt.y += shiftY; return pt; });

let hole = new THREE.Path(tri); holes.push(hole); } }

let contourShift = new THREE.Vector2(0, -1); let contour = [ baseVector.clone().multiplyScalar(4.1).add(contourShift), baseVector .clone() .rotateAround(center, (Math.PI * 2) / 3) .multiplyScalar(4.1) .add(contourShift), baseVector .clone() .rotateAround(center, (-Math.PI * 2) / 3) .multiplyScalar(4.1) .add(contourShift) ]; let shape = new THREE.Shape(contour); shape.holes = holes;

let shapeGeom = new THREE.ExtrudeGeometry(shape, { depth: 0.1, bevelEnabled: true, bevelThickness: 0.1, bevelSize: 0.1, bevelSegments: 5 }); shapeGeom.rotateZ(Math.PI * 0.25); shapeGeom.center();

this.copy(shapeGeom); }}

class Display extends THREE.InstancedMesh { constructor(thickness) { let g = new THREE.PlaneGeometry(5, 7.5); let m = new THREE.MeshBasicMaterial({ color: new THREE.Color(0x0088ff), map: null, side: THREE.DoubleSide, forceSinglePass: true, transparent: true }); super(g, m, 10); this.thickness = thickness; console.log(this); this.init(); }

init() { let dummy = new THREE.Object3D(); for (let i = 0; i < this.count; i++) { dummy.position.z = i * (this.thickness / (this.count - 1)); dummy.updateMatrix(); this.setMatrixAt(i, dummy.matrix); }

let canvas = document.createElement("canvas"); canvas.width = 1000; canvas.height = 1400; this.context = canvas.getContext("2d");

this.days = [ "SONNTAG", "MONTAG", "DIENSTAG", "MITTWOCH", "DONNERSTAG", "FREITAG", "SAMSTAG" ];

this.displayTexture = new THREE.CanvasTexture(canvas); this.material.map = this.displayTexture; this.material.onBeforeCompile = (shader) => { shader.vertexShader = ` varying float viID; ${shader.vertexShader} `.replace( `#include <begin_vertex>`, `#include <begin_vertex> float iID = float(gl_InstanceID); viID = iID; ` ); //console.log(shader.vertexShader); shader.fragmentShader = ` varying float viID; ${shader.fragmentShader} `.replace( `vec4 diffuseColor = vec4( diffuse, opacity );`, `vec4 diffuseColor = vec4( diffuse, opacity ); float aa = viID / ${this.count - 1}.; aa *= aa * aa * aa; diffuseColor.a = aa; float iID = floor(viID + 0.1); diffuseColor.rgb *= iID == ${this.count - 1}. ? 20. : 1.; if ((!gl_FrontFacing) && (iID < ${this.count - 1}.)) discard; ` ); //console.log(shader.fragmentShader); }; //this.material.needsUpdate = true; }

update() { let date = new Date(); //console.log(date); let ctx = this.context;

ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = "rgba(255, 255, 255, 1)";

ctx.textAlign = "center"; ctx.textBaseline = "middle";

let DoW = this.days[date.getDay()]; ctx.font = "bold 75px KodeMono"; ctx.fillText(DoW, ctx.canvas.width * 0.5, ctx.canvas.height * 0.3);

let days = ("0" + date.getDate()).slice(-2); let month = ("0" + (date.getMonth() + 1)).slice(-2); let year = date.getFullYear(); let fullDate = days + "." + month + "." + year; ctx.font = "bold 100px KodeMono"; ctx.fillText(fullDate, ctx.canvas.width * 0.5, ctx.canvas.height * 0.4);

let hours = ("0" + date.getHours()).slice(-2); let minutes = ("0" + date.getMinutes()).slice(-2); let milliseconds = date.getMilliseconds(); let time = hours + (milliseconds < 500 ? ":" : " ") + minutes;

ctx.font = "bold 300px KodeMono"; ctx.fillText(time, ctx.canvas.width * 0.5, ctx.canvas.height * 0.6);

/* ctx.strokeStyle = "rgba(255, 255, 255, 1)"; ctx.lineWidth = 5; ctx.strokeRect(0, 0, ctx.canvas.width, ctx.canvas.height); */

this.displayTexture.needsUpdate = true; }}

class Digitaluhr extends THREE.Mesh { constructor() { let axis = new THREE.Vector3(0, 0, 1); let baseContour = [ [0.1, 2], [1, 0], [0, -1], [-1, 0], [-0.1, 2] ].map((p) => { return new THREE.Vector3(p[0], p[1], 0).multiplyScalar(5); //.applyAxisAngle(axis, Math.PI * 0.25); });

let thickness = 1.25;

// extrusion let shapeCurve = new THREE.CatmullRomCurve3([...baseContour], true); let holeCurve = new THREE.CatmullRomCurve3( [...baseContour].map((p) => { return p.clone().multiplyScalar(0.5); }), true ); let shape = new THREE.Shape(shapeCurve.getSpacedPoints(1000)); shape.holes.push(new THREE.Path(holeCurve.getSpacedPoints(1000).reverse()));

let gExtrude = new THREE.ExtrudeGeometry(shape, { depth: thickness, steps: 1, bevelEnabled: false }); let mExtrude = new THREE.MeshLambertMaterial({ color: new THREE.Color().setHSL(0.33, 1, 1) });

let goldMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000, metalness: 1, roughness: 0.25 });

super(gExtrude, [mExtrude, goldMaterial]); this.position.y = 5.2; this.position.z = -thickness * 0.5; //this.rotation.x = Math.PI * -0.5; this.castShadow = true; this.receiveShadow = true;

let gOutline = mergeGeometries([ new THREE.TubeGeometry(shapeCurve, 1000, 0.2, 16, true), new THREE.TubeGeometry(shapeCurve, 1000, 0.2, 16, true).translate( 0, 0, thickness ), new THREE.TubeGeometry(holeCurve, 1000, 0.2, 16, true), new THREE.TubeGeometry(holeCurve, 1000, 0.2, 16, true).translate( 0, 0, thickness ) ]); let outline = new THREE.Mesh(gOutline, goldMaterial); outline.castShadow = true; outline.receiveShadow = true; this.add(outline);

let logo = new THREE.Mesh(new Logo(), goldMaterial); logo.scale.setScalar(0.4); logo.position.set(0, 7, thickness); logo.castShadow = true; this.add(logo);

this.display = new Display(thickness); this.display.position.y = 1.125; this.add(this.display); //display.update(); }}

class Background extends THREE.Mesh { constructor(backcolor) { let g = new THREE.SphereGeometry(900, 64, 32); let m = new THREE.MeshBasicMaterial({ color: backcolor, side: THREE.BackSide }); m.defines = { USE_UV: "" }; super(g, m); this.uniforms = { time: { value: 0 } }; this.material.onBeforeCompile = (shader) => { shader.uniforms.time = this.uniforms.time; shader.fragmentShader = ` uniform float time; ${snoise2d} ${shader.fragmentShader} `.replace( `vec4 diffuseColor = vec4( diffuse, opacity );`, ` float t = time * 0.1; vec2 uv = vUv; uv -= 0.5; vec2 uvMult = vec2(40., 4.); uv *= uvMult; uv.y += 0.33; uv.y = abs(uv.y);

vec2 cId = floor(uv); vec2 cUv = fract(uv); cUv.x -= 0.5; cUv.x /= 5.;

float rnd = snoise(cId + vec2(0., t)) * 0.5 + 0.5; float baseVal = 0.1; float baseRand = baseVal + rnd * 0.5; float rounding = 0.1; vec2 a = vec2(0., baseVal); vec2 b = vec2(0., baseRand);

float f = udSegment(cUv, a, b) - rounding;

vec2 fw = fwidth(cUv); float mf = min(fw.x, fw.y); float df = smoothstep(mf, -mf, f);

float circf = length(vec2(0., baseRand + 0.3) - cUv) - rounding * 0.5; float circ = smoothstep(mf, -mf, circf);

df = max(df, circ);

df *= step(0., cId.y) - step(1., cId.y); // first row only

vec3 col = mix(diffuse, diffuse + 0.005, df); vec4 diffuseColor = vec4( col, opacity );

` ); //console.log(shader.fragmentShader) }; }

update(t) { this.uniforms.time.value = t; }}

let scene = new THREE.Scene();scene.background = new THREE.Color(0x111111);let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 1000);camera.position.set(-5, 2, 8).setLength(20);let renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(innerWidth, innerHeight);renderer.shadowMap.enabled = true;renderer.shadowMap.type = THREE.PCFSoftShadowMap;document.body.appendChild(renderer.domElement);

let cameraShift = new THREE.Vector3(0, 8, 0);

let glowLayer = new GlowLayer(renderer);

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

let controls = new OrbitControls(camera, renderer.domElement);controls.enablePan = false;controls.enableDamping = true;controls.minDistance = 10;controls.maxDistance = 25;controls.maxPolarAngle = Math.PI * 0.5;

controls.target.copy(cameraShift);controls.object.position.add(cameraShift);controls.update();

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

let light = new THREE.DirectionalLight(0xffffff, Math.PI);light.castShadow = true;light.shadow.mapSize.width = 2048;light.shadow.mapSize.height = 2048;light.shadow.camera.near = 0.5;light.shadow.camera.far = 100;let hSize = 7;light.shadow.camera.top = hSize;light.shadow.camera.bottom = -hSize;light.shadow.camera.left = -hSize;light.shadow.camera.right = hSize;light.position.setScalar(20).add(cameraShift);let lightTarget = new THREE.Object3D();scene.add(lightTarget);lightTarget.position.copy(cameraShift);light.target = lightTarget;scene.add(light, new THREE.AmbientLight(0xffffff, 0.5));

//let grid = new THREE.GridHelper(10, 10);//grid.rotation.x = Math.PI * 0.5;//scene.add(grid);

let digitaluhr = new Digitaluhr();scene.add(digitaluhr);

let surface = new THREE.Mesh( new THREE.CircleGeometry(20, 72).rotateX(-Math.PI * 0.5), new THREE.MeshLambertMaterial({ color: 0x404040, transparent: true, onBeforeCompile: (shader) => { shader.uniforms.outerShade = { value: scene.background }; shader.fragmentShader = ` uniform vec3 outerShade; ${shader.fragmentShader} `.replace( `#include <dithering_fragment>`, `#include <dithering_fragment> float os = smoothstep(0.3, 0.5, length(vUv - 0.5)); gl_FragColor.a = 1. - os; //mix(gl_FragColor.rgb, outerShade, os); ` ); //console.log(shader.fragmentShader); } }));surface.material.defines = { USE_UV: "" };surface.receiveShadow = true;scene.add(surface);

let background = new Background(scene.background);scene.add(background);

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

renderer.setAnimationLoop(() => { let dt = clock.getDelta(); t += dt; controls.update(); digitaluhr.display.update(); background.update(t); glowLayer.render(); //renderer.render(scene, camera);});



源码:

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


体验:

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



感谢您的阅读      

在看点赞 好文不断  

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