import * as twgl from '../dist/7.x/twgl-full.js'; import * as chroma from '../3rdparty/chroma.min.js'; const onePointVS = ` uniform mat4 u_worldViewProjection; attribute vec4 a_position; attribute vec2 a_texcoord; varying vec4 v_position; varying vec2 v_texCoord; void main() { v_texCoord = a_texcoord; gl_Position = u_worldViewProjection * a_position; } `; const onePointFS = ` precision mediump float; varying vec4 v_position; varying vec2 v_texCoord; uniform vec4 u_diffuseMult; uniform sampler2D u_diffuse; void main() { vec4 diffuseColor = texture2D(u_diffuse, v_texCoord) * u_diffuseMult; if (diffuseColor.a < 0.1) { discard; } gl_FragColor = diffuseColor; } `; const envMapVS = ` uniform mat4 u_viewInverse; uniform mat4 u_world; uniform mat4 u_worldViewProjection; uniform mat4 u_worldInverseTranspose; attribute vec4 a_position; attribute vec3 a_normal; varying vec3 v_normal; varying vec3 v_surfaceToView; void main() { v_normal = (u_worldInverseTranspose * vec4(a_normal, 0)).xyz; v_surfaceToView = (u_viewInverse[3] - (u_world * a_position)).xyz; gl_Position = u_worldViewProjection * a_position; } `; const envMapFS = ` precision mediump float; uniform samplerCube u_texture; varying vec3 v_surfaceToView; varying vec3 v_normal; void main() { vec3 normal = normalize(v_normal); vec3 surfaceToView = normalize(v_surfaceToView); vec4 color = textureCube(u_texture, -reflect(surfaceToView, normal)); gl_FragColor = color; } `; twgl.setDefaults({attribPrefix: "a_"}); const m4 = twgl.m4; const gl = (document.querySelector("#c")).getContext("webgl"); const onePointProgramInfo = twgl.createProgramInfo(gl, [onePointVS, onePointFS]); const envMapProgramInfo = twgl.createProgramInfo(gl, [envMapVS, envMapFS]); const shapes = [ twgl.primitives.createCubeBufferInfo(gl, 2), twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12), twgl.primitives.createPlaneBufferInfo(gl, 2, 2), twgl.primitives.createTruncatedConeBufferInfo(gl, 1, 0, 2, 24, 1), ]; function rand(min: number, max?: number) { if (max === undefined) { max = min; min = 0; } return min + Math.random() * (max - min); } // Shared values const baseHue = rand(360); const camera = m4.identity(); const view = m4.identity(); const viewProjection = m4.identity(); // A circle on a canvas const ctx = document.createElement("canvas").getContext("2d"); ctx.canvas.width = 64; ctx.canvas.height = 64; function updateCanvas(time) { ctx.fillStyle = "#00f"; ctx.strokeStyle = "#ff0"; ctx.lineWidth = 10; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.beginPath(); ctx.arc(ctx.canvas.width / 2, ctx.canvas.height / 2, ctx.canvas.width / 2.2 * Math.abs(Math.cos(time)), 0, Math.PI * 2); ctx.stroke(); } updateCanvas(0); // A cubemap drawn to a canvas with a circle on each face. const cubemapCtx = document.createElement("canvas").getContext("2d"); const size = 40; cubemapCtx.canvas.width = size * 6; cubemapCtx.canvas.height = size; cubemapCtx.fillStyle = "#888"; for (let ff = 0; ff < 6; ++ff) { const color = chroma.hsv((baseHue + ff * 10) % 360, 1 - ff / 6, 1); cubemapCtx.fillStyle = color.darken().hex(); cubemapCtx.fillRect(size * ff, 0, size, size); cubemapCtx.save(); cubemapCtx.translate(size * (ff + 0.5), size * 0.5); cubemapCtx.beginPath(); cubemapCtx.arc(0, 0, size * 0.3, 0, Math.PI * 2); cubemapCtx.fillStyle = color.hex(); cubemapCtx.fill(); cubemapCtx.restore(); } // make 6 canvases to show loading from 6 element const cubeFaceCanvases = []; for (let ff = 0; ff < 6; ++ff) { const canvas = document.createElement("canvas"); canvas.width = 128; canvas.height = 128; const ctx = canvas.getContext("2d"); const color = chroma.hsv((baseHue + ff * 10) % 360, 1 - ff / 6, 1); ctx.fillStyle = color.darken().hex(); ctx.fillRect(0, 0, 128, 128); ctx.translate(64, 64); ctx.rotate(Math.PI * .25); ctx.fillStyle = color.hex(); ctx.fillRect(-40, -40, 80, 80); cubeFaceCanvases.push(canvas); } const textures = twgl.createTextures(gl, { // a power of 2 image hftIcon: { src: "images/hft-icon-16.png", mag: gl.NEAREST }, // a non-power of 2 image clover: { src: "images/clover.jpg" }, // From a canvas fromCanvas: { src: ctx.canvas }, // A cubemap from 6 images yokohama: { target: gl.TEXTURE_CUBE_MAP, src: [ 'images/yokohama/posx.jpg', 'images/yokohama/negx.jpg', 'images/yokohama/posy.jpg', 'images/yokohama/negy.jpg', 'images/yokohama/posz.jpg', 'images/yokohama/negz.jpg', ], }, // A cubemap from 1 image (can be 1x6, 2x3, 3x2, 6x1) goldengate: { target: gl.TEXTURE_CUBE_MAP, src: 'images/goldengate.jpg', }, // A 2x2 pixel texture from a JavaScript array checker: { mag: gl.NEAREST, min: gl.LINEAR, src: [ 255, 255, 255, 255, 192, 192, 192, 255, 192, 192, 192, 255, 255, 255, 255, 255, ], }, // a 1x8 pixel texture from a typed array. stripe: { mag: gl.NEAREST, min: gl.LINEAR, format: gl.LUMINANCE, src: new Uint8Array([ 255, 128, 255, 128, 255, 128, 255, 128, ]), width: 1, }, // a cubemap from array cubemapFromArray: { target: gl.TEXTURE_CUBE_MAP, format: gl.RGBA, src: [ 0xF0, 0x80, 0x80, 0xFF, 0x80, 0xE0, 0x80, 0xFF, 0x80, 0x80, 0xD0, 0xFF, 0xC0, 0x80, 0x80, 0xFF, 0x80, 0xB0, 0x80, 0xFF, 0x80, 0x80, 0x00, 0xFF, ], }, cubemapFromCanvas: { target: gl.TEXTURE_CUBE_MAP, src: cubemapCtx.canvas }, cubemapFrom6Canvases: { target: gl.TEXTURE_CUBE_MAP, src: cubeFaceCanvases }, }); // This is soley to make it easy to pick textures at random const twoDTextures = [ textures.checker, textures.stripe, textures.hftIcon, textures.clover, textures.fromCanvas, ]; const cubeTextures = [ textures.yokohama, textures.goldengate, textures.cubemapFromCanvas, textures.cubemapFrom6Canvases, textures.cubemapFromArray, ]; const objects = []; const drawObjects = []; const numObjects = 100; for (let ii = 0; ii < numObjects; ++ii) { let uniforms; let programInfo; let shape; const renderType = rand(0, 2) | 0; switch (renderType) { case 0: // checker shape = shapes[ii % shapes.length]; programInfo = onePointProgramInfo; uniforms = { u_diffuseMult: chroma.hsv((baseHue + rand(0, 60)) % 360, 0.4, 0.8).gl(), u_diffuse: twoDTextures[rand(0, twoDTextures.length) | 0], u_viewInverse: camera, u_world: m4.identity(), u_worldInverseTranspose: m4.identity(), u_worldViewProjection: m4.identity(), }; break; case 1: // yokohama shape = rand(0, 2) < 1 ? shapes[1] : shapes[3]; programInfo = envMapProgramInfo; uniforms = { u_texture: cubeTextures[rand(0, cubeTextures.length) | 0], u_viewInverse: camera, u_world: m4.identity(), u_worldInverseTranspose: m4.identity(), u_worldViewProjection: m4.identity(), }; break; default: throw "wAT!"; } drawObjects.push({ programInfo: programInfo, bufferInfo: shape, uniforms: uniforms, }); objects.push({ translation: [rand(-10, 10), rand(-10, 10), rand(-10, 10)], ySpeed: rand(0.1, 0.3), zSpeed: rand(0.1, 0.3), uniforms: uniforms, }); } function render(time) { time *= 0.001; twgl.resizeCanvasToDisplaySize(gl.canvas); gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); gl.enable(gl.DEPTH_TEST); gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); const radius = 20; const orbitSpeed = time * 0.1; const projection = m4.perspective(30 * Math.PI / 180, (gl.canvas).clientWidth / (gl.canvas).clientHeight, 0.5, 100); const eye = [Math.cos(orbitSpeed) * radius, 4, Math.sin(orbitSpeed) * radius]; const target = [0, 0, 0]; const up = [0, 1, 0]; m4.lookAt(eye, target, up, camera); m4.inverse(camera, view); m4.multiply(projection, view, viewProjection); updateCanvas(time); twgl.setTextureFromElement(gl, textures.fromCanvas, ctx.canvas); objects.forEach(function(obj) { const uni = obj.uniforms; const world = uni.u_world; m4.identity(world); m4.rotateY(world, time * obj.ySpeed, world); m4.rotateZ(world, time * obj.zSpeed, world); m4.translate(world, obj.translation, world); m4.rotateX(world, time, world); m4.transpose(m4.inverse(world, uni.u_worldInverseTranspose), uni.u_worldInverseTranspose); m4.multiply(viewProjection, uni.u_world, uni.u_worldViewProjection); }); twgl.drawObjectList(gl, drawObjects); requestAnimationFrame(render); } requestAnimationFrame(render);