<script async src="https://ga.jspm.io/npm:es-module-shims@1.5.1/dist/es-module-shims.js" crossorigin="anonymous"></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.148.0/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.148.0/examples/jsm/"
}
}
</script>
body{
overflow: hidden;
margin: 0;
}
import * as THREE from "three";
import {OrbitControls} from "three/addons/controls/OrbitControls.js";
import {mergeBufferGeometries} from "three/addons/utils/BufferGeometryUtils.js";
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from "three/addons/postprocessing/ShaderPass.js";
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
console.clear();
class Postprocessing {
constructor(scene, camera, renderer) {
const renderScene = new RenderPass(scene, camera);
const bloomPass = new UnrealBloomPass(
new THREE.Vector2(window.innerWidth, window.innerHeight),
1.25,
0.25,
0
);
let samples = 4;
const target1 = new THREE.WebGLRenderTarget(
window.innerWidth,
window.innerHeight,
{
type: THREE.FloatType,
format: THREE.RGBAFormat,
encoding: THREE.sRGBEncoding,
samples: samples
}
);
this.bloomComposer = new EffectComposer(renderer, target1);
this.bloomComposer.renderToScreen = false;
this.bloomComposer.addPass(renderScene);
this.bloomComposer.addPass(bloomPass);
const finalPass = new ShaderPass(
new THREE.ShaderMaterial({
uniforms: {
baseTexture: { value: null },
bloomTexture: { value: this.bloomComposer.renderTarget2.texture }
},
vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`,
fragmentShader: `uniform sampler2D baseTexture; uniform sampler2D bloomTexture; varying vec2 vUv; void main() { gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( bloomTexture, vUv ) ); }`,
defines: {}
}),
"baseTexture"
);
finalPass.needsSwap = true;
const target2 = new THREE.WebGLRenderTarget(
window.innerWidth,
window.innerHeight,
{
type: THREE.FloatType,
format: THREE.RGBAFormat,
encoding: THREE.sRGBEncoding,
samples: samples
}
);
this.finalComposer = new EffectComposer(renderer, target2);
this.finalComposer.addPass(renderScene);
this.finalComposer.addPass(finalPass);
}
}
class LightEmitterCurve extends THREE.Curve{
constructor( radius, turns, height ) {
super();
this.radius = radius;
this.height = height;
this.turns = turns;
}
getPoint( t, optionalTarget = new THREE.Vector3() ) {
return optionalTarget.setFromCylindricalCoords( this.radius, -Math.PI * 2 * this.turns * t, this.height * t );
}
}
class LightEmitters extends THREE.Object3D{
constructor(gu, count, maxR, height, turns, m){
super();
let gsBall = [];
let gsEmitter = [];
let start = maxR / 4;
let totalWidth = maxR * 0.9 - start;
let step = totalWidth / (count - 1);
let v3 = new THREE.Vector3();
let axis = new THREE.Vector3(0, 1, 0);
for( let i = 0; i < count; i++){
let shift = start + step * i;
let gBall = new THREE.SphereGeometry(0.05, 64, 32, 0, Math.PI * 2, 0, Math.PI * 0.5);
gBall.translate(0, 0, shift);
gsBall.push(gBall);
let lightEmitterCurve = new LightEmitterCurve(shift, turns, height);
let gEmitter = new THREE.TubeGeometry(lightEmitterCurve, 200, 0.02, 16);
gsEmitter.push(gEmitter);
}
let gBalls = mergeBufferGeometries(gsBall);
let balls = new THREE.Mesh(gBalls, m.clone());
balls.userData.nonGlowing = true;
//balls.castShadow = true;
this.add(balls);
let gEmitters = mergeBufferGeometries(gsEmitter);
let mEmitters = new THREE.MeshBasicMaterial({
side: THREE.DoubleSide,
color: new THREE.Color(1, 0.25, 0),
onBeforeCompile: shader => {
shader.uniforms.globalBloom = gu.globalBloom;
shader.vertexShader = `
varying vec3 vPos;
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
vPos = position;
`
);
//console.log(shader.vertexShader)
shader.fragmentShader = `
#define ss(a, b, c) smoothstep(a, b, c)
uniform float globalBloom;
varying vec3 vPos;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
vec3 colNonGlow = vec3(1, 0.75, 0.75); // * pow((sin(vUv.x * 200. * PI2) * 0.5 + 0.5) * 0.6 + 0.4, 3.);
vec3 colGlow = gl_FragColor.rgb;
gl_FragColor.rgb = mix(colNonGlow, colGlow, globalBloom);
`
);
}
});
mEmitters.defines = {"USE_UV" : ""};
let emitters = new THREE.Mesh(gEmitters, mEmitters);
//emitters.castShadow = true;
this.add(emitters);
}
}
class Belt extends THREE.Mesh{
constructor(gu, mainSize, rBig, rSmall, width, m){
let m1 = m.clone();
m1.color.set("gray");
//m1.side = THREE.DoubleSide;
let hSize = mainSize;
let path = new THREE.Shape()
.absarc(0, 0, rBig, Math.PI * 1.5, Math.PI)
.absarc(-hSize + rSmall, -hSize + rSmall, rSmall, Math.PI, Math.PI * 1.5)
.lineTo(0, -hSize);
const segs = 500;
const hw = width * 0.5;
let pathPts = path.getSpacedPoints(segs).reverse();
/*
let pg = new THREE.BufferGeometry().setFromPoints(pathPts);
let pm = new THREE.PointsMaterial({color: "yellow", size: 0.05});
let pp = new THREE.Points(pg, pm);
*/
let g = new THREE.BoxGeometry(segs, 0.01, width, segs, 1, 1).translate(segs * 0.5, 0.005, 0);
let vPrev = new THREE.Vector2(), vCurr = new THREE.Vector2(), vNext = new THREE.Vector2();
let vCP = new THREE.Vector2(), vCN = new THREE.Vector2(), v2 = new THREE.Vector2(), cntr = new THREE.Vector2();
let pos = g.attributes.position;
for(let i = 0; i < pos.count; i++){
let idxCurr = Math.round(pos.getX(i));
let idxPrev = idxCurr == 0 ? segs - 1 : idxCurr - 1;
let idxNext = idxCurr == segs ? 1 : idxCurr + 1;
vPrev.copy(pathPts[idxPrev]);
vCurr.copy(pathPts[idxCurr]);
vNext.copy(pathPts[idxNext]);
vCP.subVectors(vPrev, vCurr);
vCN.subVectors(vNext, vCurr);
let aCP = vCP.angle();
let aCN = vCN.angle();
let hA = Math.PI * 0.5 - (aCP - aCN) * 0.5;
let aspect = Math.cos(hA);
v2.set(vCurr.x, vCurr.y).multiplyScalar(pos.getY(i) / aspect);
v2.rotateAround(cntr, hA).add(vCurr);
pos.setXY(i, v2.x, v2.y);
}
g.rotateX(-Math.PI * 0.5);
g.computeVertexNormals();
super(g, m1);
this.castShadow = true;
this.receiveShadow = true;
this.uniforms = {
time: {value: 0},
angularSpeed: {value: 0}
}
m1.onBeforeCompile = shader => {
shader.uniforms.globalBloom = gu.globalBloom;
shader.uniforms.time = this.uniforms.time;
shader.uniforms.beltLength = {value: rBig * Math.PI * 1.5 + rSmall * Math.PI * 0.5 + (hSize - rSmall) *2}
shader.uniforms.angularSpeed = this.uniforms.angularSpeed;
shader.uniforms.rSmall = {value: rSmall};
shader.fragmentShader = `
#define ss(a, b, c) smoothstep(a, b, c)
uniform float globalBloom;
uniform float time;
uniform float beltLength;
uniform float angularSpeed;
uniform float rSmall;
${shader.fragmentShader}
`.replace(
`#include <color_fragment>`,
`#include <color_fragment>
float linearSpeed = rSmall * angularSpeed;
float uvX = mod(vUv.x * beltLength + time * linearSpeed, beltLength / 4.);
float f = step(0.25, uvX) - step(0.75, uvX);
diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.875), f);
`
).replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0), globalBloom);
`
);
//console.log(shader.fragmentShader);
}
m1.defines = {"USE_UV" : ""};
//this.add(pp);
}
}
class Roller extends THREE.Mesh{
constructor(r, h, roundR, m){
let m1 = m.clone();
let profile = new THREE.Path()
.moveTo(0, 0)
.lineTo(r - roundR, 0)
.absarc(r - roundR, roundR, roundR, Math.PI * 1.5, Math.PI * 2)
//.moveTo(r, roundR)
//.lineTo(r, h - roundR)
.absarc(r - roundR, h - roundR, roundR, 0, Math.PI * 0.5)
//.moveTo(r - roundR, h)
.lineTo(0, h);
let g = new THREE.LatheGeometry(profile.getPoints(50), 100);
super(g, m1);
this.castShadow = true;
this.receiveShadow = true;
}
}
class Base extends THREE.Mesh{
constructor(w, h, R, roundR, m){
let angleStep = Math.PI * 0.5;
let wwr = w - R - roundR;
let hwrr = h - roundR * 2;
let shape = new THREE.Shape()
.absarc(wwr, wwr, R, angleStep * 0, angleStep * 1)
.absarc(-wwr, wwr, R, angleStep * 1, angleStep * 2)
.absarc(-wwr, -wwr, R, angleStep * 2, angleStep * 3)
.absarc(wwr, -wwr, R, angleStep * 3, angleStep * 4);
let g = new THREE.ExtrudeGeometry(shape, {depth: hwrr, bevelEnabled: true, bevelThickness: roundR, bevelSize: roundR, bevelSegments: 10, curveSegments: 20});
g.translate(0, 0, roundR);
g.rotateX(-Math.PI * 0.5);
super(g, m.clone());
this.castShadow = true;
this.receiveShadow = true;
}
}
class Device extends THREE.Object3D{
constructor(gu){
super();
let m = new THREE.MeshLambertMaterial({color: new THREE.Color().setScalar(0.75)});
let base = new Base(4, 1, 0.5, 0.05, m);
const rBig = 3.75;
const rSmall = 0.5;
let lightEmitters = new LightEmitters(gu, 15, rBig, rBig * 3, 1.25, m);
lightEmitters.position.set(0, 0.25, 0);
let rollerBig = new Roller(rBig, 0.25, 0.05, m);
rollerBig.material.color.multiplyScalar(0.75);
rollerBig.position.set(0, 1, 0);
rollerBig.add(lightEmitters);
base.add(rollerBig);
let rotationIndicator = new THREE.Mesh(new THREE.SphereGeometry(0.05, 64, 16, 0, Math.PI * 2, 0, Math.PI * 0.5), new THREE.MeshBasicMaterial({color: new THREE.Color(0, 0.75, 1)}));
rotationIndicator.position.set(0.35, 0.25, 0);
let rollerSmall = new Roller(rSmall, 0.25, 0.05, m);
rollerSmall.material.color.multiplyScalar(0.75);
rollerSmall.position.set(-3.25, 1, 3.25);
rollerSmall.add(rotationIndicator);
base.add(rollerSmall);
let belt = new Belt(gu, rBig, rBig, rSmall, 0.125, m);
belt.position.set(0, 1.125, 0);
base.add(belt);
this.add(base);
const gearRatio = rBig / rSmall;
const angularSpeed = Math.PI;
belt.uniforms.angularSpeed.value = angularSpeed;
this.update = t => {
let time = t * angularSpeed;
rollerSmall.rotation.y = time;
rollerBig.rotation.y = time / gearRatio;
belt.uniforms.time.value = t;
}
[rollerSmall, rollerBig, base].forEach(o => {
o.userData.nonGlowing = true;
})
}
}
class Table extends THREE.Mesh{
constructor(gu, bgColor){
let g = new THREE.PlaneGeometry(20, 20).rotateX(-Math.PI * 0.5);
let m = new THREE.MeshLambertMaterial({
color: new THREE.Color().setScalar(0.5).getHex(),
onBeforeCompile: shader => {
shader.uniforms.globalBloom = gu.globalBloom;
shader.uniforms.bgColor = {value: new THREE.Color(bgColor)};
shader.fragmentShader = `
uniform float globalBloom;
uniform vec3 bgColor;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
float uvDist = distance(vUv, vec2(0.5)) * 2.;
float f = smoothstep(0.5, 1., uvDist);
gl_FragColor.rgb = mix(gl_FragColor.rgb, bgColor, f);
gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0), globalBloom);
`
);
//console.log()
}
});
m.defines = {"USE_UV" : ""};
super(g, m);
this.receiveShadow = true;
}
}
let bgColors = {
on: new THREE.Color(1, 0.25, 0).multiplyScalar(0.1).getHex(),
off: 0x000000
}
let scene = new THREE.Scene();
scene.background = new THREE.Color(bgColors.off);
let camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 1, 1000);
camera.position.set(-5, 5, 10).setLength(18);
let renderer = new THREE.WebGLRenderer({antialias: false});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener("resize", event => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
postprocessing.bloomComposer.setSize(innerWidth, innerHeight);
postprocessing.finalComposer.setSize(innerWidth, innerHeight);
})
let controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
//controls.enablePan = false;
controls.target.set(0, 4, 0);
let light = new THREE.DirectionalLight(0xffffff, 0.2);
light.castShadow = true;
light.shadow.camera.top = 10;
light.shadow.camera.bottom = -10;
light.shadow.camera.left = -10;
light.shadow.camera.right = 10;
light.shadow.mapSize.width = 2048;
light.shadow.mapSize.height = 2048;
light.shadow.camera.near = 0;
light.shadow.camera.far = 20;
light.position.set(10, 20, 10).setLength(10);
scene.add(light, new THREE.AmbientLight(0xffffff, 0.3));
let gu = {
globalBloom: {value: 0}
}
/*let helper = new THREE.GridHelper();
helper.userData.nonGlowing = true;
scene.add(helper);*/
let device = new Device(gu);
scene.add(device);
let table = new Table(gu, bgColors.on);
scene.add(table);
scene.traverse(child => {
if (child.userData.nonGlowing) {
child.material.onBeforeCompile = shader => {
shader.uniforms.globalBloom = gu.globalBloom;
shader.fragmentShader = `
uniform float globalBloom;
${shader.fragmentShader}
`.replace(
`#include <dithering_fragment>`,
`#include <dithering_fragment>
gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0), globalBloom);
`
);
//console.log(child.material.type);
//console.log(shader.fragmentShader);
}
}
})
let postprocessing = new Postprocessing(scene, camera, renderer);
let clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
let t = clock.getElapsedTime();
controls.update();
device.update(t);
//renderer.render(scene, camera);
gu.globalBloom.value = 1;
scene.background.set(bgColors.off);
postprocessing.bloomComposer.render();
gu.globalBloom.value = 0;
scene.background.set(bgColors.on);
postprocessing.finalComposer.render();
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.