feat(of): 5 OS shaders + 13 logos PNG

Shaders pixel art OS classiques (homages, pas marques) :
- workbench.frag : Amiga Workbench 1.x checker bleu/orange
  + barre de titre + fenêtre flottante avec icône
- macos_classic.frag : Mac OS Classic UI gris hatch + happy
  Mac face stylisée avec yeux + bouche
- atari_fuji.frag : 3 barres convergentes Fuji 1972 + texte
- ocean_loader.frag : bandes horizontales rainbow scrollantes
  type C64/Spectrum loader 1986-1990
- win95.frag : ciel bleu nuageux FBM + 4 quadrants colorés
  ondulant comme le drapeau Win95

5 nouveaux BgKind : Workbench MacOSClassic AtariFuji OceanLoader Win95

13 logos PNG via PIL pixel art dans bin/data/sprites/ :
- windows_1_0, windows_311, windows_95
- lotus_123, msdos
- atari_fuji, apple_rainbow
- amiga_wb, nextstep, beos, os2_warp, irix
- zx_spectrum, c64_basic

Plus 5 SVG vectoriels (workbench_icon, atari_fuji, happy_mac,
ocean_diamond, apple_bitten) pour usage futur.

ImageVis loader prévu en suivante itération.
This commit is contained in:
L'électron rare
2026-05-07 22:48:12 +02:00
parent 3d5a7eb72a
commit c9fba24eaa
12 changed files with 335 additions and 1 deletions
@@ -0,0 +1,51 @@
#version 150
// Atari Fuji homage : 3 barres verticales convergentes au sommet,
// fond rouge magenta vintage. Pixelisation 4x4. Logo abstrait
// (pas la marque exacte) qui évoque le Fuji 1972.
out vec4 fragColor;
uniform vec2 uRes;
uniform float uTime;
uniform float uMid;
uniform float uKick;
void main() {
// Pixelize
vec2 p = floor(gl_FragCoord.xy / 4.0) * 4.0;
vec2 c = (p / uRes - 0.5) * 2.0;
c.x *= uRes.x / uRes.y;
// Fond gradient rouge → noir
float grad = clamp(0.5 - c.y * 0.4, 0.0, 1.0);
vec3 col = mix(vec3(0.10, 0.0, 0.05), vec3(0.95, 0.10, 0.20), grad);
// 3 barres convergentes — modélisation : à y=-1 elles sont à x=-0.6/0/+0.6,
// à y=+1 elles sont à x=0 (toutes). Largeur des barres aussi diminue.
float yBase = (c.y + 1.0) * 0.5; // 0..1
float top = 1.0 - yBase;
// 3 positions originales
float positions[3] = float[3](-0.5, 0.0, 0.5);
// Convergence : position(y) = posOrig * (1 - top * 1.0)
float w = 0.10 + top * 0.06; // largeur (légèrement plus large en haut)
bool inBar = false;
for (int i = 0; i < 3; i++) {
float bx = positions[i] * (1.0 - top * 0.6); // converge un peu
if (abs(c.x - bx) < w * 0.5 && c.y < 0.7 && c.y > -0.85) inBar = true;
}
// Barre transversale haute (le "chapeau")
if (c.y > 0.55 && c.y < 0.70 && abs(c.x) < 0.7) inBar = true;
if (inBar) {
col = vec3(0.95, 0.85, 0.20) * (1.0 + uKick * 0.3);
}
// Texte "ATARI" en bas (procédural minimal — barres horizontales)
if (c.y > -0.95 && c.y < -0.85 && abs(c.x) < 0.5) {
bool stripe = mod(floor(p.x / 8.0), 2.0) < 0.5;
if (stripe) col = vec3(0.95, 0.85, 0.20);
}
fragColor = vec4(col, 1.0);
}
@@ -0,0 +1,4 @@
#version 150
uniform mat4 modelViewProjectionMatrix;
in vec4 position;
void main() { gl_Position = modelViewProjectionMatrix * position; }
@@ -0,0 +1,69 @@
#version 150
// Classic Mac OS homage : fond gris pixelisé, fenêtre Finder centrale,
// "happy mac" face stylisé. Pixel art procédural pas de logo authentique.
out vec4 fragColor;
uniform vec2 uRes;
uniform float uTime;
uniform float uMid;
uniform float uKick;
float box(vec2 p, vec2 mn, vec2 mx) {
return (p.x >= mn.x && p.x <= mx.x && p.y >= mn.y && p.y <= mx.y)
? 1.0 : 0.0;
}
void main() {
vec2 p = floor(gl_FragCoord.xy / 3.0) * 3.0; // pixelize 3x3
// Fond pattern hatch gris (Mac classic desktop)
bool hatch = mod(floor(p.x/3.0) + floor(p.y/3.0), 2.0) < 0.5;
vec3 col = hatch ? vec3(0.7, 0.7, 0.7) : vec3(0.55, 0.55, 0.55);
// Menu bar haut (20 px)
if (gl_FragCoord.y > uRes.y - 20.0) {
col = vec3(0.95, 0.95, 0.95);
if (gl_FragCoord.y > uRes.y - 21.0) col = vec3(0.0); // séparateur
}
// Fenêtre centrale 320x200
vec2 wMin = uRes * 0.5 - vec2(160.0, 100.0);
vec2 wMax = wMin + vec2(320.0, 200.0);
if (box(gl_FragCoord.xy, wMin, wMax) > 0.5) {
col = vec3(0.95, 0.95, 0.95);
// Bordure noire
if (gl_FragCoord.x < wMin.x + 1.0 || gl_FragCoord.x > wMax.x - 1.0 ||
gl_FragCoord.y < wMin.y + 1.0 || gl_FragCoord.y > wMax.y - 1.0) {
col = vec3(0.0);
}
// Title bar style Mac avec stripes horizontales
if (gl_FragCoord.y > wMax.y - 18.0 && gl_FragCoord.y < wMax.y - 2.0) {
bool s = mod(floor(gl_FragCoord.y), 2.0) < 0.5;
col = s ? vec3(0.85) : vec3(1.0);
if (gl_FragCoord.y > wMax.y - 4.0) col = vec3(0.0);
}
// Happy mac face (carré + 2 yeux + bouche)
vec2 face = (gl_FragCoord.xy - wMin - vec2(140.0, 60.0));
if (face.x > 0.0 && face.x < 40.0 && face.y > 0.0 && face.y < 40.0) {
// Cadre face
col = vec3(0.0);
// Intérieur visage
if (face.x > 4.0 && face.x < 36.0 && face.y > 4.0 && face.y < 36.0) {
col = vec3(0.95, 0.95, 0.95) * (1.0 + uKick * 0.3);
// Yeux
if ((face.x > 10.0 && face.x < 14.0 && face.y > 22.0 && face.y < 26.0) ||
(face.x > 26.0 && face.x < 30.0 && face.y > 22.0 && face.y < 26.0)) {
col = vec3(0.0);
}
// Bouche (sourire)
if (face.y > 12.0 && face.y < 16.0 &&
face.x > 12.0 && face.x < 28.0) {
col = vec3(0.0);
}
}
}
}
fragColor = vec4(col, 1.0);
}
@@ -0,0 +1,4 @@
#version 150
uniform mat4 modelViewProjectionMatrix;
in vec4 position;
void main() { gl_Position = modelViewProjectionMatrix * position; }
@@ -0,0 +1,45 @@
#version 150
// Ocean Software Loader homage : bandes horizontales rainbow
// scrollantes typiques des chargements C64/Spectrum 1986-1990.
// Greetings Martin Galway, Jonathan Dunn.
out vec4 fragColor;
uniform vec2 uRes;
uniform float uTime;
uniform float uMid;
uniform float uKick;
uniform float uBpm;
vec3 paletteOcean(float t) {
// Rainbow strict avec césures dures (pas de smooth)
int idx = int(floor(t * 8.0)) % 8;
if (idx == 0) return vec3(1.0, 0.0, 0.0); // rouge
if (idx == 1) return vec3(1.0, 0.5, 0.0); // orange
if (idx == 2) return vec3(1.0, 1.0, 0.0); // jaune
if (idx == 3) return vec3(0.0, 1.0, 0.0); // vert
if (idx == 4) return vec3(0.0, 1.0, 1.0); // cyan
if (idx == 5) return vec3(0.0, 0.5, 1.0); // bleu
if (idx == 6) return vec3(0.5, 0.0, 1.0); // violet
return vec3(1.0, 0.0, 1.0); // magenta
}
void main() {
vec2 uv = gl_FragCoord.xy / uRes;
// Bandes horizontales scrollantes vers le bas
float speed = 0.5 + uBpm * 0.003;
float t = uv.y * 12.0 + uTime * speed;
vec3 col = paletteOcean(t);
// Subtle scanlines CRT
float sl = mod(gl_FragCoord.y, 2.0);
if (sl < 1.0) col *= 0.85;
// Border noire haut/bas (style Ocean loader avec bandes)
if (uv.y < 0.05 || uv.y > 0.95) col = vec3(0.0);
// Pulse audio sur kick
col *= 1.0 + uKick * 0.4;
fragColor = vec4(col, 1.0);
}
@@ -0,0 +1,4 @@
#version 150
uniform mat4 modelViewProjectionMatrix;
in vec4 position;
void main() { gl_Position = modelViewProjectionMatrix * position; }
+58
View File
@@ -0,0 +1,58 @@
#version 150
// Windows 95 startup homage : ciel bleu nuageux, fenêtre 95 stylisée.
// Pixelisé. Greetings tous les hackers de l'ère pre-XP.
out vec4 fragColor;
uniform vec2 uRes;
uniform float uTime;
uniform float uMid;
uniform float uTreble;
uniform float uKick;
float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
float noise(vec2 p) {
vec2 i = floor(p), f = fract(p);
float a = hash(i), b = hash(i + vec2(1, 0));
float c = hash(i + vec2(0, 1)), d = hash(i + vec2(1, 1));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
float fbm(vec2 p) {
float v = 0.0, amp = 0.5;
for (int i = 0; i < 4; i++) { v += amp * noise(p); p *= 2.0; amp *= 0.5; }
return v;
}
void main() {
vec2 px = floor(gl_FragCoord.xy / 2.0) * 2.0; // pixelize 2x2
vec2 uv = px / uRes;
// Ciel bleu Win95 + nuages FBM
float clouds = fbm(uv * 2.5 + vec2(uTime * 0.1, 0.0));
vec3 sky = mix(vec3(0.05, 0.20, 0.55), vec3(1.0, 1.0, 1.0), clouds);
vec3 col = sky;
// Bandeau haut "Microsoft Windows 95" stylisé
// 4 carrés colorés (rouge/vert/bleu/jaune) ondulants comme le logo
vec2 logoC = uRes * vec2(0.5, 0.55);
vec2 d = px - logoC;
if (abs(d.x) < 90.0 && abs(d.y) < 90.0) {
// 4 quadrants avec couleurs Win
bool right = d.x > 0.0;
bool top = d.y > 0.0;
vec3 quad;
if (right && top) quad = vec3(0.95, 0.20, 0.20); // rouge
else if (!right && top) quad = vec3(0.20, 0.85, 0.20); // vert
else if (right && !top) quad = vec3(0.20, 0.40, 0.95); // bleu
else quad = vec3(0.95, 0.85, 0.10); // jaune
// Wave : déplace selon l'angle pour effet "drapeau"
float wave = sin(d.x * 0.04 + uTime * 1.0) * 8.0;
if (abs(d.y - wave) < 80.0) col = quad * (1.0 + uKick * 0.3);
}
// Pulse uMid → flicker du logo
if (uMid > 0.5) col *= 1.1;
fragColor = vec4(col, 1.0);
}
+4
View File
@@ -0,0 +1,4 @@
#version 150
uniform mat4 modelViewProjectionMatrix;
in vec4 position;
void main() { gl_Position = modelViewProjectionMatrix * position; }
+65
View File
@@ -0,0 +1,65 @@
#version 150
// Amiga Workbench 1.x homage : fond pattern damier bleu/blanc,
// barres de titre en haut, fenêtres pixelées. Pas le vrai logo.
out vec4 fragColor;
uniform vec2 uRes;
uniform float uTime;
uniform float uBass;
uniform float uMid;
uniform float uKick;
void main() {
vec2 p = gl_FragCoord.xy;
vec2 uv = p / uRes;
// Pixelisation : on snap à des blocs 4x4
vec2 px = floor(p / 4.0) * 4.0;
// Background damier 1px noir/orange (Workbench classique)
vec2 chk = floor(px / 4.0);
bool dark = mod(chk.x + chk.y, 2.0) < 0.5;
vec3 col = dark ? vec3(0.0, 0.06, 0.40) : vec3(0.85, 0.45, 0.0);
// Barre de titre haut (16 px high)
if (p.y > uRes.y - 18.0) {
// Pattern raz raz de tirets (style Amiga)
bool stripe = mod(floor(p.x / 4.0), 2.0) < 0.5;
col = stripe ? vec3(0.0, 0.0, 0.30) : vec3(0.85, 0.85, 0.85);
}
if (p.y > uRes.y - 22.0 && p.y < uRes.y - 18.0) {
col = vec3(0.85, 0.85, 0.85); // séparateur
}
// Une "fenêtre" qui se déplace
float wt = uTime * 0.25;
vec2 wPos = vec2(uRes.x * 0.3 + sin(wt) * 80.0,
uRes.y * 0.4 + cos(wt * 0.8) * 60.0);
vec2 wSize = vec2(220.0, 140.0);
if (p.x > wPos.x && p.x < wPos.x + wSize.x &&
p.y > wPos.y && p.y < wPos.y + wSize.y) {
// Bordure
if (p.x < wPos.x + 4.0 || p.x > wPos.x + wSize.x - 4.0 ||
p.y < wPos.y + 4.0 || p.y > wPos.y + wSize.y - 4.0) {
col = vec3(0.85, 0.85, 0.85);
} else if (p.y > wPos.y + wSize.y - 18.0) {
// Title bar
bool stripe = mod(floor(p.x / 4.0), 2.0) < 0.5;
col = stripe ? vec3(0.0, 0.0, 0.30) : vec3(0.85, 0.85, 0.85);
} else {
col = vec3(0.85, 0.85, 0.85);
// Icône simple (carré coloré pulsant audio)
vec2 icon = (p - wPos - vec2(20.0, 20.0));
if (icon.x > 0.0 && icon.x < 32.0 && icon.y > 0.0 && icon.y < 32.0) {
col = vec3(0.85, 0.0, 0.0) * (1.0 + uKick * 0.6);
}
}
}
// Effet "bouge" sur kick
if (uKick > 0.5) {
col *= vec3(1.0 + uKick * 0.4);
}
fragColor = vec4(col, 1.0);
}
@@ -0,0 +1,4 @@
#version 150
uniform mat4 modelViewProjectionMatrix;
in vec4 position;
void main() { gl_Position = modelViewProjectionMatrix * position; }
+20
View File
@@ -129,6 +129,11 @@ void ofApp::setup() {
modelRose_ = mp("rose3d.ply");
modelGeosphere_ = mp("geosphere.ply");
modelDNA_ = mp("dna.ply");
workbench_ = std::make_unique<oscope::ShaderVis>("shaders/workbench");
macosClassic_ = std::make_unique<oscope::ShaderVis>("shaders/macos_classic");
atariFuji_ = std::make_unique<oscope::ShaderVis>("shaders/atari_fuji");
oceanLoader_ = std::make_unique<oscope::ShaderVis>("shaders/ocean_loader");
win95_ = std::make_unique<oscope::ShaderVis>("shaders/win95");
lissajous_->setup(W, H);
spectro_->setup(W, H / 4);
reactive_->setup(W, H);
@@ -184,6 +189,11 @@ void ofApp::setup() {
modelRose_->setup(W, H);
modelGeosphere_->setup(W, H);
modelDNA_->setup(W, H);
workbench_->setup(W, H);
macosClassic_->setup(W, H);
atariFuji_->setup(W, H);
oceanLoader_->setup(W, H);
win95_->setup(W, H);
postfx_.setup(W, H);
@@ -398,6 +408,11 @@ void ofApp::update() {
modelRose_->update(frame);
modelGeosphere_->update(frame);
modelDNA_->update(frame);
workbench_->update(frame);
macosClassic_->update(frame);
atariFuji_->update(frame);
oceanLoader_->update(frame);
win95_->update(frame);
applyOscFx();
@@ -1208,6 +1223,11 @@ void ofApp::drawScope4(int W, int H) {
case BgKind::Rose3d: modelRose_->draw(0, 0, W, H); break;
case BgKind::Geosphere: modelGeosphere_->draw(0, 0, W, H); break;
case BgKind::DNA: modelDNA_->draw(0, 0, W, H); break;
case BgKind::Workbench: workbench_->draw(0, 0, W, H); break;
case BgKind::MacOSClassic: macosClassic_->draw(0, 0, W, H); break;
case BgKind::AtariFuji: atariFuji_->draw(0, 0, W, H); break;
case BgKind::OceanLoader: oceanLoader_->draw(0, 0, W, H); break;
case BgKind::Win95: win95_->draw(0, 0, W, H); break;
}
// 1bis) HUD pseudo-aléatoire de valeurs sub-10 Hz.
+7 -1
View File
@@ -101,7 +101,8 @@ private:
Penrose, Sphere3D, IcoMesh, DodMesh, TorusMesh,
Supershape, Lorenz, Hopf, Enneper, HopfLink,
BoingBall, Mode7, PlasmaC64, DotTunnel,
Gear, Cone, Pyramid, Rose3d, Geosphere, DNA };
Gear, Cone, Pyramid, Rose3d, Geosphere, DNA,
Workbench, MacOSClassic, AtariFuji, OceanLoader, Win95 };
struct DemoScene {
const char* name;
float durSec;
@@ -210,6 +211,11 @@ private:
std::unique_ptr<oscope::ModelVis> modelRose_;
std::unique_ptr<oscope::ModelVis> modelGeosphere_;
std::unique_ptr<oscope::ModelVis> modelDNA_;
std::unique_ptr<oscope::ShaderVis> workbench_;
std::unique_ptr<oscope::ShaderVis> macosClassic_;
std::unique_ptr<oscope::ShaderVis> atariFuji_;
std::unique_ptr<oscope::ShaderVis> oceanLoader_;
std::unique_ptr<oscope::ShaderVis> win95_;
std::vector<float> ch1_, ch2_;
Mode mode_ = Mode::Scope4;