import {makeBufferFromFaces } from '../geometry'; import { loadTexture, makeProgram } from '../gl'; import * as linalg from './linalg'; import * as se3 from '../se3'; import { makeOrbitObject } from './orbit'; const VSHADER = ` attribute vec3 aPosition; attribute vec3 aNormal; attribute vec2 aTextureCoord; uniform mat4 uProjection; uniform mat4 uModel; uniform mat4 uView; uniform vec3 uLightDirection; uniform float uAmbiantLight; uniform vec3 uGlowColor; varying highp vec2 vTextureCoord; varying lowp vec3 vLighting; varying lowp vec3 vRay; varying lowp vec3 vLightDir; varying lowp vec3 vNormal; highp mat3 transpose(in highp mat3 inmat) { highp vec3 x = inmat[0]; highp vec3 y = inmat[1]; highp vec3 z = inmat[2]; return mat3( vec3(x.x, y.x, z.x), vec3(x.y, y.y, z.y), vec3(x.z, y.z, z.z) ); } void main() { highp mat4 modelview = uView * uModel; gl_Position = uProjection * modelview * vec4(aPosition, 1.0); lowp vec3 normal = mat3(uModel) * aNormal; lowp float diffuseAmount = max(dot(-uLightDirection, normal), 0.0); lowp vec3 ambiant = uAmbiantLight * vec3(1.0, 1.0, 0.9); vLighting = ambiant + vec3(1.0, 1.0, 1.0) * diffuseAmount + uGlowColor; vTextureCoord = aTextureCoord; lowp vec3 camPos = -transpose(mat3(uView))*(uView * vec4(0.0, 0.0, 0.0, 1.0)).xyz; vRay = -normalize((uModel * vec4(aPosition, 1.0)).xyz - camPos); vLightDir = -uLightDirection; vNormal = normal; } `; const FSHADER = ` uniform sampler2D uSampler; varying highp vec2 vTextureCoord; varying lowp vec3 vLighting; varying lowp vec3 vRay; varying lowp vec3 vLightDir; varying lowp vec3 vNormal; void main() { highp vec4 color = texture2D(uSampler, vTextureCoord); if (color.a < 0.1) { discard; } lowp vec3 specularDir = 2.0 * dot(vLightDir, vNormal) * vNormal - vLightDir; lowp float specularAmount = smoothstep(0.92, 1.0, dot(vRay, specularDir)); lowp vec3 specular = 0.8 * specularAmount * vec3(1.0, 1.0, 0.8); gl_FragColor = vec4(vLighting * color.rgb + specular, color.a); } `; export async function initWorldGl(gl: WebGLRenderingContext) { const program = makeProgram(gl, VSHADER, FSHADER); const texture = await loadTexture(gl, 'texture.png'); // load those ahead of time const viewLoc = gl.getUniformLocation(program, 'uView'); const modelLoc = gl.getUniformLocation(program, 'uModel'); const projLoc = gl.getUniformLocation(program, 'uProjection'); const samplerLoc = gl.getUniformLocation(program, 'uSampler'); const lightDirectionLoc = gl.getUniformLocation(program, 'uLightDirection'); const ambiantLoc = gl.getUniformLocation(program, 'uAmbiantLight'); const glowColorLoc = gl.getUniformLocation(program, 'uGlowColor'); const positionLoc = gl.getAttribLocation(program, 'aPosition'); const normalLoc = gl.getAttribLocation(program, 'aNormal'); const textureLoc = gl.getAttribLocation(program, 'aTextureCoord'); const setupScene = (sceneParams) => { const { projectionMatrix, viewMatrix, ambiantLightAmount, } = sceneParams; gl.useProgram(program); gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix)); gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix)); gl.uniform1f(ambiantLoc, ambiantLightAmount); // doing this here because it's the same for all world stuff gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.identity())); gl.uniform1i(samplerLoc, 0); gl.enableVertexAttribArray(positionLoc); gl.enableVertexAttribArray(normalLoc); gl.enableVertexAttribArray(textureLoc); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); }; const drawObject = (objectParams) => { const { position, orientation, glBuffer, numVertices, lightDirection, glowColor, } = objectParams; gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.product( se3.translation(...position), orientation))); gl.uniform3fv(lightDirectionLoc, lightDirection); gl.uniform3fv(glowColorLoc, glowColor); gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer); gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0); gl.vertexAttribPointer(normalLoc, 3, gl.BYTE, true, 20, 12); gl.vertexAttribPointer(textureLoc, 2, gl.UNSIGNED_SHORT, true, 20, 16); gl.drawArrays(gl.TRIANGLES, 0, numVertices); }; return { setupScene, drawObject, }; } const ORBIT_VSHADER = ` attribute vec3 aPosition; attribute vec2 aValue; uniform mat4 uProjection; uniform mat4 uModel; uniform mat4 uView; varying lowp vec2 vCoords; void main() { highp mat4 modelview = uView * uModel; gl_Position = uProjection * modelview * vec4(aPosition, 1.0); vCoords = aValue; } `; const ORBIT_FSHADER = ` varying lowp vec2 vCoords; void main() { lowp float x = vCoords.x; lowp float y = vCoords.y; lowp float f = sqrt(x * x + y * y); if (f > 1.00) { discard; } else if (f < 0.98) { discard; } gl_FragColor = vec4(1, .5, 0, 0.5); } `; export function getOrbitDrawContext(gl: WebGLRenderingContext) { const program = makeProgram(gl, ORBIT_VSHADER, ORBIT_FSHADER); // load those ahead of time const viewLoc = gl.getUniformLocation(program, 'uView'); const modelLoc = gl.getUniformLocation(program, 'uModel'); const projLoc = gl.getUniformLocation(program, 'uProjection'); const positionLoc = gl.getAttribLocation(program, 'aPosition'); const valueLoc = gl.getAttribLocation(program, 'aValue'); const setupScene = (sceneParams) => { const { projectionMatrix, viewMatrix, } = sceneParams; gl.useProgram(program); gl.uniformMatrix4fv(projLoc, false, new Float32Array(projectionMatrix)); gl.uniformMatrix4fv(viewLoc, false, new Float32Array(viewMatrix)); // doing this here because it's the same for all world stuff gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.identity())); gl.enableVertexAttribArray(positionLoc); gl.enableVertexAttribArray(valueLoc); }; const drawObject = (objectParams) => { const { position, orientation, value, glBuffer, } = objectParams; gl.uniformMatrix4fv(modelLoc, false, new Float32Array(se3.product( se3.translation(...position), orientation))); gl.bindBuffer(gl.ARRAY_BUFFER, glBuffer); gl.vertexAttribPointer(positionLoc, 3, gl.FLOAT, false, 20, 0); gl.vertexAttribPointer(valueLoc, 2, gl.FLOAT, false, 20, 12); gl.disable(gl.CULL_FACE); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); gl.enable(gl.CULL_FACE); }; return { setupScene, drawObject, }; } function getObjects(context, body, parentPosition = undefined) { const objects = []; const {gl, glContext, player} = context; const {position, orientation, glowColor} = body; if (body.glBuffer === undefined) { body.glBuffer = makeBufferFromFaces(gl, body.geometry); } objects.push({ geometry: body.glBuffer, orientation, position, glContext, glowColor, }); if (parentPosition !== undefined) { const orbitObject = makeOrbitObject(gl, context.orbitGlContext, body.orbit, parentPosition); objects.push(orbitObject); } else { const shipOrientation = [ se3.rotationOnly(player.tf), se3.rotationOnly(context.camera.tf), se3.rotxyz(-Math.PI / 2, Math.PI / 2, Math.PI / 2), ].reduce(se3.product); const shipPos = player.position; objects.push({ geometry: makeBufferFromFaces(gl, context.spaceship), orientation: shipOrientation, position: shipPos, glContext, }); } if (body.children !== undefined) { for (const child of body.children) { objects.push(...getObjects(context, child, position)); } } return objects; } function sunDirection(position: linalg.vec3) { return linalg.scale(position, 1/linalg.norm(position)); } export function draw(context) { const {gl, camera, player, universe, orbit, orbitGlContext, orbitBody} = context; const {skyColor, ambiantLight, projMatrix} = context; const objects = getObjects(context, universe); if (orbit !== undefined && orbit.excentricity < 1) { objects.push(makeOrbitObject(gl, orbitGlContext, orbit, orbitBody.position)); } gl.clearColor(...skyColor, 1.0); gl.clearDepth(1.0); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.enable(gl.CULL_FACE); gl.cullFace(gl.BACK); 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 viewMatrix = se3.inverse([ player.tf, // player position & orientation camera.tf, // camera orientation relative to player se3.translation(0, 1, 4), // step back from the player ].reduce(se3.product)); let lastGlContext; for (const {position, orientation, geometry, glContext, glowColor} of objects) { if (glContext !== lastGlContext) { glContext.setupScene({ projectionMatrix: projMatrix, viewMatrix, ambiantLightAmount: ambiantLight, }); } lastGlContext = glContext; const lightDirection = sunDirection(position); glContext.drawObject({ position, orientation, glBuffer: geometry.glBuffer, numVertices: geometry.numVertices, lightDirection, glowColor: glowColor || [0, 0, 0], }); } }