View Icon ۳۹

ساعت دیجیتال سه‌بعدی تعاملی با استفاده از لایبرری Three.js و رابط WebGL

... Loading
Post Pic

یک ساعت دیجیتال سه‌بعدی با قابلیت تعامل که با استفاده از Three.js و WebGL پیاده‌سازی شده است. این برنامه شامل یک ساعت دیجیتال با طراحی پیچیده، جلوه‌های نوری و پس‌پردازش‌های ویژه است که به صورت بلادرنگ با استفاده از شیدرهای گرافیکی و ورودی‌های کاربر به نمایش در می‌آید. همچنین بک گراند نیز متحرک است و همانطور که در پیش نمایش مشخص است در ساعت قابلیت چرخش دوربین و کنترل‌های تعاملی برای تجربه بصری بهتر فراهم شده است. شماره کد: 4

منبع کد
HTML

CSS
body{
overflow: hidden;
margin: 0;
}
JS
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 fonts
await (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 `,
`#include 
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 `,
`#include 
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);
});
HTML
47
CSS
4
JS
506

CM
(۰)
Copy link