diff --git a/public/textures/fox.png b/public/textures/fox.png new file mode 100755 index 0000000..195a4d1 Binary files /dev/null and b/public/textures/fox.png differ diff --git a/public/textures/ice.png b/public/textures/ice.png new file mode 100644 index 0000000..bf19aec Binary files /dev/null and b/public/textures/ice.png differ diff --git a/public/textures/noise.png b/public/textures/noise.png new file mode 100644 index 0000000..5faf165 Binary files /dev/null and b/public/textures/noise.png differ diff --git a/public/textures/wall.png b/public/textures/wall.png new file mode 100644 index 0000000..d240b3b Binary files /dev/null and b/public/textures/wall.png differ diff --git a/src/client/buffers.ts b/src/client/buffers.ts index 2f68934..0b30336 100644 --- a/src/client/buffers.ts +++ b/src/client/buffers.ts @@ -5,13 +5,15 @@ * @param {Array} positions the position buffer to be loaded * @param {Array} indices the index buffer to be loaded * @param {Array} normals the normal buffer to be loaded + * @param {Array} uvs the texture buffer to be loaded * @return {any} the buffers */ export function initBuffers( gl: any, positions: Array, indices: Array, - normals: Array) { + normals: Array, + uvs: Array) { const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.bufferData( @@ -32,10 +34,17 @@ export function initBuffers( new Uint16Array(indices), gl.STATIC_DRAW); // Now send the element array to GL + const uvBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, uvBuffer); + gl.bufferData(gl.ARRAY_BUFFER, + new Float32Array(uvs), + gl.STATIC_DRAW); + return { - position: positionBuffer, + positions: positionBuffer, indices: indexBuffer, normals: normalBuffer, + uvs: uvBuffer, }; } @@ -45,8 +54,8 @@ export function initBuffers( * @param {any} buffers the buffers to delete */ export function deleteBuffers(gl: any, buffers: any) { - gl.deleteBuffer(buffers.color); - gl.deleteBuffer(buffers.position); + gl.deleteBuffer(buffers.uvs); + gl.deleteBuffer(buffers.positions); gl.deleteBuffer(buffers.normals); gl.deleteBuffer(buffers.indices); } diff --git a/src/client/changeshader.ts b/src/client/changeshader.ts index 49a50c7..5742e05 100644 --- a/src/client/changeshader.ts +++ b/src/client/changeshader.ts @@ -30,6 +30,8 @@ export function changeFragmentShader(gl: any, 'aVertexColor'), vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'), + textureCoord: gl.getAttribLocation(shaderProgram, + 'aTextureCoord'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation( @@ -38,6 +40,8 @@ export function changeFragmentShader(gl: any, shaderProgram, 'uviewMatrix'), modelMatrix: gl.getUniformLocation( shaderProgram, 'umodelMatrix'), + uSampler: gl.getUniformLocation( + shaderProgram, 'uSampler'), }, }; return [programInfo, fragmentShader]; diff --git a/src/client/main.ts b/src/client/main.ts index a7b6679..93c1357 100644 --- a/src/client/main.ts +++ b/src/client/main.ts @@ -1,12 +1,13 @@ // @ts-ignore import mat4 from 'gl-mat4'; // @ts-ignore -import convert from './objparser'; +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; @@ -31,6 +32,7 @@ async function main() { const vsSource = ` attribute vec4 aVertexPosition; attribute vec4 aVertexNormal; + attribute vec2 aTextureCoord; uniform mat4 uProjectionMatrix; uniform mat4 uviewMatrix; @@ -38,6 +40,8 @@ async function main() { varying highp vec4 vNormal; varying highp vec3 vPosition; + varying highp vec2 vTextureCoord; + void main() { gl_Position = uProjectionMatrix * @@ -46,6 +50,7 @@ async function main() { aVertexPosition; vPosition = vec3(aVertexPosition); vNormal = umodelMatrix * aVertexNormal; + vTextureCoord = aTextureCoord; } `; @@ -151,6 +156,27 @@ async function main() { vec3 texture = extremize(mod(vPosition.xyz + vec3(1000.), vec3(2.)), 2.) / vec3(2); 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); + }`; /* eslint-enable */ @@ -165,7 +191,7 @@ async function main() { return data; } - const data = await getObj('/static/objs/teapot.obj'); + const data = await getObj('/static/objs/fox.obj'); let distance: any = $('#input1').val(); let circleSize: any = $('#input2').val(); distance = parseFloat(distance); @@ -177,10 +203,9 @@ async function main() { indices, ] = convert(data); let length = indices.length; - console.log(uvs); let [shaderProgram, fragmentShader]: any = initShaderProgram(gl, vsSource, - fsSource3); + fsSource4); let programInfo: any = { program: shaderProgram, @@ -189,6 +214,8 @@ async function main() { 'aVertexPosition'), vertexNormal: gl.getAttribLocation(shaderProgram, 'aVertexNormal'), + textureCoord: gl.getAttribLocation(shaderProgram, + 'aTextureCoord'), }, uniformLocations: { projectionMatrix: gl.getUniformLocation( @@ -197,10 +224,13 @@ async function main() { shaderProgram, 'uviewMatrix'), modelMatrix: gl.getUniformLocation( shaderProgram, 'umodelMatrix'), + uSampler: gl.getUniformLocation( + shaderProgram, 'uSampler'), }, }; - let buffers = initBuffers(gl, positions, indices, normals); + let texture = loadTexture(gl, '/static/textures/fox.png'); + let buffers = initBuffers(gl, positions, indices, normals, uvs); let then = 0; let changed = false; @@ -221,7 +251,8 @@ async function main() { deltaTime, length, distance, - circleSize); + circleSize, + texture); requestAnimationFrame(render); } @@ -236,10 +267,9 @@ async function main() { uvs, indices, ] = convert(data); - console.log(uvs); length = indices.length; deleteBuffers(gl, buffers); - buffers = initBuffers(gl, positions, indices, normals); + buffers = initBuffers(gl, positions, indices, normals, uvs); } $(function() { @@ -255,18 +285,22 @@ async function main() { $('#changecirclesize').click(); } }); - $('#button1').on('click', function() { + $('#s_blackandwhite').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource, vsSource); }); - $('#button2').on('click', function() { + $('#s_color').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource2, vsSource); }); - $('#button3').on('click', function() { + $('#s_flat').on('click', function() { [programInfo, fragmentShader] = changeFragmentShader(gl, shaderProgram, fragmentShader, fsSource3, vsSource); }); + $('#s_texture').on('click', function() { + [programInfo, fragmentShader] = changeFragmentShader(gl, + shaderProgram, fragmentShader, fsSource4, vsSource); + }); $('#changedistance').on('click', function() { distance = $('#input1').val(); distance = parseFloat(distance); @@ -287,6 +321,18 @@ async function main() { const data = await getObj('/static/objs/fox.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'); + }); }); requestAnimationFrame(render); } @@ -300,6 +346,7 @@ async function main() { * @param {number} length the index buffer length * @param {number} distance distance of camera * @param {number} circleSize size of circle the object is rotating around + * @param {any} texture the texture to load */ function drawScene(gl: any, programInfo: any, @@ -307,7 +354,8 @@ function drawScene(gl: any, deltaTime: number, length: number, distance: number, - circleSize: number) { + circleSize: number, + texture: any) { gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); @@ -374,7 +422,7 @@ function drawScene(gl: any, const normalize = false; const stride = 0; const offset = 0; - gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position); + gl.bindBuffer(gl.ARRAY_BUFFER, buffers.positions); gl.vertexAttribPointer( programInfo.attribLocations.vertexPosition, numComponents, @@ -405,6 +453,30 @@ function drawScene(gl: any, 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 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); + // Tell WebGL which indices to use to index the vertices gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.indices); diff --git a/src/client/objparser.js b/src/client/objparser.js index b2290e1..5c02517 100644 --- a/src/client/objparser.js +++ b/src/client/objparser.js @@ -1,6 +1,5 @@ -export default function convert (objText) { +export function convert (objText) { const lines = objText.split("\n"); - let smoothing = false; const positions = []; @@ -11,67 +10,87 @@ export default function convert (objText) { const indices = []; for (let i = 0; i < lines.length; i++) { - if (lines[i] != '') - { - const rawchunks = lines[i].split(" ").map(x => x.trim()); - const chunks = []; - for (let i = 0; i < rawchunks.length; i++) { - if (rawchunks[i] != '') { - chunks.push(rawchunks[i]); - } - } - - switch (chunks[0]) { - case "v": { - positions.push(chunks.splice(1).map(parseFloat)); - break; - } - case "vt": { - const uv = chunks.splice(1); - uvs.push([parseFloat(uv[0]), parseFloat(uv[1])]); - break; - } - case "vn": { - normals.push(chunks.splice(1).map(parseFloat)); - break; - } - case "s": { - if (chunks[1] > 0) { - smoothing = true; - } else { - smoothing = false; - - } - break; - } - case "f": { - const c1 = (smoothing ? "s" : "") + chunks[1]; - const c2 = (smoothing ? "s" : "") + chunks[2]; - const c3 = (smoothing ? "s" : "") + chunks[3]; - - let index1 = vertices.indexOf(c1); - if (index1 === -1) { - index1 = vertices.length; - vertices.push(c1); - } - - let index2 = vertices.indexOf(c2); - if (index2 === -1) { - index2 = vertices.length; - vertices.push(c2); - } - - let index3 = vertices.indexOf(c3); - if (index3 === -1) { - index3 = vertices.length; - vertices.push(c3); - } - - indices.push(index1, index2, index3); - break; - } + const line = lines[i]; + const rawchunks = line.split(" ").map(x => x.trim()); + const chunks = []; + for (let i = 0; i < rawchunks.length; i++) { + if (rawchunks[i] != '') { + chunks.push(rawchunks[i]); } } + switch (chunks[0]) { + case "v": { + positions.push(chunks.splice(1).map(parseFloat)); + break; + } + case "vt": { + const uv = chunks.splice(1); + uvs.push([parseFloat(uv[0]), parseFloat(uv[1])]); + break; + } + case "vn": { + normals.push(chunks.splice(1).map(parseFloat)); + break; + } + case "s": { + if (chunks[1] > 0) { + smoothing = true; + } else { + smoothing = false; + + } + break; + } + case "f": { + const c1 = (smoothing ? "s" : "") + chunks[1]; + const c2 = (smoothing ? "s" : "") + chunks[2]; + const c3 = (smoothing ? "s" : "") + chunks[3]; + + + let index1 = vertices.indexOf(c1); + if (index1 === -1) { + index1 = vertices.length; + vertices.push(c1); + } + + let index2 = vertices.indexOf(c2); + if (index2 === -1) { + index2 = vertices.length; + vertices.push(c2); + } + + let index3 = vertices.indexOf(c3); + if (index3 === -1) { + index3 = vertices.length; + vertices.push(c3); + } + + indices.push(index1, index2, index3); + break; + } + } + } + + const avgNormals = []; + for (let i = 0; i < vertices.length; i++) { + if (vertices[i].startsWith("s")) { + const d = vertices[i].substr(1).split("/"); + const normal = normals[d[2] - 1]; + const index = d[0] - 1; + + if (avgNormals[index]) { + avgNormals[index][0] += normal[0]; + avgNormals[index][1] += normal[1]; + avgNormals[index][2] += normal[2]; + } else { + avgNormals[index] = normal; + } + } + } + + for (let n of avgNormals) { + const len = Math.hypot(...n); + avgNormals[avgNormals.indexOf(n)] = n.map(x => x / len); } const outPositions = []; @@ -82,12 +101,16 @@ export default function convert (objText) { const d = (vertices[i].startsWith("s") ? vertices[i].substr(1) : vertices[i]).split("/"); outPositions.push(...positions[d[0] - 1]); - if (normals.length) { + + if (vertices[i].startsWith("s")) { + outNormals.push(...avgNormals[d[0] - 1]); + } else { outNormals.push(...normals[d[2] - 1]); } - if (uvs.length) { - outUVs.push(...uvs[d[1] - 1]); - } + outUVs.push(...uvs[d[1] - 1]); } + + console.log(indices); + return [outPositions, outNormals, outUVs, indices]; }; \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index b7e0871..386bdc9 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -15,18 +15,18 @@
- - - - + + + +
- +
Max distance is 1000
- +
@@ -35,10 +35,10 @@
- - + +