Frontend Forever App
We have a mobile app for you to download and use. And you can unlock many features in the app.
Get it now
Intall Later
Run
HTML
CSS
Javascript
Output
body, html { position: absolute; margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; } canvas { position: absolute; width: 100%; height: 100%; background: #000; cursor: pointer; }
{ // Code never lies, comments sometimes do. const webgl = { init() { // create webGL canvas context this.elem = document.createElement("canvas"); document.body.appendChild(this.elem); let gl = this.elem.getContext("webgl", { antialiasing: true }); if (!gl) gl = this.elem.getContext("experimental-webgl"); if (!gl) return false; this.gl = gl; // set shaders this.vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource( this.vertexShader, ` uniform mat4 camProj, camView; uniform mat4 model; attribute vec3 position; uniform vec4 modelColor; uniform vec3 light1Pos; uniform vec3 light1Color; uniform vec3 light2Pos; uniform vec3 light2Color; varying vec3 vLightWeighting; void main(void) { vec4 normal = model * vec4(position, 0.0); vec4 worldPosition = model * vec4(position, 1.0); vec3 lightDirection = normalize(light1Pos - worldPosition.xyz); float angle = max(dot(lightDirection, (camView * normal).xyz), 0.0); vLightWeighting = light1Color * angle * modelColor.rgb; lightDirection = normalize(light2Pos - worldPosition.xyz); angle = max(dot(lightDirection, normal.xyz), 0.0); vLightWeighting += light2Color * angle * modelColor.rgb; if (modelColor.a == 0.0) vLightWeighting = modelColor.rgb; gl_Position = camProj * camView * worldPosition; } ` ); this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource( this.fragmentShader, ` precision highp float; uniform vec4 modelColor; varying vec3 vLightWeighting; void main(void) { vec3 col = modelColor.rgb * vLightWeighting; gl_FragColor = vec4(col.rgb, modelColor.a); } ` ); // compile shaders gl.compileShader(this.vertexShader); gl.compileShader(this.fragmentShader); this.program = gl.createProgram(); gl.attachShader(this.program, this.vertexShader); gl.attachShader(this.program, this.fragmentShader); gl.linkProgram(this.program); gl.useProgram(this.program); // camera this.camera = { pos: this.vec3(), mov: this.vec3(), proj: this.mat4().uniform("camProj"), view: this.mat4().uniform("camView") }; // resize event this.resize(); window.addEventListener("resize", () => this.resize(), false); return gl; }, Attribute: class { constructor(name) { this.gl = webgl.gl; this.index = gl.getAttribLocation(webgl.program, name); gl.enableVertexAttribArray(this.index); this.buffer = gl.createBuffer(); this.numElements = 0; } load(data, size, stride, offset) { this.itemSize = size; this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.buffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, data instanceof Array ? new Float32Array(data) : data, this.gl.STATIC_DRAW ); this.numElements = data.length / size; this.gl.vertexAttribPointer( this.index, size, this.gl.FLOAT, false, stride, offset ); return this.buffer; } loadIndices(indices) { if (!this.indices) this.indices = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indices); this.gl.bufferData( this.gl.ELEMENT_ARRAY_BUFFER, indices instanceof Array ? new Uint16Array(indices) : indices, this.gl.STATIC_DRAW ); this.numElements = indices.length; return this.indices; } }, resize() { this.width = this.elem.width = this.elem.offsetWidth; this.height = this.elem.height = this.elem.offsetHeight; this.aspect = this.width / this.height; // perspective projection this.camera.proj.perspective(60).load(); this.gl.viewport( 0, 0, this.gl.drawingBufferWidth, this.gl.drawingBufferHeight ); }, mat4() { return new this.Mat4(); }, vec3(x, y, z) { return new this.Vec3(x, y, z); }, vec4(x, y, z, w) { return new this.Vec4(x, y, z, w); }, Mat4: class { constructor() { this.matrix = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]); this.gl = webgl.gl; this.u = null; } identity() { const d = this.matrix; d[0] = 1; d[1] = 0; d[2] = 0; d[3] = 0; d[4] = 0; d[5] = 1; d[6] = 0; d[7] = 0; d[8] = 0; d[9] = 0; d[10] = 1; d[11] = 0; d[12] = 0; d[13] = 0; d[14] = 0; d[15] = 1; return this; } translate(x, y, z) { const d = this.matrix; d[12] = d[0] * x + d[4] * y + d[8] * z + d[12]; d[13] = d[1] * x + d[5] * y + d[9] * z + d[13]; d[14] = d[2] * x + d[6] * y + d[10] * z + d[14]; d[15] = d[3] * x + d[7] * y + d[11] * z + d[15]; return this; } fromTranslation(x, y, z) { const a = this.matrix; a[0] = 1; a[1] = 0; a[2] = 0; a[3] = 0; a[4] = 0; a[5] = 1; a[6] = 0; a[7] = 0; a[8] = 0; a[9] = 0; a[10] = 1; a[11] = 0; a[12] = x; a[13] = y; a[14] = z; a[15] = 1; return this; } rotateX(angle) { const d = this.matrix; const s = Math.sin(angle); const c = Math.cos(angle); const a10 = d[4]; const a11 = d[5]; const a12 = d[6]; const a13 = d[7]; const a20 = d[8]; const a21 = d[9]; const a22 = d[10]; const a23 = d[11]; d[4] = a10 * c + a20 * s; d[5] = a11 * c + a21 * s; d[6] = a12 * c + a22 * s; d[7] = a13 * c + a23 * s; d[8] = a10 * -s + a20 * c; d[9] = a11 * -s + a21 * c; d[10] = a12 * -s + a22 * c; d[11] = a13 * -s + a23 * c; return this; } rotateY(angle) { const d = this.matrix; const s = Math.sin(angle); const c = Math.cos(angle); const a00 = d[0]; const a01 = d[1]; const a02 = d[2]; const a03 = d[3]; const a20 = d[8]; const a21 = d[9]; const a22 = d[10]; const a23 = d[11]; d[0] = a00 * c + a20 * -s; d[1] = a01 * c + a21 * -s; d[2] = a02 * c + a22 * -s; d[3] = a03 * c + a23 * -s; d[8] = a00 * s + a20 * c; d[9] = a01 * s + a21 * c; d[10] = a02 * s + a22 * c; d[11] = a03 * s + a23 * c; return this; } rotateZ(angle) { const d = this.matrix; const s = Math.sin(angle); const c = Math.cos(angle); const a00 = d[0]; const a01 = d[1]; const a02 = d[2]; const a03 = d[3]; const a10 = d[4]; const a11 = d[5]; const a12 = d[6]; const a13 = d[7]; d[0] = a00 * c + a10 * s; d[1] = a01 * c + a11 * s; d[2] = a02 * c + a12 * s; d[3] = a03 * c + a13 * s; d[4] = a00 * -s + a10 * c; d[5] = a01 * -s + a11 * c; d[6] = a02 * -s + a12 * c; d[7] = a03 * -s + a13 * c; return this; } scale(x, y, z) { const d = this.matrix; d[0] *= x; d[1] *= x; d[2] *= x; d[3] *= x; d[4] *= y; d[5] *= y; d[6] *= y; d[7] *= y; d[8] *= z; d[9] *= z; d[10] *= z; d[11] *= z; return this; } perspective(fov) { const d = this.matrix; const near = 0.01; const far = 100; const top = near * Math.tan(fov * Math.PI / 360); const right = top * webgl.aspect; const left = -right; const bottom = -top; d[0] = 2 * near / (right - left); d[1] = 0; d[2] = 0; d[3] = 0; d[4] = 0; d[5] = 2 * near / (top - bottom); d[6] = 0; d[7] = 0; d[8] = (right + left) / (right - left); d[9] = (top + bottom) / (top - bottom); d[10] = -(far + near) / (far - near); d[11] = -1; d[12] = 0; d[13] = 0; d[14] = -(2 * far * near) / (far - near); d[15] = 0; return this; } uniform(uName) { this.u = this.gl.getUniformLocation(webgl.program, uName); return this; } load() { this.gl.uniformMatrix4fv(this.u, this.gl.FALSE, this.matrix); return this; } }, Vec3: class { constructor(x = 0.0, y = 0.0, z = 0.0) { this.x = x; this.y = y; this.z = z; this.u = null; this.gl = webgl.gl; } set(x, y, z) { this.x = x; this.y = y; this.z = z; return this; } lerp(v, s) { this.x += (v.x - this.x) * s; this.y += (v.y - this.y) * s; this.z += (v.z - this.z) * s; return this; } distance(b) { const dx = b.x - this.x; const dy = b.y - this.y; const dz = b.z - this.z; return Math.sqrt(dx * dx + dy * dy + dz * dz); } transformMat4(v, m) { const d = m.matrix; const x = v.x; const y = v.y; const z = v.z; const w = d[3] * x + d[7] * y + d[11] * z + d[15] || 1.0; this.x = (d[0] * x + d[4] * y + d[8] * z + d[12]) / w; this.y = (d[1] * x + d[5] * y + d[9] * z + d[13]) / w; this.z = (d[2] * x + d[6] * y + d[10] * z + d[14]) / w; return this; } uniform(uName) { this.u = this.gl.getUniformLocation(webgl.program, uName); return this; } load() { this.gl.uniform3f(this.u, this.x, this.y, this.z); return this; } }, Vec4: class { constructor(x = 0.0, y = 0.0, z = 0.0, w = 0.0) { this.x = x; this.y = y; this.z = z; this.w = w; this.u = null; this.gl = webgl.gl; } set(x, y, z, w) { this.x = x; this.y = y; this.z = z; this.w = w; return this; } lerp(v, s) { this.x += (v.x - this.x) * s; this.y += (v.y - this.y) * s; this.z += (v.z - this.z) * s; this.w += (v.w - this.w) * s; return this; } uniform(uName) { this.u = this.gl.getUniformLocation(webgl.program, uName); return this; } load() { this.gl.uniform4f(this.u, this.x, this.y, this.z, this.w); return this; } } }; // set pointer const pointer = { init(canvas) { this.x = 0; this.y = 0; this.isDown = false; this.add(window, "mousemove,touchmove", this.move); this.add(canvas.elem, "mousedown,touchstart", e => { this.move(e); this.isDown = true; }); this.add(window, "mouseup,touchend,touchcancel", e => (this.isDown = false)); }, move(e) { let touchMode = e.targetTouches, pointer; if (touchMode) { pointer = touchMode[0]; } else pointer = e; this.x = pointer.clientX; this.y = pointer.clientY; }, add(elem, events, fn) { for (let i = 0, e = events.split(","); i < e.length; i++) { elem.addEventListener(e[i], fn, false); } } }; const Sphere = class { constructor(ax, ay, d, radius, r, g, b) { this.pos = webgl.vec3(); this.matrix = webgl .mat4() .rotateY(ay) .rotateX(ax) .translate(0, 0, d) .scale(radius, radius, radius) .uniform("model"); this.pos.transformMat4(this.pos, this.matrix); this.radius = radius; this.color = webgl.vec4().uniform("modelColor"); this.id = 0; } draw() { this.matrix.load(); this.color.load(); gl.drawElements(gl.TRIANGLES, position.numElements, gl.UNSIGNED_SHORT, 0); } }; const sphereGeometry = res => { const vertices = []; const indices = []; for (let i = 0; i <= res; i++) { const theta = i * Math.PI / res; const sinTheta = Math.sin(theta); const cosTheta = Math.cos(theta); for (let j = 0; j <= res; j++) { const phi = j * 2 * Math.PI / res; const sinPhi = Math.sin(phi); const cosPhi = Math.cos(phi); const vx = cosPhi * sinTheta; const vy = cosTheta; const vz = sinPhi * sinTheta; vertices.push(vy, vx, vz); } } for (let i = 0; i < res; i++) { for (let j = 0; j < res; j++) { const first = i * (res + 1) + j; const second = first + res + 1; indices.push(first, second, first + 1, second, second + 1, first + 1); } } // upload geometry to gpu position.load(vertices, 3, 0, 0); position.loadIndices(indices); }; // ---- init ---- const gl = webgl.init(); gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE); pointer.init(webgl); const camera = webgl.camera; const position = new webgl.Attribute("position"); const sphere = sphereGeometry(72); // more inside const inside = () => { const spheres = []; let id = 0; const r = 1; const g = 1; const b = 1; const s = new Sphere(0, 0, 0, 0.12, 10, 7, 4); s.color.set(1, 1, 0.9, 0).load(); spheres.push(s); for (let i = 0; i < 10; i++) { for (let k = 0; k < 100; k++) { let ok = true; const s = new Sphere( Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI, 2 * Math.random(), 0.2 + Math.random() * 1 ); for (const st of spheres) { if (s.pos.distance(st.pos) < 1.2 * (s.radius + st.radius)) { ok = false; break; } } if (!spheres.length || ok) { s.id = ++id; s.color.set(r, g, b, id / 255).load(); spheres.push(s); break; } } } return spheres; }; let spheres = inside(); let target = null; const pixel = new Uint8Array(4); let dist = 12; let rotation = 0; let rotationY = -0.004; const center = webgl.vec3(); // lights const light1Pos = webgl.vec3(0, 0, 6).uniform("light1Pos").load(); const light1Color = webgl.vec3(0.25, 0.5, 1).uniform("light1Color").load(); const light2Pos = webgl.vec3(0, 0, 0).uniform("light2Pos").load(); const light2Color = webgl.vec3(2, 1, 0).uniform("light2Color").load(); // main loop const run = () => { requestAnimationFrame(run); // clear screen gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); // reach target if (target) { camera.mov.lerp(target.pos, 0.02); target.color.lerp(center, -0.01).load(); dist *= 0.99; if (dist < target.radius * 1.2) { target = null; dist = 26; camera.view.identity(); spheres = inside(); } } else { camera.mov.lerp(center, 0.02); if (dist > 3) dist *= 0.99; } // rotate world matrix rotation += rotationY; camera.view .fromTranslation(0, 0, -dist) .rotateY(rotation) .rotateX(rotation * 0.5) .translate(-camera.mov.x, -camera.mov.y, -camera.mov.z) .load(); camera.pos.transformMat4(camera.pos, camera.view); // draw spheres for (const sphere of spheres) { sphere.draw(); } // picking if (pointer.isDown && !target) { gl.readPixels( pointer.x, webgl.height - pointer.y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel ); const id = pixel[3]; if (id) { for (const sphere of spheres) { if (sphere.id === id) { target = sphere; break; } } } } }; requestAnimationFrame(run); }