UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

291 lines (228 loc) • 7.72 kB
import { DoubleSide, GLSL3, LinearFilter, Mesh as ThreeMesh, OrthographicCamera, RedIntegerFormat, RGBAIntegerFormat, RGIntegerFormat, Scene, ShaderMaterial, WebGLRenderTarget } from "three"; import { assert } from "../../../../../core/assert.js"; import { UINT32_MAX } from "../../../../../core/binary/UINT32_MAX.js"; import { CanvasView } from "../../../../../view/elements/CanvasView.js"; import { FULL_SCREEN_TRIANGLE_GEOMETRY } from "../../../geometry/FULL_SCREEN_TRIANGLE_GEOMETRY.js"; import { glsl_gen_swizzled_read } from "../../../shaders/glsl_gen_swizzled_read.js"; import CheckersTexture from "../../../texture/CheckersTexture.js"; import { formatToChannelCount } from "../../../texture/formatToChannelCount.js"; import { GL_RGBIntegerFormat } from "../../../texture/GL_RGBIntegerFormat.js"; import { Sampler2D } from "../../../texture/sampler/Sampler2D.js"; /** * * @param {THREE.Texture} texture * @returns {string} */ function defineSamplerType(texture) { switch (texture.format) { case RedIntegerFormat: case RGIntegerFormat: case GL_RGBIntegerFormat: case RGBAIntegerFormat: return 'usampler2D'; default: return 'sampler2D' } } /** * * @param {THREE.Texture} texture * @param {string} name * @param {string} uv * @returns {string} */ function sampleTexel(texture, name, uv) { const texture_sample = `texture2D(${name}, ${uv})`; switch (texture.format) { case RedIntegerFormat: return `vec4(${texture_sample})`; default: return texture_sample; } } /** * * @param {THREE.Texture} texture * @param {string} sample * @retunrs {string} */ function scaleSample(texture, sample) { switch (texture.internalFormat) { case "R32UI": return `${sample} * ${1 / UINT32_MAX}` default: return sample; } } /** * * @param {THREE.Texture} texture * @param {number} [alpha_override] will force specified alpha value in output, useful for cases where alpha would be 0 otherwise * @param {(string|number)[]} swizzle allows us to re-arrange pixels as they are being written out */ function makeShader({ texture, alpha_override, swizzle }) { let _swizzle = swizzle.slice(); const texture_channel_count = formatToChannelCount(texture.format); if (texture_channel_count < 4) { // no override specified, and alpha would be 0, force it to one to make image visible _swizzle[3] = 1; } return new ShaderMaterial({ vertexShader: /* glsl */` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }`, fragmentShader: /* glsl */` precision mediump usampler2D; uniform ${defineSamplerType(texture)} tDiffuse; varying vec2 vUv; out vec4 out_frag; void main() { vec4 texel = ${scaleSample(texture, sampleTexel(texture, 'tDiffuse', 'vUv'))}; out_frag = ${glsl_gen_swizzled_read('texel', _swizzle)}; }`, uniforms: { 'tDiffuse': { value: null } }, glslVersion: GLSL3 }); } /** * * @param {number} height * @param {number} width * @param {THREE.Texture} texture * @param {THREE.WebGLRenderer} renderer * @param {boolean} [flipY] * @param {(string|number)[]} [swizzle] * @param {Sampler2D} [destination] * @return {{destination: Sampler2D, render: function}} */ export function renderSamplerFromTexture({ texture = CheckersTexture.create(), width = texture.image.width, height = texture.image.height, renderer, flipY = true, swizzle = ['r', 'g', 'b', 'a'], destination = Sampler2D.uint8clamped(4, width, height) }) { assert.isArray(swizzle, 'swizzle'); let copyShader = null; const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1); const scene = new Scene(); scene.add(camera); const quad = new ThreeMesh(FULL_SCREEN_TRIANGLE_GEOMETRY, null); if (flipY) { quad.scale.y = -1; } scene.add(quad); const gl = renderer.getContext(); const renderTargetOptions = { generateMipmaps: false, minFilter: LinearFilter, stencilBuffer: false, depthBuffer: false }; const renderTarget = new WebGLRenderTarget(width, height, renderTargetOptions); let __texture_cached = texture; /** * * @return {ShaderMaterial} */ function getShader() { if (__texture_cached !== texture || copyShader === null) { copyShader = makeShader({ texture: texture, swizzle }); copyShader.side = DoubleSide; copyShader.uniforms.tDiffuse.value = texture; quad.material = copyShader; __texture_cached = texture; } return copyShader; } const render = () => { const shader = getShader(); shader.uniforms.tDiffuse.value = texture; shader.uniformsNeedUpdate = true; const _rt = renderer.getRenderTarget(); renderer.setRenderTarget(renderTarget); renderer.render(scene, camera); destination.resize(width, height); gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, destination.data); renderer.setRenderTarget(_rt); }; return { destination, render, set texture(v) { if (v !== texture) { texture = v; getShader().uniforms.tDiffuse.value = v; } } } } /** * * @param {number} height * @param {number} width * @param {THREE.Texture} texture * @param {THREE.WebGLRenderer} renderer * @param {boolean} [flipY] * @param {(string|number)[]} [swizzle] * @return {{view: View, render: function}} */ export function buildCanvasViewFromTexture({ texture, width = texture.image.width, height = texture.image.height, renderer, flipY = true, swizzle }) { const canvasView = new CanvasView(); canvasView.css({ position: 'absolute', left: 0, top: 0 }); canvasView.size.set(width, height); const ctx = canvasView.context2d; const imageData = ctx.createImageData(width, height); // wrap imageData in Sampler2D const sampler = new Sampler2D(imageData.data, 4, width, height); const ctrl = renderSamplerFromTexture({ texture, width, height, flipY, renderer, swizzle, destination: sampler }); function render() { ctrl.render(); ctx.putImageData(imageData, 0, 0); } return { view: canvasView, render, set texture(v) { ctrl.texture = v; } } }