feat(of): aces tonemap + 3 demoscene shaders
Vague 1 :
- postfx.frag : Reinhard -> ACES filmic (Narkowicz). Courbe S
filmique, contraste hautes lumieres.
- 3 nouveaux shaders fullscreen frag-only :
* metaballs : 7 balls inverse-square potential, threshold
module par kick, halo extra
* voronoi : F1/F2 cells animees + bordure F2-F1 phosphor
* twister : 4 colonnes Amiga-style rotation Y, faceting,
palette IQ + scanlines
- ShaderVis.h : wrapper Visualizer mince qui charge n'importe
quel shader/uniforms standards (uTime/uRes/uBass/uMid/
uTreble/uKick/uBpm).
- DemoScene::background = BgKind enum (Tunnel/Starfield/
Metaballs/Voronoi/Twister), choisi par scene narrative.
- Demo 1 AMIGA -> Twister bg, demo 6 GLITCH -> Voronoi bg.
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
#version 150
|
||||
|
||||
// Metaballs : champ scalaire = somme de potentiels rᵢ²/distᵢ², seuil
|
||||
// iso-surface, anneaux additionnels pour le contour. Audio-réactif :
|
||||
// position des balls modulée par bands, threshold par kick.
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 uRes;
|
||||
uniform float uTime;
|
||||
uniform float uBass;
|
||||
uniform float uMid;
|
||||
uniform float uTreble;
|
||||
uniform float uKick;
|
||||
|
||||
#define NB 7
|
||||
|
||||
void main() {
|
||||
vec2 p = (gl_FragCoord.xy / uRes) * 2.0 - 1.0;
|
||||
p.x *= uRes.x / uRes.y;
|
||||
|
||||
float field = 0.0;
|
||||
for (int i = 0; i < NB; i++) {
|
||||
float fi = float(i);
|
||||
// Position des balls : orbites lentes + modulation audio
|
||||
vec2 c = vec2(
|
||||
cos(uTime * (0.3 + fi * 0.07) + fi * 1.1) * (0.6 + uMid * 0.3),
|
||||
sin(uTime * (0.4 + fi * 0.05) + fi * 1.7) * (0.4 + uBass * 0.3)
|
||||
);
|
||||
float r = 0.18 + 0.05 * sin(uTime * 0.7 + fi);
|
||||
float d = length(p - c);
|
||||
// Inverse-square potential (smooth falloff)
|
||||
field += (r * r) / max(d * d, 0.001);
|
||||
}
|
||||
|
||||
// Seuil iso-surface modulé par kick (le blob "respire" sur kick)
|
||||
float thresh = 4.5 - uKick * 1.5;
|
||||
float surf = smoothstep(thresh - 0.5, thresh + 0.5, field);
|
||||
|
||||
// Couleur dégradée par champ — intérieur chaud, contour cyan
|
||||
vec3 inside = vec3(1.0, 0.4 + uTreble * 0.4, 0.2);
|
||||
vec3 contour = vec3(0.2, 0.8, 1.0);
|
||||
vec3 col = mix(contour, inside, surf);
|
||||
|
||||
// Halo extérieur (anneau plus large pour l'épaisseur visuelle)
|
||||
float halo = smoothstep(thresh - 1.5, thresh - 0.4, field) *
|
||||
(1.0 - smoothstep(thresh - 0.4, thresh, field));
|
||||
col += vec3(0.3, 0.9, 1.0) * halo * 0.6;
|
||||
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 150
|
||||
|
||||
uniform mat4 modelViewProjectionMatrix;
|
||||
in vec4 position;
|
||||
void main() {
|
||||
gl_Position = modelViewProjectionMatrix * position;
|
||||
}
|
||||
@@ -156,11 +156,14 @@ void main() {
|
||||
col *= mix(1.0, v, uVignette);
|
||||
}
|
||||
|
||||
// Final tone-map / clamp — without this, bloom + grain + feedback
|
||||
// saturate to pure white very quickly on bright visualizers.
|
||||
// Reinhard avec exposition : col = (col*e) / (1 + col*e), e=0.6 → la
|
||||
// valeur d'entrée 1.0 mappe à ~0.375, 5.0 à ~0.75, jamais à 1.0.
|
||||
col = (col * 0.6) / (1.0 + col * 0.6);
|
||||
col = clamp(col, 0.0, 1.0);
|
||||
// Tone-map ACES filmic (Narkowicz approximation) — courbe S filmique
|
||||
// bien meilleure que Reinhard pour le contraste et la saturation des
|
||||
// hautes lumières. Empêche le clip + garde du peps dans les blacks.
|
||||
// y = (x*(a*x+b)) / (x*(c*x+d)+e)
|
||||
{
|
||||
const float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
|
||||
col = clamp((col * (a * col + b)) / (col * (c * col + d) + e),
|
||||
0.0, 1.0);
|
||||
}
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#version 150
|
||||
|
||||
// Twister bars : 4 colonnes verticales tournant sur axe Y, gradient
|
||||
// palette par hauteur. Mythe Amiga (Spaceballs etc.). Audio-réactif :
|
||||
// vitesse de rotation suit BPM, couleur cycle modulé par mid.
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 uRes;
|
||||
uniform float uTime;
|
||||
uniform float uBpm;
|
||||
uniform float uMid;
|
||||
uniform float uKick;
|
||||
|
||||
#define NCOL 4
|
||||
|
||||
vec3 palette(float t) {
|
||||
return 0.5 + 0.5 * cos(6.28318 *
|
||||
(vec3(1.0, 0.6, 0.4) * t + vec3(0.0, 0.33, 0.67)));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 p = (gl_FragCoord.xy / uRes) * 2.0 - 1.0;
|
||||
p.x *= uRes.x / uRes.y;
|
||||
|
||||
// Repère colonne : on divise l'écran horizontalement en NCOL bandes.
|
||||
float cw = 0.55; // largeur chaque bar
|
||||
float speed = 0.4 + uBpm * 0.005;
|
||||
vec3 col = vec3(0.02, 0.04, 0.06); // fond bleu nuit
|
||||
|
||||
for (int i = 0; i < NCOL; i++) {
|
||||
float fi = float(i);
|
||||
// Centre X de la barre, espacement régulier
|
||||
float cx = -1.5 + 1.0 * fi;
|
||||
// Phase rotation propre + offset par barre
|
||||
float phase = uTime * speed * (1.0 + fi * 0.08) + fi * 0.7;
|
||||
|
||||
// Coord locale dans la barre
|
||||
float lx = (p.x - cx);
|
||||
float ly = p.y;
|
||||
|
||||
// "Rotation" sur axe Y : la largeur visible varie en sin(phase)
|
||||
// → projection 3D simulée. lx normalisé sur cw.
|
||||
float xn = lx / cw;
|
||||
if (abs(xn) <= 1.0) {
|
||||
// l'angle apparent du faceting du twister
|
||||
float ang = atan(xn, sin(phase + ly * 1.6));
|
||||
// Couleur gradient vertical + cycle hue par phase
|
||||
float t = ly * 0.5 + 0.5;
|
||||
vec3 base = palette(t * 0.7 + phase * 0.05 + uMid * 0.3);
|
||||
// Faceting = ombre additionnelle selon angle (faux 3D)
|
||||
float facet = 0.5 + 0.5 * cos(ang * 4.0 + phase * 2.0);
|
||||
col = mix(col, base * (0.4 + facet * 1.0), 0.95);
|
||||
// Bord clair sur les arêtes
|
||||
float edge = smoothstep(0.85, 1.0, abs(xn));
|
||||
col += vec3(1.0, 0.9, 0.7) * edge * 0.6 * (0.8 + uKick * 1.5);
|
||||
}
|
||||
}
|
||||
|
||||
// Scanlines CRT
|
||||
col *= 0.85 + 0.15 * sin(gl_FragCoord.y * 1.5);
|
||||
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 150
|
||||
|
||||
uniform mat4 modelViewProjectionMatrix;
|
||||
in vec4 position;
|
||||
void main() {
|
||||
gl_Position = modelViewProjectionMatrix * position;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#version 150
|
||||
|
||||
// Voronoi / Worley cells (Steven Worley 1996 + Inigo Quilez approach).
|
||||
// Pour chaque pixel : distance F1 au site le plus proche, F2 au 2e.
|
||||
// Couleur basée sur F1 (bordure cellulaire = F2-F1), animation temporelle.
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform vec2 uRes;
|
||||
uniform float uTime;
|
||||
uniform float uBass;
|
||||
uniform float uMid;
|
||||
uniform float uTreble;
|
||||
uniform float uKick;
|
||||
|
||||
vec2 hash2(vec2 p) {
|
||||
p = vec2(dot(p, vec2(127.1, 311.7)),
|
||||
dot(p, vec2(269.5, 183.3)));
|
||||
return fract(sin(p) * 43758.5453);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 uv = gl_FragCoord.xy / uRes.xx * 6.0; // ~6 cells horizontalement
|
||||
uv.y *= uRes.x / uRes.y;
|
||||
|
||||
vec2 ip = floor(uv);
|
||||
vec2 fp = fract(uv);
|
||||
|
||||
float F1 = 9.9, F2 = 9.9;
|
||||
vec2 closest = vec2(0.0);
|
||||
|
||||
// Recherche 3x3 cells autour du fragment
|
||||
for (int j = -1; j <= 1; j++) {
|
||||
for (int i = -1; i <= 1; i++) {
|
||||
vec2 g = vec2(float(i), float(j));
|
||||
vec2 o = hash2(ip + g);
|
||||
// Site animé sur orbite lente + bass shake
|
||||
o = 0.5 + 0.5 * sin(uTime * (0.5 + uBass * 0.6) + 6.2831 * o);
|
||||
vec2 r = g + o - fp;
|
||||
float d = dot(r, r);
|
||||
if (d < F1) { F2 = F1; F1 = d; closest = ip + g; }
|
||||
else if (d < F2) { F2 = d; }
|
||||
}
|
||||
}
|
||||
|
||||
F1 = sqrt(F1);
|
||||
F2 = sqrt(F2);
|
||||
|
||||
// Bordure cellulaire (F2-F1 = distance à la frontière)
|
||||
float edge = smoothstep(0.0, 0.10, F2 - F1);
|
||||
|
||||
// Couleur par cell : hash de la position pour stabilité
|
||||
vec3 cellColor = 0.5 + 0.5 * sin(
|
||||
vec3(0.0, 2.1, 4.2) + hash2(closest).x * 6.28 + uTime * 0.3
|
||||
);
|
||||
cellColor *= 0.4 + 0.8 * smoothstep(0.7, 0.0, F1); // assombrit centre
|
||||
|
||||
// Mix bordure phosphor cyan
|
||||
vec3 borderColor = vec3(0.4, 0.9, 1.0) * (1.0 - edge) * (1.0 + uTreble * 1.5);
|
||||
|
||||
vec3 col = mix(borderColor, cellColor, edge);
|
||||
|
||||
// Pulse sur kick : flash global
|
||||
col += vec3(uKick * 0.3);
|
||||
|
||||
// Vignette
|
||||
vec2 v = (gl_FragCoord.xy / uRes) - 0.5;
|
||||
col *= smoothstep(1.2, 0.3, length(v) * 1.5);
|
||||
|
||||
fragColor = vec4(col, 1.0);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#version 150
|
||||
|
||||
uniform mat4 modelViewProjectionMatrix;
|
||||
in vec4 position;
|
||||
void main() {
|
||||
gl_Position = modelViewProjectionMatrix * position;
|
||||
}
|
||||
+37
-14
@@ -80,6 +80,9 @@ void ofApp::setup() {
|
||||
kaleido_ = std::make_unique<oscope::KaleidoVis>();
|
||||
tunnel_ = std::make_unique<oscope::TunnelVis>();
|
||||
mesh_ = std::make_unique<oscope::MeshVis>();
|
||||
metaballs_ = std::make_unique<oscope::ShaderVis>("shaders/metaballs");
|
||||
voronoi_ = std::make_unique<oscope::ShaderVis>("shaders/voronoi");
|
||||
twister_ = std::make_unique<oscope::ShaderVis>("shaders/twister");
|
||||
lissajous_->setup(W, H);
|
||||
spectro_->setup(W, H / 4);
|
||||
reactive_->setup(W, H);
|
||||
@@ -90,6 +93,9 @@ void ofApp::setup() {
|
||||
kaleido_->setup(W, H);
|
||||
tunnel_->setup(W, H);
|
||||
mesh_->setup(W, H);
|
||||
metaballs_->setup(W, H);
|
||||
voronoi_->setup(W, H);
|
||||
twister_->setup(W, H);
|
||||
|
||||
postfx_.setup(W, H);
|
||||
|
||||
@@ -238,6 +244,9 @@ void ofApp::update() {
|
||||
kaleido_->update(frame);
|
||||
tunnel_->update(frame);
|
||||
mesh_->update(frame);
|
||||
metaballs_->update(frame);
|
||||
voronoi_->update(frame);
|
||||
twister_->update(frame);
|
||||
|
||||
applyOscFx();
|
||||
|
||||
@@ -354,12 +363,14 @@ void ofApp::initDemos() {
|
||||
demos_[0] = {"AMIGA TRIBUTE", {
|
||||
{"INSERT DISK", 18.0f, stars(), SS::Neon,
|
||||
" *** AMIGA 500 RELOADED *** KICKSTART 1.3 "
|
||||
" INSERT WORKBENCH DISK ", "M"},
|
||||
" INSERT WORKBENCH DISK ", "M", BgKind::Starfield},
|
||||
{"SCROLLZ", 35.0f, all(), SS::Classic,
|
||||
" GREETINGS FROM 1989 COPPER BARS AND BOBS FOREVER "
|
||||
" AMIGA NEVER DIES HELLO TO ALL DEMOSCENE VETERANS ", "M"},
|
||||
" AMIGA NEVER DIES HELLO TO ALL DEMOSCENE VETERANS ", "M",
|
||||
BgKind::Twister},
|
||||
{"COPPER STORM", 30.0f, all(), SS::Rainbow,
|
||||
" COPPER LIST IS POETRY EVERY SCANLINE A NEW COLOR ", "G"},
|
||||
" COPPER LIST IS POETRY EVERY SCANLINE A NEW COLOR ", "G",
|
||||
BgKind::Twister},
|
||||
}};
|
||||
|
||||
// ─── 2 · C64 LOWLIFE ─────────────────────────────────────
|
||||
@@ -413,12 +424,12 @@ void ofApp::initDemos() {
|
||||
// ─── 6 · GLITCH WORLD ────────────────────────────────────
|
||||
demos_[5] = {"GLITCH WORLD", {
|
||||
{"PROBE", 15.0f, stars(), SS::Neon,
|
||||
" SCANNING ANOMALY REALITY UNSTABLE ", "V"},
|
||||
" SCANNING ANOMALY REALITY UNSTABLE ", "V", BgKind::Voronoi},
|
||||
{"CORRUPT", 40.0f, all(), SS::Glitch,
|
||||
" BUFFER OVERFLOW SIGNAL CORRUPTED "
|
||||
" THE GHOSTS IN THE WIRES ARE WAKING UP ", "V"},
|
||||
" THE GHOSTS IN THE WIRES ARE WAKING UP ", "V", BgKind::Voronoi},
|
||||
{"CRASH", 30.0f, all(), SS::Glitch,
|
||||
" KERNEL PANIC BUT THE BEAT GOES ON ", "Q"},
|
||||
" KERNEL PANIC BUT THE BEAT GOES ON ", "Q", BgKind::Voronoi},
|
||||
}};
|
||||
|
||||
// ─── 7 · AMBIENT VOID ────────────────────────────────────
|
||||
@@ -660,14 +671,26 @@ void ofApp::drawScope4(int W, int H) {
|
||||
demo_.bobsEnabled() = scope4_.bobs;
|
||||
demo_.starfieldEnabled() = scope4_.starfield;
|
||||
|
||||
// 1) Fond : starfield remplace le tunnel si toggle actif, sinon tunnel.
|
||||
if (scope4_.starfield) {
|
||||
ofBackground(0);
|
||||
demo_.drawStarfield(W, H);
|
||||
} else if (scope4_.tunnel) {
|
||||
tunnel_->draw(0, 0, W, H);
|
||||
} else {
|
||||
ofBackground(0);
|
||||
// 1) Fond : selon scène narrative active (BgKind), sinon tunnel/starfield
|
||||
// classique en mode live.
|
||||
BgKind bg = BgKind::Tunnel;
|
||||
if (narrativeMode_ && currentDemo_ < (int)demos_.size() &&
|
||||
narrativeIdx_ < (int)demos_[currentDemo_].scenes.size()) {
|
||||
bg = demos_[currentDemo_].scenes[narrativeIdx_].background;
|
||||
} else if (scope4_.starfield) {
|
||||
bg = BgKind::Starfield;
|
||||
} else if (!scope4_.tunnel) {
|
||||
bg = BgKind::Tunnel; // pas de fond -> tunnel skip below
|
||||
}
|
||||
switch (bg) {
|
||||
case BgKind::Tunnel:
|
||||
if (scope4_.tunnel) tunnel_->draw(0, 0, W, H);
|
||||
else ofBackground(0);
|
||||
break;
|
||||
case BgKind::Starfield: ofBackground(0); demo_.drawStarfield(W, H); break;
|
||||
case BgKind::Metaballs: metaballs_->draw(0, 0, W, H); break;
|
||||
case BgKind::Voronoi: voronoi_->draw(0, 0, W, H); break;
|
||||
case BgKind::Twister: twister_->draw(0, 0, W, H); break;
|
||||
}
|
||||
|
||||
// 1bis) HUD pseudo-aléatoire de valeurs sub-10 Hz.
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "visualizers/KaleidoVis.h"
|
||||
#include "visualizers/TunnelVis.h"
|
||||
#include "visualizers/MeshVis.h"
|
||||
#include "visualizers/ShaderVis.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -89,6 +90,7 @@ private:
|
||||
// 10 démoparties narratives multi-actes (touches 1..9 + 0).
|
||||
// Chaque démo = liste de scènes qui défilent dans le temps avec
|
||||
// narration scénarisée + track SC + transitions.
|
||||
enum class BgKind { Tunnel, Starfield, Metaballs, Voronoi, Twister };
|
||||
struct DemoScene {
|
||||
const char* name;
|
||||
float durSec;
|
||||
@@ -96,6 +98,7 @@ private:
|
||||
oscope::ScrollerStyle scroller;
|
||||
const char* narration;
|
||||
const char* albumLetter;
|
||||
BgKind background = BgKind::Tunnel;
|
||||
};
|
||||
struct Demo {
|
||||
const char* name;
|
||||
@@ -123,6 +126,10 @@ private:
|
||||
std::unique_ptr<oscope::KaleidoVis> kaleido_;
|
||||
std::unique_ptr<oscope::TunnelVis> tunnel_;
|
||||
std::unique_ptr<oscope::MeshVis> mesh_;
|
||||
// Demoscene fullscreen FX (shader-based, frag-only).
|
||||
std::unique_ptr<oscope::ShaderVis> metaballs_;
|
||||
std::unique_ptr<oscope::ShaderVis> voronoi_;
|
||||
std::unique_ptr<oscope::ShaderVis> twister_;
|
||||
|
||||
std::vector<float> ch1_, ch2_;
|
||||
Mode mode_ = Mode::Scope4;
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
// ShaderVis : visualizer fullscreen-quad piloté par un fragment shader.
|
||||
// Charge n'importe quel shader (path = "shaders/<nom>") et passe les
|
||||
// uniforms standards uTime / uRes + bandes audio (bass/mid/treble/kick).
|
||||
// Sert de wrapper léger pour les FX type demoscene (metaballs, voronoi,
|
||||
// twister, plasma fbm, rotozoomer, etc.) sans code redondant.
|
||||
|
||||
#include "Visualizer.h"
|
||||
#include "ofMain.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace oscope {
|
||||
|
||||
class ShaderVis : public Visualizer {
|
||||
public:
|
||||
explicit ShaderVis(const std::string& shaderPath)
|
||||
: shaderPath_(shaderPath) {}
|
||||
|
||||
void setup(int w, int h) override { w_ = w; h_ = h; reloadShaders(); }
|
||||
void update(const VisFrame& frame) override {
|
||||
bass_ = frame.bands.bass;
|
||||
mid_ = frame.bands.mid;
|
||||
treble_ = frame.bands.treble;
|
||||
kick_ = frame.bands.kick;
|
||||
bpm_ = frame.osc.bpm();
|
||||
}
|
||||
void draw(int x, int y, int w, int h) override {
|
||||
if (!shader_.isLoaded()) {
|
||||
ofPushStyle();
|
||||
ofSetColor(60);
|
||||
ofDrawRectangle(x, y, w, h);
|
||||
ofSetColor(255, 80, 80);
|
||||
ofDrawBitmapString(shaderPath_ + " missing", x + 12, y + 24);
|
||||
ofPopStyle();
|
||||
return;
|
||||
}
|
||||
shader_.begin();
|
||||
shader_.setUniform2f("uRes", static_cast<float>(w), static_cast<float>(h));
|
||||
shader_.setUniform1f("uTime", ofGetElapsedTimef());
|
||||
shader_.setUniform1f("uBass", bass_);
|
||||
shader_.setUniform1f("uMid", mid_);
|
||||
shader_.setUniform1f("uTreble",treble_);
|
||||
shader_.setUniform1f("uKick", kick_);
|
||||
shader_.setUniform1f("uBpm", bpm_);
|
||||
ofDrawRectangle(x, y, w, h);
|
||||
shader_.end();
|
||||
}
|
||||
void reloadShaders() override { shader_.load(shaderPath_); }
|
||||
|
||||
private:
|
||||
std::string shaderPath_;
|
||||
int w_ = 0, h_ = 0;
|
||||
float bass_ = 0, mid_ = 0, treble_ = 0, kick_ = 0, bpm_ = 120;
|
||||
ofShader shader_;
|
||||
};
|
||||
|
||||
} // namespace oscope
|
||||
Reference in New Issue
Block a user