recur-shadertoy-ports
Shaders adaptats a r_e_c_u_r que funcionen a la Raspberry Pi.
Shadertoy corre GLSL ES 3.00; r_e_c_u_r corre GLSL ES 1.00 sobre una GPU VideoCore IV. La Pi no avisa quan alguna cosa no hi cap: un shader mal portat compila, enllaça i pinta una pantalla verda, negra, o es penja, sense cap error ni a la màquina ni a la interfície. Que es vegi bé al portàtil, amb un GLSL més modern, no garanteix res sobre l'aparell — per això la regla d'aquesta carpeta és tan estricta.
Es va omplint a mesura que un port passa la prova a la màquina de veritat.
Els ports es fan amb agent-recur, un agent d'IA especialitzat amb la seva skill, públic i reutilitzable — encara que qui tanca un port sempre és qui el veu funcionar a la Pi.
Cada shader d'aquí és obra d'algú altre, i no tots amb les mateixes condicions: deu són
CC BY-NC-SA 3.0 i un és MIT. Vol dir que has de citar l'autor original —no pas a
nosaltres— i que en deu dels onze un bolo pagat queda fora. Abans de reutilitzar-ne
cap, llegeix LICENCIAS.md: hi ha la procedència, l'autoria i la llicència
de cadascun, una per una.
La regla d'aquesta carpeta
Aquí només hi entra el que algú ha vist funcionar a la Raspberry Pi. No el que
compila, no el que es veu bé a glslViewer, no el que «hauria d'anar». Cada shader de
shaders/ té darrere una sessió a l'aparell i una confirmació explícita.
El que està en dubte, a mig fer o descartat per lent es queda al repositori de treball
(Medialab-Terata, a proyectos-en-curso/cacharreo/recur/), que és on viu també tota
l'eina: el revisor automàtic, la guia de conversió, les notes de màquina i la
instrumentació de fps. Aquest repositori és la sortida, no el taller.
Què hi ha
| Shader | Què és | Controls |
|---|---|---|
crt_margarita |
Patró geomètric sobre CRT simulat | 3 |
crt_plaid |
Ídem, trama de quadres | 3 |
crt_threesome |
Ídem, ventall de colors | 3 |
crt_bacteria |
Ídem, tramat orgànic | 3 |
crt_sunlight |
Ídem, calidoscopi | 3 |
beat_ring |
Anell de 8 polsos amb envolupant ADSR sincronitzada a un tempo | x0 velocidad, x1 radio, x2 halo |
rejilla |
Reixa de pilars vista des de dins, amb sorra | x0 zoom, x1 spine, x2 arena |
rejilla_lisa |
La mateixa sense sorra, cantell net | x0 zoom, x1 spine, x2 zoom2 |
panal |
Reixa hexagonal de dos tons polsant en anells, a 144 BPM | x0 compás, x1 onda, x2 zoom |
osciloscopio |
L'ona d'un bombo sintetitzat, traçada com en un oscil·loscopi | x0 fase, x1 escala, x2 limpieza |
osciloscopio_negativo |
El mateix, en negatiu: fons negre i traç blanc | x0 fase, x1 escala, x2 limpieza |
sol |
Un sol en remolí de pinzellades, a pantalla completa | x0 altura, x1 remolino, x2 color |
atardecer |
El mateix cel sobre un mar de pinzellades, amb camí de sol | x0 altura, x1 remolino, x2 color |
pad |
Veu de pad: ona quadrada amb FM lleu, traçada sobre un remolí de soroll | x0 tempo, x1 temblor, x2 densidad |
bateria |
Veu de bateria: bombo amb atac sec i hi-hat de soroll | x0 tempo, x1 temblor, x2 densidad |
arpegio |
Veu d'arpegi: sis notes encadenades pel seqüenciador, en diagonals | x0 tempo, x1 temblor, x2 densidad |
acorde |
Veu d'acord: tres notes per síntesi additiva, sis harmònics | x0 tempo, x1 temblor, x2 densidad |
acorde_ligero |
El mateix acord amb tres harmònics en lloc de sis, i més barat | x0 tempo, x1 temblor, x2 densidad |
cascada |
Cascada infinita de triangles en zoom, tres direccions a 120 graus, una per canal de color | x0 velocitat, x1 enquadrament, x2 contorn/ple |
voronoi |
Cel·les de voronoi animades, amb les vores taronja, isolínies a dins i un punt per cel·la | x0 escala, x1 velocitat, x2 gruix |
zumbido |
Camp de franges en blanc i negre que es retorça i titil·la com si reaccionés a un cop de greus | x0 limpieza, x1 manual, x2 velocidad |
alumbrado |
La mateixa sala del shader d'en Flyguy —reixa de punts, barres, anell amb una ranura que gira— recorreguda per les sis llums, sense ombres. Adaptació poc fidel: no s'assembla a l'original | x0 parell de mà, x1 pas de la reixa, x2 intensitat |
alumbrado_sombras |
La mateixa sala amb UNA llum i ombres de debò, marcades pas a pas. Va a 4,8 fps: base lenta, no per marcar ritme | x0 mou la llum, x1 pas de la reixa, x2 intensitat |
alumbrado_luces |
Sis llums de color movent-se sobre un fons llis: una de vermella que batega al centre, una de blava girant, dues que pugen i baixen pels costats i un parell simètric que porta el comandament | x0 parell de mà, x1 velocitat, x2 intensitat |
Tots són 0-input (generatius). Les captures de referència són a capturas/.
alumbrado es publica declarat com a adaptació poc fidel: qui el va provar a
l'aparell diu que no s'assembla a l'original, i es publica per allò que és —una peça que
va a 30 fps i es pot tocar— no com a reproducció. Qui busqui l'original, que vagi a
Shadertoy.
alumbrado_sombras és l'única de les tres amb ombres reals, i va a 4,8 fps, mesurats
a l'aparell. Són 208 ms entre fotogrames, així que qualsevol cop més curt que un cinquè de
segon no s'hi veu. Es publica dient-ho.
alumbrado_luces és un tros, i convé saber-ho: l'original de Flyguy és de dues
passades i el seu Buffer A calcula un mapa d'ombres que r_e_c_u_r no pot tenir. Aquí hi
ha les sis llums amb els colors, les brillantors i els recorreguts copiats número a número
d'aquell buffer; el que no hi és és la sala que il·luminaven ni les ombres que
projectaven. Refer-ho sencer en una sola passada surten unes 5.800 operacions per píxel
contra un sostre mesurat de 250.
panal no és una traducció: l'original fa raymarching (234 crides a map() per
píxel) i en aquesta màquina això no hi cap de cap manera. Es conserva la idea —la reixa
hexagonal, l'ona radial, el compàs— i es dibuixa en 2D, amb una avaluació per píxel. Està
explicat a la seva capçalera.
osciloscopio sí que és una traducció, però amb un avís: l'original multiplica el temps
per 5e19, que no és un càlcul sinó llençar la precisió a propòsit perquè l'ona surti com
a soroll. Això és l'efecte, i per això no es veu igual que a Shadertoy: depèn de com
arrodoneixi cada GPU. El control x2 baixa aquest multiplicador i porta del soroll a un
traç net.
Diagnòstics
diagnostico/ no són efectes: són shaders que contesten preguntes sobre l'aparell que no
es poden contestar llegint el codi.
test-orientation.frag— resolució i orientació de l'eix Y.test-precision.frag— simediumpen aquesta màquina és fp16 o fp32. Tres franges: blanques = es comporta com a fp32.
On es va provar
Raspberry Pi 3B+, amb el sistema precompilat per cyberboy666, versió 2.0.2
(imatge recur2_0_2).
| Placa | Raspberry Pi 3B+ |
| Sistema | Imatge recur2_0_2 de cyberboy666, precompilada |
| GPU | VideoCore IV, OpenGL ES 2.0 (GLSL ES 1.00) |
| Sortida | 720x576 (sdtv_mode=02, PAL entrellaçat) |
| Ritme | Topall de 30 fps, que el posa c_o_n_j_u_r per programari |
Això no és un peu de pàgina: els límits que hi ha darrere d'aquests ports són els d'aquesta combinació concreta, no els de r_e_c_u_r en abstracte. Quanta complexitat hi cap abans que la GPU no pugui més es va mesurar allà, i en una altra placa o amb una altra imatge caldria tornar a mesurar-ho.
Sobre la norma de vídeo (corregit el 2026-09-19): l'aparell de referència és PAL,
perquè tota la cadena de sortida de qui l'opera (Roland V-4, CRT) ho és. Fins aquesta
data, aquí es documentava NTSC (720x480) per error. Alguns dels fps que hi ha en aquesta
mateixa pàgina i a les capçaleres dels .frag es van mesurar abans que es detectés
l'error, sobre un quadre un 20% més petit del que corre l'aparell de debò -- encara
estan pendents de remesurar-se un per un.
Cyberboy publica també una 2.1.0 que no hem comprovat. Si fas servir la 2.1.0 i et funciona, posa't en contacte amb les companyes del Medialab Terata [www.terata.org].
Instal·lació
Copiar els .frag a la carpeta de shaders de la targeta. A la imatge 2.0.2:
rsync -av shaders/0-input/ pi@IP:/home/pi/Shaders/mis_shaders/
Compte: r_e_c_u_r busca per nom de fitxer i es queda amb la primera coincidència,
començant per /media/pi. Si hi ha un llapis USB posat amb un shader del mateix nom, es
carrega aquell i no el de la targeta.
Notes de cada port
Les condicions de cada shader són a LICENCIAS.md, una per una. El que
segueix aquí són les notes de cada port: què es va retallar, què es va reescriure i què
convé saber abans de carregar-lo.
sol i atardecer són un retall, no una traducció, i convé saber-ho. L'original de
Noztol té quatre capes —cel, mar i sorra, arbres, herba de l'illa— i costa unes 2214
operacions per píxel. Aquest aparell aguanta unes 250. Mesurades les capes per separat, la
del cel costa 62 i va a 13,7 fps; qualsevol versió amb el bucle de pinzellades de
l'horitzó es queda entre 3,8 i 7,8.
Així que sol és aquesta capa sola, i atardecer és aquesta capa més un mar escrit a
mà: en lloc de portar la capa d'aigua de l'original (725 operacions), reflecteix el punt
respecte a l'horitzó, hi avalua només la paleta i ho modula amb bandes i un camí de sol.
Unes 19 operacions. Va a 10,1 fps.
Els dos porten dos ajustos que no són a l'original i que són d'aquest aparell:
ASPECTO_PANTALLA, perquè el quadre és 720x480 i surt per compost a una tele 4:3 que
l'aixafa de costat; i TONO_BASE, perquè amb la paleta sense desplaçar el cel surt blau
de dia i això és una posta de sol.
osciloscopio_negativo és el mateix shader amb una sola línia canviada: 1.0 - l en lloc
de l. Surt blanc net i no un color complementari perquè la imatge ja era grisa —
kick() retorna les dues components iguals, així que el vec4(l.x, l.y, l.x, ...) de
l'original està preparat per a dos traços que aquí mai arriben a separar-se. Comprovat
abans de donar-lo per bo: fixant el temps, els dos renders sumen 255 en els 262.144
píxels, o sigui que és una inversió exacta. Va a 28,1 fps.
zumbido és el primer port l'original del qual depenia de l'entrada d'àudio de
Shadertoy, que r_e_c_u_r no té: conjur.cpp enllaça u_time, u_resolution,
u_x0..u_x3, u_tex0 i u_tex1, i res més. L'original no calculava un volum — llegia la
textura d'àudio amb abs(sin(t)*100.0), que gira tan de pressa que retorna soroll acotat
en [0,1] — així que aquí es reconstrueix el titileig, no l'àudio, amb un hash de coma
flotant sobre el temps. Es va descartar a propòsit treure'l de la luminància de u_tex0:
hauria fet que un shader generatiu depengués de si hi ha vídeo carregat a la cadena en
aquell moment.
Avís per a qui el carregui: de vegades salta un fotograma sencer en negre, i no és una
fallada. w és un sinus, va de -1 a 1, i el framebuffer retalla a 0 tot el negatiu;
quan el factor que no depèn del píxel cau pel costat negatiu s'apaguen tots alhora.
Verificat amb números, no suposat: mitjana de gris exactament 0 en els 262.144 píxels amb
manual=0.5 a t=5.0, i no nul·la a t=5.3 ni a t=6.0 amb el mateix control. És un
apagament de l'instant, no un tram mort del control, i encaixa amb el cop de greus que
imita. Costa 12 operacions per píxel: el port més barat del repositori.
Les quatre veus de "Music toolbox"
pad, bateria, arpegio i acorde (més acorde_ligero) surten d'un mateix shader de
nimitz que a Shadertoy divideix la pantalla en quatre quadrants i traça a cadascun la
forma d'ona d'una veu. És la pestanya d'imatge d'un shader amb so, però la part visual és
autocontinguda: dibuixa les ones a partir d'un rellotge de compassos a 112 BPM, sense
necessitar l'àudio.
Els quatre quadrants junts no hi caben aquí: la traducció literal dona 179 línies i unes 396 operacions per píxel, i aquesta màquina deixa de dibuixar per sobre de 146 línies i es traba per sobre de 360. Va una veu per fitxer, a pantalla completa, com els 124 shaders que porta la imatge. Es perd veure-les alhora; es guanya que es vegin.
El que a l'original era la textura de soroll de Shadertoy (iChannel0) aquí és soroll
procedural, perquè no depenguin de si hi ha vídeo carregat a la cadena.
Sobre acorde i acorde_ligero: el lleuger retalla els harmònics de la síntesi
additiva de sis a tres. Després d'optimitzar els dos, el lleuger costa 66 i el fidel 80,
així que la diferència ja no és de "hi cap o no hi cap" sinó de timbre: el fidel sona —es
veu— més dens. Hi són els dos perquè l'elecció és estètica.
Els cinc van passar per tres voltes d'optimització que no canvien un sol píxel (0 de
262.144, verificat amb el temps i els controls fixos): envolupants idèntiques que es
calculaven un cop per nota en lloc de tres, pesos d'arpegiador calculats quinze cops quan
en bastaven tres, i un cos que s'escrivia a les dues branques d'un if —que en aquesta
GPU es paguen totes dues, perquè els salts curts s'aplanen—. El més car va baixar de 213 a
80.
cascada, i la trampa de l'alfa
De Fabrice Neyret2. Una cascada infinita de triangles que fa zoom cap al seu propi punt de fuga, amb tres direccions a 120 graus, cada una en un canal de color.
Val la pena explicar-ne el port perquè la trampa que va costar més no és de GLSL, és
d'aquí: l'original acaba amb O = 1. - vec4(N,N,N, 1.), i el quart component surt
1.-1. = 0.0. O sigui que el shader pinta amb alfa zero. Shadertoy ignora l'alfa i
allà es veu perfecte; en aquest aparell un fragment transparent no pinta res, i la
pantalla es queda negra tot i que el shader compila i corre a 20 fps. Va costar tres
viatges amb la targeta.
Dues regles que en surten, per a qui porti alguna cosa:
- Escriu l'alfa a part i a 1.0 sempre:
gl_FragColor = vec4(c, 1.0), amb el color calculat com avec3. No el deixis sortir d'una operació. - Quan mesuris un render, mira els QUATRE canals. Les conversions a escala de grisos o a RGB descarten l'alfa, així que un shader invisible dona mesures de brillantor perfectes.
L'altra decisió: l'original ofereix dues sortides i en deixa una comentada — abs(d)
dibuixa el contorn i d omple els triangles. El contorn és fidel però molt
fosc (brillantor mitjana 3/255 contra 85/255 de l'omplert), i si aquestes línies fines
es llegeixen en un tub per compost no es pot saber sense mirar-ho. Per això la tria és
un commutador al comandament x2 en lloc d'una constant: es decideix davant la
pantalla. fwidth(), que l'original fa servir per mantenir el gruix del traç constant,
s'ha substituït per una fórmula analítica, perquè en aquest aparell no se sap si
GL_OES_standard_derivatives existeix.
Els crt_* van de 2-6 fps a 30
Els cinc crt_* es van mesurar el 2026-09-09 i quatre anaven a 2,1-6,4 fps. La causa
era l'antialiasing: l'únic que corria bé era l'únic amb AA 1., i els altres quatre
tenien AA 2., o sigui quatre mostres i el cos del bucle desenrotllat quatre vegades.
Amb AA 1. van a 30,0 / 30,0 / 30,0 / 15,4.
El detall que ho explica: la reixeta de CRT és cos(3.14*uv.x*640.), 320 cicles en
720 px = 2,25 px per franja, just al límit de Nyquist d'aquesta sortida. Es pagaven
quatre mostres per píxel per sostenir una trama que la sortida no pot mostrar a cap
nivell d'AA. També se'ls va treure el comandament u_x3 d'intensitat de CRT, que estava
mort: amb X3_AS_SPEED actiu aquest uniform arriba valent 0.0 i el comandament no el
mou.
voronoi, i el limit que no es veu al codi
D'Inigo Quilez, i l'unic d'aquest repositori amb llicencia MIT --els altres nou son
CC BY-NC-SA. Aixo vol dir que aquest si es pot fer servir en un bolo pagat i si es
podria aportar de tornada a r_e_c_u_r; els altres no. MIT obliga a conservar el text
integre de la llicencia, i per aixo originals/voronoi-bordes-iq.frag no es opcional.
El que hi ha aqui NO es l'algorisme del shader original, i val la pena saber per que. iq va escriure aquell shader precisament per ensenyar el metode CORRECTE de dibuixar les vores: una segona passada que mesura la distancia real a la bisectriu entre la cel·la guanyadora i les veines, en lloc del F2-F1 de tota la vida. El seu article comenca ensenyant que F2-F1 dona linies de gruix desigual.
Aquella segona passada no hi cap en aquest aparell. Amb els dos bucles 3x3 el shader
compila, enllaca, corre a 4,6 fps i pinta NEGRE; amb un de sol funciona. Un bucle 3x3 es
desenrotlla en nou copies del cos, i dos son divuit. Es va trigar cinc carregues a
esbrinar-ho, i pel cami va apareixer una cosa que no sabiem: el log SI avisa, amb
OpenGL error after checkAndCreateProgram(): error 1285, que es GL_OUT_OF_MEMORY.
Aixi que aqui hi ha el F2-F1 d'una sola passada --el que el mateix iq escriu al seu article com "la implementacio que la gent fa servir"--. Es perd l'exactitud de la vora, que era la gracia de l'original, i es conserven les cel·les, l'animacio, les isolinies i els punts. Cost 66 en lloc de 154.