M frag.glsl => frag.glsl +60 -0
@@ 0,0 1,60 @@
+// #define PALETTE_SIZE 16
+
+// uniform vec3 palette[PALETTE_SIZE];
+uniform sampler2D palette;
+uniform sampler2D tDiffuse;
+uniform float threshold;
+in vec2 vUv;
+out vec4 fragColor;
+
+// https://discourse.threejs.org/t/what-version-of-webgl-does-three-use/19649/8
+// vec4 texelFetch( sampler2D tex, vec2 pos, vec2 res ) {
+// return texture2D( tex, ( pos + 0.5 ) / res );
+// }
+
+vec3 paletteEntry(int i) {
+ return texelFetch(palette, ivec2(i, 0), 0).rgb;
+}
+
+void main() {
+ vec3 color = texture2D(tDiffuse, vUv).rgb;
+ bool isEven = mod(gl_FragCoord.x + gl_FragCoord.y, 2.0) == 0.0;
+ float closestDistance = 1.0;
+ vec3 closestColor = paletteEntry(0);
+ int firstIndex = 0;
+ int secondIndex = 0;
+ float secondClosestDistance = 1.0;
+ vec3 secondClosestColor = paletteEntry(1);
+ /*
+ * loop through the palette colors and compute the two closest colors
+ * to the input pixel color
+ */
+ for(int i = 0; i < PALETTE_SIZE; i++) {
+ vec3 paletteColor = paletteEntry(i);
+ float d = distance(color, paletteColor);
+ if(d <= closestDistance) {
+ secondIndex = firstIndex;
+ secondClosestDistance = closestDistance;
+ secondClosestColor = closestColor;
+ firstIndex = i;
+ closestDistance = d;
+ closestColor = paletteColor;
+ } else if(d <= secondClosestDistance) {
+ secondIndex = i;
+ secondClosestDistance = d;
+ secondClosestColor = paletteColor;
+ }
+ }
+ /*
+ * if the two closest colors are within the threshold of each other
+ * preform a dither
+ */
+ if(distance(closestDistance, secondClosestDistance) < threshold) {
+ vec3 a = firstIndex < secondIndex ? closestColor : secondClosestColor;
+ vec3 b = firstIndex < secondIndex ? secondClosestColor : closestColor;
+ fragColor = vec4(isEven ? a : b, 1);
+ /* otherwise use the closest color */
+ } else {
+ fragColor = vec4(closestColor, 1);
+ }
+}<
\ No newline at end of file
M main.js => main.js +46 -15
@@ 3,10 3,14 @@ import Input from "@ajeeb/input"
import time from "@ajeeb/input/time"
import * as THREE from "three"
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer'
+import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass'
+
import ls16 from "./ls16"
// import * as Tone from 'tone'
import 'reset-css/reset.css'
import './style.css'
+import palettePass from "./palette-pass"
const SCHED = new coro.Schedule()
const INPUT = new Input([
@@ 34,32 38,59 @@ function resize() {
window.addEventListener('resize', resize)
resize()
-scene.add(new THREE.GridHelper(10, 10))
+// scene.add(new THREE.GridHelper(10, 10))
new OrbitControls(camera, renderer.domElement)
+const composer = new EffectComposer(renderer)
+composer.addPass(new RenderPass(scene, camera))
+
+new THREE.TextureLoader().load(ls16.palette, palette => {
+ const pass = palettePass(palette)
+ composer.addPass(pass)
+})
+
+const paletteColors = [
+ 0xff032b,
+ 0x800034,
+ 0xffff0d,
+ 0xff8f00,
+ 0x0aff0a,
+ 0x007062,
+ 0x0dffff,
+ 0x3c80db,
+ 0x2929ff,
+ 0x2d006e,
+ 0xff08ff,
+ 0x6e0085,
+ 0x260a34,
+ 0x000000,
+ 0xffffff,
+ 0x7d7da3,
+]
+
SCHED.add(function* () {
- var geometry = new THREE.BoxGeometry();
- var material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
- var cube = new THREE.Mesh(geometry, material);
- scene.add(cube);
+ const s = 0.39
+ let x = -3
+ for (const color of paletteColors) {
+ const geometry = new THREE.BoxGeometry(s, s, s);
+ const material = new THREE.MeshBasicMaterial({ color });
+ const cube = new THREE.Mesh(geometry, material);
+ cube.position.x = x
+ x += s
+ scene.add(cube);
+ }
while (true) {
- cube.rotateX(INPUT.now.time.delta)
- cube.rotateZ(INPUT.now.time.delta)
+ // cube.rotateX(INPUT.now.time.delta)
+ // cube.rotateZ(INPUT.now.time.delta)
yield
}
})
-
function tick() {
requestAnimationFrame(tick)
INPUT.collect()
SCHED.tick()
+ composer.render()
}
-tick()
-
-function render() {
- requestAnimationFrame(render)
- renderer.render(scene, camera)
-}
-render()
+tick()<
\ No newline at end of file
A palette-pass.js => palette-pass.js +23 -0
@@ 0,0 1,23 @@
+import * as THREE from "three"
+import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass'
+
+import vertexShader from './vert.glsl?raw'
+import fragmentShader from './frag.glsl?raw'
+
+export default function (texture) {
+ const paletteSize = texture.source.data.width
+ texture.minFilter = THREE.NearestFilter
+ texture.magFilter = THREE.NearestFilter
+ // texture.colorSpace = THREE.LinearSRGBColorSpace
+
+ return new ShaderPass(new THREE.ShaderMaterial({
+ uniforms: {
+ tDiffuse: { value: null },
+ palette: { value: texture },
+ threshold: { value: 0 },
+ },
+ glslVersion: THREE.GLSL3,
+ vertexShader,
+ fragmentShader: `#define PALETTE_SIZE ${paletteSize}\n${fragmentShader}`
+ }))
+}<
\ No newline at end of file
M style.css => style.css +4 -0
@@ 7,4 7,8 @@ body {
display: flex;
align-items: center;
justify-content: center;
+}
+
+canvas {
+ transition: 0.25s;
}=
\ No newline at end of file