~nasser/lospec-2

792b6b76b3fcb0ec0ed53f0a1ca7e685bc23df30 — Ramsey Nasser 7 months ago ab3de1c
Progress on palette shader, colors not right
4 files changed, 133 insertions(+), 15 deletions(-)

M frag.glsl
M main.js
A palette-pass.js
M style.css
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