// @ts-ignore import mat4 from 'gl-mat4'; // @ts-ignore import {convert} from './objparser'; import $ from 'jquery'; import {initBuffers, deleteBuffers} from './buffers'; import {initShaderProgram} from './shaders'; import {changeFragmentShader} from './changeshader'; import {loadTexture} from './texture'; let squareRotation = 0.0; main(); /** * The program purpose is encapsulated in a main function */ async function main() { const canvas: any = document.querySelector('#glCanvas')!; const gl = canvas.getContext('webgl'); if (gl == null) { canvas.parentNode.removeChild(canvas); document.getElementById('root')!.insertAdjacentHTML('beforeend', `

Unable to initialize WebGL. Your browser or machine may not support it.

`); } /* eslint-disable */ const vsSource = ` attribute vec4 aVertexPosition; attribute vec4 aVertexNormal; attribute vec2 aTextureCoord; uniform mat4 uProjectionMatrix; uniform mat4 uviewMatrix; uniform mat4 umodelMatrix; uniform mat4 unormalModelMatrix; varying highp vec4 vNormal; varying highp vec3 vPosition; varying highp vec2 vTextureCoord; void main() { gl_Position = uProjectionMatrix * uviewMatrix * unormalModelMatrix * umodelMatrix * aVertexPosition; vPosition = vec3(aVertexPosition); vNormal = unormalModelMatrix * aVertexNormal; vTextureCoord = aTextureCoord; } `; const fsSource = ` precision highp float; varying highp vec2 vTextureCoord; varying highp vec4 vNormal; varying highp vec3 vPosition; uniform sampler2D uSampler; vec3 extremize(vec3 v, float n) { if (v.x > n / 2.) v.x = n; else v.x = 0.; if (v.y > n / 2.) v.y = n; else v.y = 0.; if (v.z > n / 2.) v.z = n; else v.z = 0.; return v; } void main() { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); vec3 tmp = extremize(mod(vPosition.xyz + vec3(1000.), vec3(2.)), 2.); float texture = (tmp.x + tmp.y + tmp.z) / 6.; gl_FragColor = vec4((texture * diffuse * 0.8) + (texture * vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource2 = ` precision highp float; varying highp vec2 vTextureCoord; varying highp vec4 vNormal; varying highp vec3 vPosition; uniform sampler2D uSampler; vec3 extremize(vec3 v, float n) { if (v.x > n / 2.) v.x = n; else v.x = 0.; if (v.y > n / 2.) v.y = n; else v.y = 0.; if (v.z > n / 2.) v.z = n; else v.z = 0.; return v; } void main() { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); vec3 texture = extremize(mod(vPosition.xyz + vec3(1000.), vec3(2.)), 2.) / vec3(2); gl_FragColor = vec4((texture * diffuse * 0.8) + (texture * vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource3 = ` precision highp float; varying highp vec2 vTextureCoord; varying highp vec4 vNormal; varying highp vec3 vPosition; uniform sampler2D uSampler; void main() { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); gl_FragColor = vec4((diffuse * 0.8) + (vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource4 = ` precision highp float; varying highp vec2 vTextureCoord; varying highp vec4 vNormal; varying highp vec3 vPosition; uniform sampler2D uSampler; void main() { highp vec4 texelColor = texture2D(uSampler, vTextureCoord); vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); gl_FragColor = vec4((texelColor.xyz * diffuse * 0.8) + (texelColor.xyz * vec3(0.2)) + (specular * vec3(1.)), 1.0); }`; const fsSource5 = ` precision highp float; varying highp vec2 vTextureCoord; varying highp vec4 vNormal; varying highp vec3 vPosition; uniform sampler2D uSampler; void make_kernel(inout vec4 n[9], sampler2D tex, vec2 coord) { float w = 1.0 / 640.; float h = 1.0 / 640.; n[0] = texture2D(tex, coord + vec2( -w, -h)); n[1] = texture2D(tex, coord + vec2(0.0, -h)); n[2] = texture2D(tex, coord + vec2( w, -h)); n[3] = texture2D(tex, coord + vec2( -w, 0.0)); n[4] = texture2D(tex, coord); n[5] = texture2D(tex, coord + vec2( w, 0.0)); n[6] = texture2D(tex, coord + vec2( -w, h)); n[7] = texture2D(tex, coord + vec2(0.0, h)); n[8] = texture2D(tex, coord + vec2( w, h)); } void main(void) { vec4 n[9]; make_kernel( n, uSampler, vTextureCoord); vec4 sobel_edge_h = n[2] + (2.0*n[5]) + n[8] - (n[0] + (2.0*n[3]) + n[6]); vec4 sobel_edge_v = n[0] + (2.0*n[1]) + n[2] - (n[6] + (2.0*n[7]) + n[8]); vec4 sobel = sqrt((sobel_edge_h * sobel_edge_h) + (sobel_edge_v * sobel_edge_v)); if ((1. - sobel.r) + (1. - sobel.g) + (1. - sobel.b) < 2.5) gl_FragColor = vec4(1.0 - sobel.rgb, 1.0 ); else { vec3 n = normalize(vec3(-50000., 100000., 50000.) - vPosition); float diffuse = max(dot(normalize(vNormal.xyz), n), 0.); float specular = pow( max(dot( reflect(n, normalize(vNormal.xyz)), normalize(vec3(0., 0., -50.) - vPosition)), 0.), 10.); gl_FragColor = vec4((diffuse * 0.8) + (vec3(0.2)) + (specular * vec3(1.)), 1.0); } }`; /* eslint-enable */ /** * Fetch an obj file * @param {string} url the url to fetch the object from * @return {string} the raw data of the obj file */ async function getObj(url: string) { const response = await fetch(url); const data = await response.text(); return data; } const data = await getObj('/static/objs/racer.obj'); const params: any = { distance: $('#distance').val(), circleSize: $('#circlesize').val(), fov: $('#fov').val(), avg: { x: 0, y: 0, z: 0, }, rot: { x: $('#rotx').val(), y: $('#roty').val(), z: $('#rotz').val(), }, rotSpeed: $('#rotspeed').val(), }; const [ positions, normals, uvs, indices, ] = convert(data); let x = 0; let y = 0; let z = 0; for (let i = 0; i < positions.length; i++) { if (i % 3 == 0) { x += positions[i]; } else if (i % 3 == 1) { y += positions[i]; } else { z += positions[i]; } } params.avg.x = x / (positions.length / 3); params.avg.y = y / (positions.length / 3); params.avg.z = z / (positions.length / 3); let length = indices.length; let [shaderProgram, fragmentShader]: any = initShaderProgram(gl, vsSource, fsSource4); let programInfo: any = { program: shaderProgram, attribLocations: { vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition'), vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'), textureCoord: gl.getAttribLocation(shaderProgram, 'aTextureCoord'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation( shaderProgram, 'uProjectionMatrix'), viewMatrix: gl.getUniformLocation( shaderProgram, 'uviewMatrix'), modelMatrix: gl.getUniformLocation( shaderProgram, 'umodelMatrix'), normalModelMatrix: gl.getUniformLocation( shaderProgram, 'unormalModelMatrix'), uSampler: gl.getUniformLocation( shaderProgram, 'uSampler'), }, }; let texture = loadTexture(gl, '/static/textures/racer.png'); let buffers = initBuffers(gl, positions, indices, normals, uvs); let then = 0; let changed = false; /** * Draws the scene repeatedly * @param {number} now the current time */ function render(now: any) { now *= 0.001; const deltaTime = now - then; if (now >= 1 && changed == false) { changed = true; } then = now; drawScene(gl, programInfo, buffers, deltaTime, length, params, texture); requestAnimationFrame(render); } /** * Pushes a new obj file to the gl buffer * @param {string} data the obj file to push */ function updateObj(data: string) { const [ positions, normals, uvs, indices, ] = convert(data); length = indices.length; let x = 0; let y = 0; let z = 0; for (let i = 0; i < positions.length; i++) { if (i % 3 == 0) { x += positions[i]; } else if (i % 3 == 1) { y += positions[i]; } else { z += positions[i]; } } params.avg.x = x / (positions.length / 3); params.avg.y = y / (positions.length / 3); params.avg.z = z / (positions.length / 3); deleteBuffers(gl, buffers); buffers = initBuffers(gl, positions, indices, normals, uvs); } $(function() { $('#distance').on('input', function() { const distance: any = $('#distance').val(); params.distance = parseFloat(distance); }); $('#circlesize').on('input', function() { const circleSize: any = $('#circlesize').val(); params.circleSize = parseFloat(circleSize); }); $('#rotx').on('input', function() { const rotx: any = $('#rotx').val(); params.rot.x = parseFloat(rotx); }); $('#roty').on('input', function() { const roty: any = $('#roty').val(); params.rot.y = parseFloat(roty); }); $('#rotz').on('input', function() { const rotz: any = $('#rotz').val(); params.rot.z = parseFloat(rotz); }); $('#rotspeed').on('input', function() { const rotSpeed: any = $('#rotspeed').val(); params.rotSpeed = parseFloat(rotSpeed); }); $('#fov').on('input', function() { const fov: any = $('#fov').val(); params.fov = parseFloat(fov); }); $('#s_blackandwhite').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource, vsSource); }); $('#s_color').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource2, vsSource); }); $('#s_grey').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource3, vsSource); }); $('#s_texture').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource4, vsSource); }); $('#s_sobel').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource5, vsSource); }); $('#o_sphere').on('click', async function() { const data = await getObj('/static/objs/sphere.obj'); updateObj(data); }); $('#o_teapot').on('click', async function() { const data = await getObj('/static/objs/teapot.obj'); updateObj(data); }); $('#o_fox').on('click', async function() { const data = await getObj('/static/objs/fox.obj'); updateObj(data); }); $('#o_mecha').on('click', async function() { const data = await getObj('/static/objs/mecha.obj'); updateObj(data); }); $('#o_racer').on('click', async function() { const data = await getObj('/static/objs/racer.obj'); updateObj(data); }); $('#t_wall').on('click', async function() { texture = loadTexture(gl, '/static/textures/wall.png'); }); $('#t_ice').on('click', async function() { texture = loadTexture(gl, '/static/textures/ice.png'); }); $('#t_noise').on('click', async function() { texture = loadTexture(gl, '/static/textures/noise.png'); }); $('#t_fox').on('click', async function() { texture = loadTexture(gl, '/static/textures/fox.png'); }); $('#t_racer').on('click', async function() { texture = loadTexture(gl, '/static/textures/racer.png'); }); $('#t_racer_wireframe').on('click', async function() { texture = loadTexture(gl, '/static/textures/racer_wireframe.png'); }); }); requestAnimationFrame(render); } /** * Draw a webgl scene * @param {any} gl the WebGL context * @param {any} programInfo WebGL program information * @param {any} buffers the buffers to draw * @param {number} deltaTime the difference in time since last call * @param {number} length the index buffer length * @param {number} params various parameterss * @param {any} texture the texture to load */ function drawScene(gl: any, programInfo: any, buffers: any, deltaTime: number, length: number, params: any, texture: any) { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); // Clear the canvas before we start drawing on it. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); // Create a perspective matrix, a special matrix that is // used to simulate the distortion of perspective in a camera. // Our field of view is 45 degrees, with a width/height // ratio that matches the display size of the canvas // and we only want to see objects between 0.1 units // and 100 units away from the camera. const fieldOfView = params.fov * Math.PI / 180; const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; const zNear = 0.1; const zFar = 1000.0; const projectionMatrix = mat4.create(); // note: glmatrix.js always has the first argument // as the destination to receive the result. mat4.perspective( projectionMatrix, fieldOfView, aspect, zNear, zFar); const normalModelMatrix = mat4.create(); mat4.rotateX(normalModelMatrix, normalModelMatrix, params.rot.x); mat4.rotateY(normalModelMatrix, normalModelMatrix, params.rot.y); mat4.rotateZ(normalModelMatrix, normalModelMatrix, params.rot.z); const modelMatrix = mat4.create(); mat4.translate(modelMatrix, modelMatrix, [ -params.avg.x, -params.avg.y, -params.avg.z, ]); mat4.rotateY(modelMatrix, modelMatrix, squareRotation); // Set the drawing position to the "identity" point, which is // the center of the scene. const viewMatrix = mat4.create(); mat4.translate( viewMatrix, viewMatrix, [ Math.cos(squareRotation) * params.circleSize, Math.sin(squareRotation) * params.circleSize, 0, ]); mat4.translate( viewMatrix, viewMatrix, [0.0, 0.0, -params.distance]); // Tell WebGL how to pull out the positions from the position // buffer into the vertexPosition attribute. { const numComponents = 3; const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positions); gl.vertexAttribPointer( programInfo.attribLocations.vertexPosition, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray( programInfo.attribLocations.vertexPosition); } { const numComponents = 3; const type = gl.FLOAT; const normalize = false; const stride = 0; const offset = 0; gl.bindBuffer(gl.ARRAY_BUFFER, buffers.normals); gl.vertexAttribPointer( programInfo.attribLocations.vertexNormal, numComponents, type, normalize, stride, offset); gl.enableVertexAttribArray( programInfo.attribLocations.vertexNormal); } // tell webgl how to pull out the texture coordinates from buffer { const num = 2; // every coordinate composed of 2 values const type = gl.FLOAT; // the data in the buffer is 32 bit float const normalize = false; // don't normalize const stride = 0; // how many bytes to get from one set to the next const offset = 0; // how many bytes inside the buffer to start from gl.bindBuffer(gl.ARRAY_BUFFER, buffers.uvs); gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, num, type, normalize, stride, offset); gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord); } // Tell WebGL which indices to use to index the vertices gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices); // Tell WebGL to use our program when drawing gl.useProgram(programInfo.program); // Set the shader uniforms gl.uniformMatrix4fv( programInfo.uniformLocations.projectionMatrix, false, projectionMatrix); gl.uniformMatrix4fv( programInfo.uniformLocations.viewMatrix, false, viewMatrix); gl.uniformMatrix4fv( programInfo.uniformLocations.modelMatrix, false, modelMatrix); gl.uniformMatrix4fv( programInfo.uniformLocations.normalModelMatrix, false, normalModelMatrix); // Tell WebGL we want to affect texture unit 0 gl.activeTexture(gl.TEXTURE0); // Bind the texture to texture unit 0 gl.bindTexture(gl.TEXTURE_2D, texture); // Tell the shader we bound the texture to texture unit 0 gl.uniform1i(programInfo.uniformLocations.uSampler, 0); { const vertexCount = length; const type = gl.UNSIGNED_SHORT; const offset = 0; gl.drawElements(gl.TRIANGLES, vertexCount, type, offset); } squareRotation += deltaTime * params.rotSpeed; }