/** * The batch data that is needed by {@link OccludableSamplerShader} to render an element with batching. * @typedef {object} OccludableBatchData * @property {PIXI.Texture} _texture The texture * @property {Float32Array} vertexData The vertices * @property {Uint16Array|Uint32Array|number[]} indices The indices * @property {Float32Array} uvs The texture UVs * @property {number} worldAlpha The world alpha * @property {number} _tintRGB The tint * @property {number} blendMode The blend mode * @property {number} elevation The elevation * @property {number} unoccludedAlpha The unoccluded alpha * @property {number} occludedAlpha The unoccluded alpha * @property {number} fadeOcclusion The amount of FADE occlusion * @property {number} radialOcclusion The amount of RADIAL occlusion * @property {number} visionOcclusion The amount of VISION occlusion */ /** * The occlusion sampler shader. */ class OccludableSamplerShader extends BaseSamplerShader { /** * The fragment shader code that applies occlusion. * @type {string} */ static #OCCLUSION = ` vec3 occluded = 1.0 - step(occlusionElevation, texture(occlusionTexture, vScreenCoord).rgb); float occlusion = max(occluded.r * fadeOcclusion, max(occluded.g * radialOcclusion, occluded.b * visionOcclusion)); fragColor *= mix(unoccludedAlpha, occludedAlpha, occlusion); `; /* -------------------------------------------- */ /* Batched version Rendering */ /* -------------------------------------------- */ /** @override */ static classPluginName = "batchOcclusion"; /* ---------------------------------------- */ /** @override */ static batchGeometry = [ {id: "aVertexPosition", size: 2, normalized: false, type: PIXI.TYPES.FLOAT}, {id: "aTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT}, {id: "aColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE}, {id: "aTextureId", size: 1, normalized: false, type: PIXI.TYPES.UNSIGNED_SHORT}, {id: "aOcclusionAlphas", size: 2, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE}, {id: "aOcclusionData", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE} ]; /* -------------------------------------------- */ /** @override */ static batchVertexSize = 7; /* -------------------------------------------- */ /** @override */ static reservedTextureUnits = 1; // We need a texture unit for the occlusion texture /* -------------------------------------------- */ /** @override */ static defaultUniforms = { screenDimensions: [1, 1], sampler: null, tintAlpha: [1, 1, 1, 1], occlusionTexture: null, unoccludedAlpha: 1, occludedAlpha: 0, occlusionElevation: 0, fadeOcclusion: 0, radialOcclusion: 0, visionOcclusion: 0 }; /* -------------------------------------------- */ /** @override */ static batchDefaultUniforms(maxTex) { return { screenDimensions: [1, 1], occlusionTexture: maxTex }; } /* -------------------------------------------- */ /** @override */ static _preRenderBatch(batchRenderer) { const occlusionMask = canvas.masks.occlusion; const uniforms = batchRenderer._shader.uniforms; uniforms.screenDimensions = canvas.screenDimensions; batchRenderer.renderer.texture.bind(occlusionMask.renderTexture, uniforms.occlusionTexture); } /* ---------------------------------------- */ /** @override */ static _packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) { const {float32View, uint8View, uint16View, uint32View} = attributeBuffer; // Write indices into buffer const packedVertices = aIndex / this.vertexSize; const indices = element.indices; for ( let i = 0; i < indices.length; i++ ) { indexBuffer[iIndex++] = packedVertices + indices[i]; } // Prepare attributes const vertexData = element.vertexData; const uvs = element.uvs; const baseTexture = element._texture.baseTexture; const alpha = Math.min(element.worldAlpha, 1.0); const argb = PIXI.Color.shared.setValue(element._tintRGB).toPremultiplied(alpha, baseTexture.alphaMode > 0); const textureId = baseTexture._batchLocation; const unoccludedAlpha = (element.unoccludedAlpha * 255) | 0; const occludedAlpha = (element.occludedAlpha * 255) | 0; const occlusionElevation = (canvas.masks.occlusion.mapElevation(element.elevation) * 255) | 0; const fadeOcclusion = (element.fadeOcclusion * 255) | 0; const radialOcclusion = (element.radialOcclusion * 255) | 0; const visionOcclusion = (element.visionOcclusion * 255) | 0; // Write attributes into buffer const vertexSize = this.vertexSize; for ( let i = 0, j = 0; i < vertexData.length; i += 2, j += vertexSize ) { let k = aIndex + j; float32View[k++] = vertexData[i]; float32View[k++] = vertexData[i + 1]; float32View[k++] = uvs[i]; float32View[k++] = uvs[i + 1]; uint32View[k++] = argb; k <<= 1; uint16View[k++] = textureId; k <<= 1; uint8View[k++] = unoccludedAlpha; uint8View[k++] = occludedAlpha; uint8View[k++] = occlusionElevation; uint8View[k++] = fadeOcclusion; uint8View[k++] = radialOcclusion; uint8View[k++] = visionOcclusion; } } /* -------------------------------------------- */ /** @override */ static get batchVertexShader() { return ` #version 300 es ${this.GLSL1_COMPATIBILITY_VERTEX} precision ${PIXI.settings.PRECISION_VERTEX} float; in vec2 aVertexPosition; in vec2 aTextureCoord; in vec4 aColor; uniform mat3 translationMatrix; uniform vec4 tint; uniform vec2 screenDimensions; ${this._batchVertexShader} in float aTextureId; in vec2 aOcclusionAlphas; in vec4 aOcclusionData; uniform mat3 projectionMatrix; out vec2 vTextureCoord; out vec2 vScreenCoord; flat out vec4 vColor; flat out float vTextureId; flat out float vUnoccludedAlpha; flat out float vOccludedAlpha; flat out float vOcclusionElevation; flat out float vFadeOcclusion; flat out float vRadialOcclusion; flat out float vVisionOcclusion; void main() { vec2 vertexPosition; vec2 textureCoord; vec4 color; _main(vertexPosition, textureCoord, color); gl_Position = vec4((projectionMatrix * vec3(vertexPosition, 1.0)).xy, 0.0, 1.0); vTextureCoord = textureCoord; vScreenCoord = vertexPosition / screenDimensions; vColor = color; vTextureId = aTextureId; vUnoccludedAlpha = aOcclusionAlphas.x; vOccludedAlpha = aOcclusionAlphas.y; vOcclusionElevation = aOcclusionData.x; vFadeOcclusion = aOcclusionData.y; vRadialOcclusion = aOcclusionData.z; vVisionOcclusion = aOcclusionData.w; } `; } /* -------------------------------------------- */ /** * The batch vertex shader source. Subclasses can override it. * @type {string} * @protected */ static _batchVertexShader = ` void _main(out vec2 vertexPosition, out vec2 textureCoord, out vec4 color) { vertexPosition = (translationMatrix * vec3(aVertexPosition, 1.0)).xy; textureCoord = aTextureCoord; color = aColor * tint; } `; /* ---------------------------------------- */ /** @override */ static get batchFragmentShader() { return ` #version 300 es ${this.GLSL1_COMPATIBILITY_FRAGMENT} precision ${PIXI.settings.PRECISION_FRAGMENT} float; in vec2 vTextureCoord; in vec2 vScreenCoord; flat in vec4 vColor; flat in float vTextureId; uniform sampler2D uSamplers[%count%]; ${this._batchFragmentShader} flat in float vUnoccludedAlpha; flat in float vOccludedAlpha; flat in float vOcclusionElevation; flat in float vFadeOcclusion; flat in float vRadialOcclusion; flat in float vVisionOcclusion; uniform sampler2D occlusionTexture; out vec4 fragColor; void main() { fragColor = _main(); float unoccludedAlpha = vUnoccludedAlpha; float occludedAlpha = vOccludedAlpha; float occlusionElevation = vOcclusionElevation; float fadeOcclusion = vFadeOcclusion; float radialOcclusion = vRadialOcclusion; float visionOcclusion = vVisionOcclusion; ${OccludableSamplerShader.#OCCLUSION} } `; } /* -------------------------------------------- */ /** * The batch fragment shader source. Subclasses can override it. * @type {string} * @protected */ static _batchFragmentShader = ` vec4 _main() { vec4 color; %forloop% return color * vColor; } `; /* -------------------------------------------- */ /* Non-Batched version Rendering */ /* -------------------------------------------- */ /** @override */ static get vertexShader() { return ` #version 300 es ${this.GLSL1_COMPATIBILITY_VERTEX} precision ${PIXI.settings.PRECISION_VERTEX} float; in vec2 aVertexPosition; in vec2 aTextureCoord; uniform vec2 screenDimensions; ${this._vertexShader} uniform mat3 projectionMatrix; out vec2 vUvs; out vec2 vScreenCoord; void main() { vec2 vertexPosition; vec2 textureCoord; _main(vertexPosition, textureCoord); gl_Position = vec4((projectionMatrix * vec3(vertexPosition, 1.0)).xy, 0.0, 1.0); vUvs = textureCoord; vScreenCoord = vertexPosition / screenDimensions; } `; } /* -------------------------------------------- */ /** * The vertex shader source. Subclasses can override it. * @type {string} * @protected */ static _vertexShader = ` void _main(out vec2 vertexPosition, out vec2 textureCoord) { vertexPosition = aVertexPosition; textureCoord = aTextureCoord; } `; /* -------------------------------------------- */ /** @override */ static get fragmentShader() { return ` #version 300 es ${this.GLSL1_COMPATIBILITY_FRAGMENT} precision ${PIXI.settings.PRECISION_FRAGMENT} float; in vec2 vUvs; in vec2 vScreenCoord; uniform sampler2D sampler; uniform vec4 tintAlpha; ${this._fragmentShader} uniform sampler2D occlusionTexture; uniform float unoccludedAlpha; uniform float occludedAlpha; uniform float occlusionElevation; uniform float fadeOcclusion; uniform float radialOcclusion; uniform float visionOcclusion; out vec4 fragColor; void main() { fragColor = _main(); ${OccludableSamplerShader.#OCCLUSION} } `; } /* -------------------------------------------- */ /** * The fragment shader source. Subclasses can override it. * @type {string} * @protected */ static _fragmentShader = ` vec4 _main() { return texture(sampler, vUvs) * tintAlpha; } `; /* -------------------------------------------- */ /** @inheritdoc */ _preRender(mesh, renderer) { super._preRender(mesh, renderer); const uniforms = this.uniforms; uniforms.screenDimensions = canvas.screenDimensions; const occlusionMask = canvas.masks.occlusion; uniforms.occlusionTexture = occlusionMask.renderTexture; uniforms.occlusionElevation = occlusionMask.mapElevation(mesh.elevation); uniforms.unoccludedAlpha = mesh.unoccludedAlpha; uniforms.occludedAlpha = mesh.occludedAlpha; const occlusionState = mesh._occlusionState; uniforms.fadeOcclusion = occlusionState.fade; uniforms.radialOcclusion = occlusionState.radial; uniforms.visionOcclusion = occlusionState.vision; } }