@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
291 lines (228 loc) • 7.72 kB
JavaScript
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;
}
}
}