UNPKG

@mesmotronic/three-retropass

Version:

RetroPass applies a retro aesthetic to your Three.js project, emulating the visual style of classic 8-bit and 16-bit games

340 lines (332 loc) 9.71 kB
var U = Object.defineProperty; var C = (e) => { throw TypeError(e); }; var T = (e, o, t) => o in e ? U(e, o, { enumerable: !0, configurable: !0, writable: !0, value: t }) : e[o] = t; var p = (e, o, t) => T(e, typeof o != "symbol" ? o + "" : o, t), m = (e, o, t) => o.has(e) || C("Cannot " + t); var l = (e, o, t) => (m(e, o, "read from private field"), t ? t.call(e) : o.get(e)), c = (e, o, t) => o.has(e) ? C("Cannot add the same private member more than once") : o instanceof WeakSet ? o.add(e) : o.set(e, t), d = (e, o, t, i) => (m(e, o, "write to private field"), i ? i.call(e, t) : o.set(e, t), t); import * as r from "three"; import { ShaderPass as P } from "three/addons/postprocessing/ShaderPass.js"; function w(e) { const o = e.length, t = 1, i = new Uint8Array(o * 4); for (let s = 0; s < e.length; s++) { const a = e[s]; i[s * 4] = Math.floor(a.r * 255), i[s * 4 + 1] = Math.floor(a.g * 255), i[s * 4 + 2] = Math.floor(a.b * 255), i[s * 4 + 3] = 255; } const n = new r.DataTexture( i, o, t, r.RGBAFormat, r.UnsignedByteType ); return n.needsUpdate = !0, n; } function x(e) { let o = []; switch (!0) { // 4096 colours - Full Atari STE / Amiga color palette case e > 512: { const t = []; for (let i = 0; i < 16; i++) for (let n = 0; n < 16; n++) for (let s = 0; s < 16; s++) t.push(new r.Color(i / 15, n / 15, s / 15)); o = t; break; } // 512 colours - Full Atari ST (before E) color palette case e > 256: { const t = []; for (let i = 0; i < 8; i++) for (let n = 0; n < 8; n++) for (let s = 0; s < 8; s++) t.push(new r.Color(i / 7, n / 7, s / 7)); o = t; break; } // 256 colours - Web safe palette plus grayscale case e > 16: { const t = [], i = [0, 0.2, 0.4, 0.6, 0.8, 1]; for (let n of i) for (let s of i) for (let a of i) t.push(new r.Color(n, s, a)); for (let n = 0; n < 40; n++) { const s = n / 39; t.push(new r.Color(s, s, s)); } o = t.slice(0, 256); break; } // 16 colours - Microsoft Windows Standard VGA Palette case e > 4: { o = [ new r.Color(0), // Black new r.Color(170), // Blue new r.Color(43520), // Green new r.Color(43690), // Cyan new r.Color(11141120), // Red new r.Color(11141290), // Magenta new r.Color(11162880), // Brown new r.Color(11184810), // Light Gray new r.Color(5592405), // Dark Gray new r.Color(5592575), // Light Blue new r.Color(5635925), // Light Green new r.Color(5636095), // Light Cyan new r.Color(16733525), // Light Red new r.Color(16733695), // Light Magenta new r.Color(16777045), // Yellow new r.Color(16777215) // White ]; break; } // 4 colours - CGA mode 1 case e > 2: { o = [ new r.Color(0), // Black new r.Color(43690), // Cyan new r.Color(11141290), // Magenta new r.Color(16777215) // White ]; break; } // 2 colours - Monochrome case e >= 0: { o = [ new r.Color(0), // Black new r.Color(16777215) // White ]; break; } default: throw new Error(`Invalid colorCount: ${e}`); } return o.length !== e && console.warn(`No default color palette found for ${e} colors, using ${o.length} colors instead.`), o; } const M = { uniforms: { tDiffuse: { value: null }, resolution: { value: new r.Vector2(320, 200) }, colorCount: { value: 16 }, colorTexture: { value: w(x(16)) }, dithering: { value: !0 }, ditheringOffset: { value: 0.2 } }, vertexShader: ( /* glsl */ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } ` ), fragmentShader: ( /* glsl */ ` uniform sampler2D tDiffuse; uniform vec2 resolution; uniform int colorCount; uniform sampler2D colorTexture; uniform bool dithering; uniform float ditheringOffset; varying vec2 vUv; // Bayer matrix 4x4 const float bayer4x4[16] = float[16]( 0.0 / 16.0, 8.0 / 16.0, 2.0 / 16.0, 10.0 / 16.0, 12.0 / 16.0, 4.0 / 16.0, 14.0 / 16.0, 6.0 / 16.0, 3.0 / 16.0, 11.0 / 16.0, 1.0 / 16.0, 9.0 / 16.0, 15.0 / 16.0, 7.0 / 16.0, 13.0 / 16.0, 5.0 / 16.0 ); void main() { // Compute retro UV for pixelation vec2 retroUV = (floor(vUv * resolution) + 0.5) / resolution; vec3 c = texture2D(tDiffuse, retroUV).rgb; // Compute retro pixel coordinates vec2 retroCoord = floor(vUv * resolution); if (dithering) { float offset = 0.0; // Skip dithering for pure black pixels if (!(c.r == 0.0 && c.g == 0.0 && c.b == 0.0)) { int ix = int(mod(retroCoord.x, 4.0)); int iy = int(mod(retroCoord.y, 4.0)); float bayer = bayer4x4[iy * 4 + ix]; offset = (bayer - 0.5) * ditheringOffset; } c += vec3(offset); c = clamp(c, 0.0, 1.0); } // Find closest color in palette vec3 closestColor = vec3(0.0); float minDist = 1e6; for (int i = 0; i < colorCount; i++) { vec3 paletteColor = texture2D(colorTexture, vec2((float(i) + 0.5) / float(colorCount), 0.5)).rgb; float dist = distance(c, paletteColor); if (dist < minDist) { minDist = dist; closestColor = paletteColor; } } gl_FragColor = vec4(closestColor, 1.0); } ` ) }, O = 2, b = 4096; Array.from({ length: b - O + 1 }).map((e, o) => o + 2); function v(e) { return e === r.MathUtils.clamp(e, O, b); } var g, f, u, h; class _ extends P { /** * Creates a new RetroPass instance * @param parameters - Configuration parameters for the retro effect */ constructor({ colorCount: t = 16, colorPalette: i, dithering: n = !0, ditheringOffset: s = 0.2, autoDitheringOffset: a = !1, pixelRatio: R = 0.25, resolution: D = new r.Vector2(320, 200), autoResolution: y = !1 } = {}) { super(M); p(this, "size", new r.Vector2()); c(this, g); c(this, f, !1); c(this, u, !1); c(this, h, 0); this.dithering = n, this.ditheringOffset = s, this.autoDitheringOffset = a, this.pixelRatio = R, this.resolution = D, this.autoResolution = y, i ? this.colorPalette = i : this.colorCount = t; } /** * Pixel resolution to use */ get resolution() { return this.uniforms.resolution.value; } set resolution(t) { t.equals(this.uniforms.resolution.value) || this.uniforms.resolution.value.copy(t); } /** * Whether to automatically update the resolution based on the specified pixelRatio */ get autoResolution() { return l(this, u); } set autoResolution(t) { l(this, u) !== t && (d(this, u, t), this.updateResolution()); } /** * Pixel ratio to use if autoResolution is true, typically 0.0-1.0 (optional) */ get pixelRatio() { return l(this, h); } set pixelRatio(t) { l(this, h) !== t && (d(this, h, t), this.updateResolution()); } /** * The number of colors in the palette */ get colorCount() { return this.uniforms.colorCount.value; } set colorCount(t) { if (t !== this.colorCount) { if (!v(t)) throw new Error("Invalid colorPalette, must contain between 2 and 4096 colours"); this.colorPalette = x(t); } } /** * The current color palette */ get colorPalette() { return l(this, g); } set colorPalette(t) { var s; const i = t == null ? void 0 : t.length; if (!v(i)) throw new Error("Invalid colorPalette, must contain between 2 and 4096 colours"); const n = w(t); this.uniforms.colorCount.value = i, (s = this.uniforms.colorTexture.value) == null || s.dispose(), this.uniforms.colorTexture.value = n, this.autoDitheringOffset && this.updateDitheringOffset(), d(this, g, t.slice()); } /** * Whether or not to apply dithering */ get dithering() { return this.uniforms.dithering.value; } set dithering(t) { this.uniforms.dithering.value = t; } /** * The amount of dithering to apply, typically 0.0 to 1.0 */ get ditheringOffset() { return this.uniforms.ditheringOffset.value; } set ditheringOffset(t) { this.uniforms.ditheringOffset.value = t; } /** * Whether to automatically update the dithering offset based on the color count */ get autoDitheringOffset() { return l(this, f); } set autoDitheringOffset(t) { l(this, f) !== t && (d(this, f, t), t && this.updateDitheringOffset()); } /** * Set the pixel resolution to use (used by EffectComposer) * @see {@link RetroPass.resolution} */ setSize(t, i) { this.size.set(t, i), this.updateResolution(); } /** * Updates the resolution based on the current pixel ratio and size */ updateResolution() { this.autoResolution && this.resolution.set(Math.round(this.size.x * this.pixelRatio), Math.round(this.size.y * this.pixelRatio)); } /** * Updates the dithering offset based on the current color count */ updateDitheringOffset() { this.autoDitheringOffset && (this.ditheringOffset = 0.1 + 0.9 / (this.colorCount - 1)); } } g = new WeakMap(), f = new WeakMap(), u = new WeakMap(), h = new WeakMap(); export { _ as RetroPass, x as createColorPalette };