UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

346 lines (262 loc) • 10.1 kB
import { ClampToEdgeWrapping, DoubleSide, GLSL3, Matrix4, NearestFilter, NoBlending, RawShaderMaterial, RGBAIntegerFormat, UnsignedByteType, Vector2, Vector4, WebGLRenderTarget } from "three"; import { assert } from "../../../../core/assert.js"; import { array_copy } from "../../../../core/collection/array/array_copy.js"; import { clamp } from "../../../../core/math/clamp.js"; import { max2 } from "../../../../core/math/max2.js"; import { generate_halton_jitter } from "../../../../core/math/random/generate_halton_jitter.js"; import { renderScreenSpace } from "../../render/utils/renderScreenSpace.js"; import { Sampler2D } from "../sampler/Sampler2D.js"; import { VirtualTextureUsage } from "./VirtualTextureUsage.js"; import { fragment, vertex } from "./VirtualTextureUsageShader.js"; const usage_material_uniforms = { "u_mt_params": { value: new Vector2(0, 1) }, /** * Format: VT_WIDTH, VT_HEIGHT, VT_TILE_SIZE, VT_ID * - VT_WIDTH : width of the virtual texture * - VT_HEIGHT : height of the virtual texture * - VT_TILE_SIZE : resolution of a single tile * - VT_ID : multiple different virtual textures may be used, this identifies current texture */ "u_mt_tex": { value: new Vector4(1, 1, 1, 3) }, "u_mt_viewport_scale": { value: new Vector2(1, 1) }, "modelViewMatrix": { value: new Matrix4() }, "projectionMatrix": { value: new Matrix4() } }; const usage_material = new RawShaderMaterial({ uniforms: usage_material_uniforms, vertexShader: vertex(), fragmentShader: fragment(), side: DoubleSide, glslVersion: GLSL3 }); usage_material.blending = NoBlending; const clear_usage_material = new RawShaderMaterial({ depthTest: false, depthWrite: false, vertexShader: ` precision mediump float; in vec2 uv; void main() { gl_Position = vec4( (uv - 0.5)*2.0, 0.0, 1.0 ); }`, fragmentShader: ` precision mediump int; out uvec4 out_decoded; void main(){ out_decoded = uvec4(0u); } `, glslVersion: GLSL3 }); const scratch_matrix = new Float32Array(16); export class VirtualTextureUsageUpdater { #frame_index = 0; /** * In order to avoid aliasing due to the fact that we render at a lower resolution, we move the camera slightly each frame to sweep across texels in-between * @type {Float32Array} */ #frame_jitter_offsets = new Float32Array([0, 0]); #frame_jitter_samples = 1; #frame_jitter_enabled = false; get frame_jitter_enabled() { return this.#frame_jitter_enabled; } /** * * @param {boolean} v */ set frame_jitter_enabled(v) { this.#frame_jitter_enabled = v; } #initialize_frame_jitter() { this.#frame_jitter_samples = this.#usage_resolution_scale; this.#frame_jitter_offsets = generate_halton_jitter(this.#frame_jitter_samples); } #usage_buffer = new WebGLRenderTarget(1, 1, { wrapT: ClampToEdgeWrapping, wrapS: ClampToEdgeWrapping, minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAIntegerFormat, type: UnsignedByteType, internalFormat: "RGBA8UI", anisotropy: 1, depthBuffer: true, stencilBuffer: false, generateMipmaps: false, }); #usage_pixel_data = Sampler2D.uint8(4, 1, 1); #viewport_resolution = [1, 1]; /** * * @type {number} */ #usage_resolution_scale = 32; #usage_metadata = new VirtualTextureUsage(); #texture_id = 3; #texture_resolution = 65536; #tile_resolution = 256; #max_mip_level = 0; get texture_resolution() { return this.#texture_resolution; } get tile_resolution() { return this.#tile_resolution; } get max_mip_level() { return this.#max_mip_level; } /** * Used to fetch higher/lower resolution tiles, negative value will fetch higher resolution tiles, and vice-versa * @type {number} */ #usage_texture_bias = 0; /** * * @param {number} resolution * @param {number} tile_size * @param {number} texture_id */ setTextureParameters( resolution, tile_size, texture_id ) { assert.isNonNegativeInteger(resolution,'resolution'); assert.lessThanOrEqual(tile_size, resolution, `tile_size(=${tile_size}) must be <= than the overall texture resolution(=${resolution})`); this.#texture_id = texture_id; this.#texture_resolution = resolution; this.#tile_resolution = tile_size; this.#max_mip_level = Math.log2(this.#texture_resolution / this.#tile_resolution); if (!Number.isInteger(this.#max_mip_level)) { throw new Error(`texture resolution must be a power-of-two multiple of tile resolution`); } this.#initialize_usage_uniforms(); this.#usage_metadata.max_mip_level = this.#max_mip_level; } get usage_metadata() { return this.#usage_metadata; } get usage_data() { return this.#usage_pixel_data; } constructor() { const target = this.#usage_buffer; target.scissorTest = false; this.#initialize_usage_uniforms(); this.#initialize_frame_jitter(); } #initialize_usage_uniforms() { const uniforms = usage_material.uniforms; const max_mip_level = this.#max_mip_level; uniforms.u_mt_params.value.set( this.#usage_texture_bias, max_mip_level ); uniforms.u_mt_tex.value.set( this.#texture_resolution, this.#texture_resolution, this.#tile_resolution, this.#texture_id ); } /** * * @param {number} x * @param {number} y */ setViewportResolution(x, y) { assert.isNumber(x, 'x'); assert.isNumber(y, 'y'); assert.notNaN(x, 'x'); assert.notNaN(y, 'y'); const _x = max2(1, Math.floor(x)); const _y = max2(1, Math.floor(y)); const viewport = this.#viewport_resolution; if (_x === viewport[0] && _y === viewport[1]) { // no change, do nothing return; } viewport[0] = _x; viewport[1] = _y; const usage_resolution_x = clamp(Math.floor(_x / this.#usage_resolution_scale), 1, _x); const usage_resolution_y = clamp(Math.floor(_y / this.#usage_resolution_scale), 1, _y); this.#usage_buffer.setSize(usage_resolution_x, usage_resolution_y); this.#usage_pixel_data.resize(usage_resolution_x, usage_resolution_y, true); const usage_material_uniforms = usage_material.uniforms; usage_material_uniforms.u_mt_viewport_scale.value.set(usage_resolution_x / _x, usage_resolution_y / _y); } #prepareUsageUniforms(camera) { const uniforms = usage_material.uniforms; const usage_buffer = this.#usage_buffer; const camera_projection_matrix = camera.projectionMatrix.elements; if (this.#frame_jitter_enabled) { // apply jitter to projection matrix const jitter_index = this.#frame_index % this.#frame_jitter_samples; const jitter_index_2 = jitter_index * 2; const jitter_offset_x = this.#frame_jitter_offsets[jitter_index_2]; const jitter_offset_y = this.#frame_jitter_offsets[jitter_index_2 + 1]; camera_projection_matrix[8] = jitter_offset_x / usage_buffer.width; camera_projection_matrix[9] = jitter_offset_y / usage_buffer.height; } uniforms.modelViewMatrix.value.multiplyMatrices(camera.matrixWorldInverse, camera.matrixWorld); uniforms.projectionMatrix.value.copy(camera.projectionMatrix); } /** * * @param {WebGLRenderer} renderer * @param {Scene} scene * @param {Camera} camera */ updateUsage(renderer, scene, camera) { const usage_buffer = this.#usage_buffer; const camera_projection_matrix = camera.projectionMatrix.elements; // remember existing state const _old_render_target = renderer.getRenderTarget(); const _auto_clear = renderer.autoClear; array_copy(camera_projection_matrix, 0, scratch_matrix, 0, 16); const _old_material = scene.overrideMaterial; const _old_camera_matrix_update = camera.matrixAutoUpdate; camera.matrixAutoUpdate = false; renderer.autoClear = false; renderer.setRenderTarget(usage_buffer); // clear out usage texture renderScreenSpace(renderer, clear_usage_material); renderer.clearDepth(); scene.overrideMaterial = usage_material; this.#prepareUsageUniforms(camera); renderer.render(scene, camera); renderer.readRenderTargetPixels( usage_buffer, 0, 0, usage_buffer.width, usage_buffer.height, this.#usage_pixel_data.data ); // restore state scene.overrideMaterial = _old_material; renderer.setRenderTarget(_old_render_target); renderer.autoClear = _auto_clear; array_copy(scratch_matrix, 0, camera_projection_matrix, 0, 16); camera.matrixAutoUpdate = _old_camera_matrix_update; // this.#usage_metadata.fromTexture(this.#usage_pixel_data.data); this.#usage_metadata.promoteAncestors(1); this.#usage_metadata.sortOccupancyByLOD(); this.#frame_index++; } dispose() { this.#usage_buffer.dispose(); } }