UNPKG

s2maps-gpu

Version:

S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.

156 lines (155 loc) 5.77 kB
import { Color } from 'style/color/index.js'; import Workflow from './workflow.js'; import { adjustURL } from 'util/index.js'; import { degToRad } from 'gis-tools/index.js'; import { invert, multiply, perspective, rotate } from 'ui/camera/projector/mat4.js'; // WEBGL1 import frag1 from '../shaders/skybox1.fragment.glsl'; import vert1 from '../shaders/skybox1.vertex.glsl'; // WEBGL2 import frag2 from '../shaders/skybox2.fragment.glsl'; import vert2 from '../shaders/skybox2.vertex.glsl'; /** Skybox Workflow renders a user styled skybox to the GPU */ export default class SkyboxWorkflow extends Workflow { label = 'skybox'; cubeMap; facesReady = 0; ready = false; fov = degToRad(80); angle = degToRad(40); matrix = new Float32Array(16); /** @param context - The WebGL(1|2) context */ constructor(context) { // get gl from context const { gl, type } = context; // install shaders super(context); // build shaders if (type === 1) this.buildShaders(vert1, frag1); else this.buildShaders(vert2, frag2); // prep a cube texture const cubeMap = gl.createTexture(); if (cubeMap === null) throw new Error('Failed to create skybox cube map texture'); this.cubeMap = cubeMap; } /** * Update the skybox style * @param style - user defined style attributes * @param camera - The camera * @param urlMap - The url map to properly resolve urls */ updateStyle(style, camera, urlMap) { const { context } = this; const { skybox } = style; const { type, size, loadingBackground } = skybox ?? {}; let path = skybox?.path; if (typeof path !== 'string') throw new Error('Skybox path must be a string'); if (typeof type !== 'string') throw new Error('Skybox type must be a string'); if (typeof size !== 'number') throw new Error('Skybox size must be a number'); path = adjustURL(path, urlMap); // grab clear color and set inside painter if (loadingBackground !== undefined) { context.setClearColor(new Color(loadingBackground ?? 'rgb(0, 0, 0)').getRGB()); } // reset our tracking variables this.facesReady = 0; this.ready = false; // request each face and assign to cube map for (let i = 0; i < 6; i++) void this.#getImage(i, `${path}/${size}/${i}.${type}`, camera); } /** * Get an image and assign to cube map * @param index - the index of the face on the cube * @param path - the path to the image * @param camera - The camera */ async #getImage(index, path, camera) { const { gl } = this; const data = await fetch(path) .then(async (res) => { if (res.status !== 200 && res.status !== 206) return; return await res.blob(); }) .catch(() => { return undefined; }); if (data === undefined) return; const image = await createImageBitmap(data); gl.bindTexture(gl.TEXTURE_CUBE_MAP, this.cubeMap); gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + index, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); // ensure size X size is a power of 2 (only way to generate mips) this.facesReady++; if (this.facesReady === 6) { gl.generateMipmap(gl.TEXTURE_CUBE_MAP); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); this.ready = true; // set the projector as dirty to ensure a proper initial render camera.projector.reset(); // call the full re-render camera.render(); } } /** * Update the perspective matrix * @param projector - The projector */ #updateMatrix(projector) { const { gl, uniforms, fov, angle, matrix } = this; const { aspect, lon, lat } = projector; // create a perspective matrix perspective(matrix, fov, aspect.x / aspect.y, 1, 10000); // rotate perspective rotate(matrix, [degToRad(lat), degToRad(lon), angle]); // this is a simplified "lookat", since we maintain a set camera position multiply(matrix, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]); // invert view invert(matrix); // set matrix if necessary gl.uniformMatrix4fv(uniforms.uMatrix, false, matrix); } /** Flush the uniforms to the GPU (no-op) */ flush() { /* no-op */ } /** Use this workflow as the current shaders for the GPU */ use() { super.use(); const { context } = this; // ignore z-fighting and only pass where stencil is 0 context.defaultBlend(); context.disableCullFace(); context.disableDepthTest(); context.enableStencilTest(); context.stencilFuncEqual(0); } /** * Draw the skybox * @param projector - The projector */ draw(projector) { // setup variables const { gl, context, ready, cubeMap } = this; // let the context know the current workflow context.setWorkflow(this); // if ready, time to draw if (ready) { // bind the texture cube map gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeMap); // update matrix if necessary if (projector.dirty) this.#updateMatrix(projector); // Draw the skybox context.drawQuad(); } } }