Documentacion en catalan
El ingles se queda como fuente canonica (docs/*.en.md, plantilles/template-*) y el resto pasa a catalan: el agente, la skill, la guia y la referencia (.es.md -> .ca.md), el documento del aparato (el-aparato -> l-aparell), el README, el LLEGEIX-ME de instrumentacio y los dos diagnosticos. Renombradas tambien las carpetas: plantillas/ -> plantilles/, instrumentacion/ -> instrumentacio/. Los enlaces de la documentacion inglesa apuntan ya a los nombres nuevos. Los comentarios de los .frag van sin acentos, en catalan igual que antes en castellano: el driver de la Pi rechaza los bytes no-ASCII incluso dentro de comentarios y el shader da pantalla verde sin avisar. Se quedan en castellano a proposito, y esta dicho en el README: los nombres de variable y las cadenas de log de la instrumentacion --fps-por-shader.py los busca tal cual y los registros ya capturados del aparato los llevan asi-- y los mensajes de check_port.py, que habla ingles y castellano. Generado con generar-agente-portable.py. No editar aqui. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,681 @@
|
||||
---
|
||||
doc: SHADERTOY-RECUR-GUIDE
|
||||
rev: 03
|
||||
canonical_language: en
|
||||
canonical_source: docs/conversion-guide.en.md
|
||||
translations:
|
||||
- ca: docs/conversion-guide.ca.md (rev 03, al dia)
|
||||
---
|
||||
|
||||
# De Shadertoy a r_e_c_u_r
|
||||
|
||||
Shadertoy corre sobre WebGL2, és a dir **GLSL ES 3.00**. r_e_c_u_r corre sobre OpenGL
|
||||
ES 2.0, és a dir **GLSL ES 1.00**. No és un canvi de noms: és un llenguatge anterior al
|
||||
qual li falten coses que mig Shadertoy fa servir sense pensar. D'aquí les dues parts de
|
||||
sota: la traducció mecànica (ràpida) i allò que s'ha de reescriure de debò.
|
||||
|
||||
Per saber quins uniforms existeixen i per què, vegeu la
|
||||
[referència](recur-shader-reference.ca.md).
|
||||
|
||||
> **Si el teu shader és 1-input, llegeix abans el tutorial de l'autor.**
|
||||
> [tutorial_converting_simple_1input_shader_from_shadertoy](https://git.terata.org/eessppeelllloo/r_e_c_u_r/wiki/tutorial_converting_simple_1input_shader_from_shadertoy)
|
||||
> és el pas a pas del mateix cyberboy666 i el camí més curt per a un shader que processa
|
||||
> el vídeo d'entrada. Aquesta guia és més ampla —cobreix 0-input, els límits de la GPU i
|
||||
> quatre errors mesurats a l'aparell— però no el substitueix. Vegeu
|
||||
> [El tutorial oficial](#el-tutorial-oficial-i-en-què-sen-aparta-aquesta-guia) per saber
|
||||
> què ensenya i on s'acaba el seu abast.
|
||||
|
||||
## Part 1 — la traducció mecànica
|
||||
|
||||
### Estructura
|
||||
|
||||
Shadertoy et dona només el cos de `mainImage`. r_e_c_u_r vol un fitxer complet:
|
||||
|
||||
```glsl
|
||||
// SHADERTOY
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord){
|
||||
vec2 uv = fragCoord/iResolution.xy;
|
||||
fragColor = vec4(uv, 0.5 + 0.5*sin(iTime), 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
```glsl
|
||||
// R_E_C_U_R
|
||||
//0-input
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
varying vec2 v_texcoord;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_time;
|
||||
uniform float u_x0;
|
||||
uniform float u_x1;
|
||||
uniform float u_x2;
|
||||
uniform float u_x3;
|
||||
|
||||
void main(){
|
||||
vec2 uv = v_texcoord;
|
||||
gl_FragColor = vec4(uv, 0.5 + 0.5*sin(u_time), 1.0);
|
||||
}
|
||||
```
|
||||
|
||||
La marca `//N-input` de la primera línia és el que r_e_c_u_r busca per etiquetar el
|
||||
shader al seu menú. Sense ella, el shader apareix com a tipus `-`.
|
||||
|
||||
### Taula d'equivalències
|
||||
|
||||
| Shadertoy | r_e_c_u_r | Compte amb |
|
||||
|---|---|---|
|
||||
| `void mainImage(out vec4 fragColor, in vec2 fragCoord)` | `void main()` | |
|
||||
| `fragColor` | `gl_FragColor` | |
|
||||
| `fragCoord` | `v_texcoord * u_resolution` | O `gl_FragCoord.xy`, que també existeix |
|
||||
| `fragCoord/iResolution.xy` | `v_texcoord` | El cas comú: se substitueix sencer |
|
||||
| `iResolution` | `u_resolution` | Shadertoy dona un **`vec3`**; aquí és `vec2`. `iResolution.z` no existeix |
|
||||
| `iTime` | `u_time` | Pot anar enrere i ser negatiu |
|
||||
| `iChannel0` / `iChannel1` | `u_tex0` / `u_tex1` | |
|
||||
| `texture(ch, uv)` | `texture2D(ch, uv)` | |
|
||||
| `iMouse.xy/iResolution.xy` | `vec2(u_x0, u_x1)` | Ja ve en 0–1, no cal dividir |
|
||||
| `iMouse.z > 0.0` (botó) | `u_x2 > 0.5` | Els params són continus; es fan servir com a interruptor |
|
||||
| `iTimeDelta`, `iFrame`, `iDate`, `iSampleRate`, `iChannelResolution` | **no existeixen** | Treure'ls o simular-los |
|
||||
|
||||
### Els paràmetres: de constants màgiques a comandaments
|
||||
|
||||
Aquesta és la part que fa que un port valgui la pena. Un shader de Shadertoy té números
|
||||
fixos per tot arreu; a r_e_c_u_r aquells números poden ser els comandaments.
|
||||
|
||||
`u_x0`…`u_x3` **arriben sempre entre 0.0 i 1.0** (`conjur.cpp:54`). L'escalat es fa dins
|
||||
del shader:
|
||||
|
||||
```glsl
|
||||
float velocitat = u_x0 * 4.0; // 0 → 4
|
||||
float zoom = 0.5 + u_x1 * 3.5; // 0.5 → 4 (mai 0, que rebenta una divisio)
|
||||
float angle = u_x2 * 6.2831853; // volta completa
|
||||
bool invertir = u_x3 > 0.5; // interruptor
|
||||
```
|
||||
|
||||
Triar quins quatre números mereixen comandament és la decisió de disseny del port. Dues
|
||||
regles que funcionen bé:
|
||||
|
||||
- **Amb els quatre comandaments a zero, el shader s'hauria de veure com l'original.** És
|
||||
com arriba en carregar-lo a l'aparell: si a zero es veu pla o negre, sembla trencat.
|
||||
- **Cada comandament ha de canviar alguna cosa reconeixible a tres metres**, no afinar un
|
||||
decimal. Si l'original cicla automàticament entre estats, poder **triar** l'estat sol
|
||||
ser el comandament més valuós de tots: sense ell, toca esperar.
|
||||
|
||||
A més: si l'ajust `X3_AS_SPEED` està activat, **`u_x3` no arriba mai al shader**; passa a
|
||||
ser el control de velocitat. Un shader que necessiti els quatre params només funciona amb
|
||||
aquell ajust desactivat. Dissenya per a tres.
|
||||
|
||||
### Proporció i centrat
|
||||
|
||||
Shadertoy sol normalitzar així perquè els cercles surtin rodons:
|
||||
|
||||
```glsl
|
||||
vec2 uv = (fragCoord - 0.5*iResolution.xy)/iResolution.y;
|
||||
```
|
||||
|
||||
`v_texcoord` va de 0 a 1 en **tots dos** eixos, així que copiar-ho tal qual deixa tot
|
||||
estirat en una sortida panoràmica. L'equivalent:
|
||||
|
||||
```glsl
|
||||
vec2 uv = v_texcoord - 0.5;
|
||||
uv.x *= u_resolution.x / u_resolution.y;
|
||||
```
|
||||
|
||||
## Part 2 — allò que GLSL ES 1.00 no té
|
||||
|
||||
Aquí és on se'n van les hores. Si el shader de Shadertoy fa servir res d'aquesta llista,
|
||||
no és copiar i enganxar: s'ha de reescriure.
|
||||
|
||||
### Operadors de bits — el que més trenca
|
||||
|
||||
GLSL ES 1.00 **no té `&`, `|`, `^`, `<<`, `>>` ni `%`**. Els hashos moderns de Shadertoy
|
||||
els fan servir constantment:
|
||||
|
||||
```glsl
|
||||
// SHADERTOY — no compila a la Pi
|
||||
uint h = uint(p.x) * 374761393u;
|
||||
h = (h ^ (h >> 13u)) * 1274126177u;
|
||||
```
|
||||
|
||||
Substituir per un hash de coma flotant dels de tota la vida:
|
||||
|
||||
```glsl
|
||||
float hash(vec2 p){
|
||||
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
|
||||
}
|
||||
```
|
||||
|
||||
No produeix el mateix soroll, així que el resultat no serà idèntic a l'original. És una
|
||||
reescriptura, no una traducció, i convé dir-ho en veu alta.
|
||||
|
||||
Tampoc no hi ha `uint`, ni `uvec2/3/4`, ni `%`. Per al mòdul: `mod(a, b)` amb floats.
|
||||
|
||||
### Bucles
|
||||
|
||||
Els límits han de ser **constants en temps de compilació**. Això no compila:
|
||||
|
||||
```glsl
|
||||
for(int i = 0; i < passos; i++){ ... } // `passos` es una variable
|
||||
```
|
||||
|
||||
Es posa el màxim com a constant i se surt amb un `break`:
|
||||
|
||||
```glsl
|
||||
const int MAX_PASSOS = 64;
|
||||
for(int i = 0; i < MAX_PASSOS; i++){
|
||||
if(float(i) >= passos) break;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Tampoc no hi ha `while`, ni `switch`, ni indexar arrays amb un índex variable.
|
||||
|
||||
### Funcions que falten
|
||||
|
||||
| No hi és a ES 1.00 | Alternativa |
|
||||
|---|---|
|
||||
| `textureLod`, `texelFetch` | `texture2D` a seques (sense control de nivell de mipmap) |
|
||||
| `dFdx`, `dFdy`, `fwidth` | Necessiten l'extensió `GL_OES_standard_derivatives`. **[PER VERIFICAR A LA MÀQUINA]** |
|
||||
| `round`, `trunc`, `roundEven` | `floor(x + 0.5)`, `floor(abs(x))*sign(x)` |
|
||||
| `sinh`, `cosh`, `tanh` | Escriure-les amb `exp()` |
|
||||
| `inverse`, `transpose`, `determinant` | A mà, o redissenyar per no necessitar-les |
|
||||
| `isnan`, `isinf` | No hi ha manera neta |
|
||||
| `mix()` amb booleans | Només floats |
|
||||
|
||||
### Noms que tapen funcions
|
||||
|
||||
Una variable local anomenada com una funció incorporada (`mix`, `step`, `length`…) tapa
|
||||
aquella funció dins del seu àmbit. Compila a escriptori i és imprevisible a la Pi.
|
||||
Reanomena-la.
|
||||
|
||||
### El bucle d'antialiasing multiplica la mida per AA*AA
|
||||
|
||||
Un parany que se suma al de la mida, i facil de passar per alt. Un bucle aixi:
|
||||
|
||||
```glsl
|
||||
#define AA 2.
|
||||
for(float i = 0.; i < AA*AA - 0.5; i += 1.) {
|
||||
...
|
||||
vec3 c = el_meu_patro(p);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
te limits constants, aixi que el compilador el **desenrotlla**. El cos sencer -- funcio
|
||||
de patro inclosa -- acaba al codi final **quatre vegades**. Amb `AA` a 3 en serien nou.
|
||||
|
||||
Mesurat a la maquina: de cinc ports que nomes es diferencien en la funcio de patro, els
|
||||
quatre amb un patro de 10-15 linies van funcionar, i el que tenia 31 linies i 8
|
||||
condicions va sortir negre. Abaixant-lo a `AA 1.` hi va entrar.
|
||||
|
||||
Aixi que quan un port es a prop del limit, `AA` es la palanca mes barata: divideix per
|
||||
quatre el codi generat canviant un caracter. El que es perd es el suavitzat de vores.
|
||||
|
||||
Abans de tocar `AA`, busca condicions repetides. En aquell mateix patro hi havia tres
|
||||
branques que tornaven a comprovar el mateix rang; treure-ho fora va llevar sis
|
||||
comparacions per mostra -- vint-i-quatre ja desenrotllat -- **sense canviar ni un sol
|
||||
pixel**.
|
||||
|
||||
### Quan el soroll ES l'efecte
|
||||
|
||||
Hi ha shaders que no calculen una imatge: calculen un accident controlat. El senyal es
|
||||
una constant absurda. `time *= 5e19` no es un compte, es llencar la precisio a proposit
|
||||
perque el `mod()` de despres llegeixi bits que ja no signifiquen res. La sortida es
|
||||
soroll numeric, volent.
|
||||
|
||||
Dues consequencies, i totes dues convé dir-les en veu alta abans de portar:
|
||||
|
||||
- **Necessita `highp`.** `5e19` en `mediump` es infinit, i no queda shader.
|
||||
- **No es veura igual que al navegador**, mai, perque depen de com arrodoneixi cada GPU.
|
||||
Prometre un resultat identic es prometre el que no es pot complir.
|
||||
|
||||
El mateix va passar al reves amb un shader de reixa el clapejat del qual venia de la
|
||||
precisio baixa del navegador: l'aparell el dibuixava net i el port semblava *malament*
|
||||
per estar be. En tots dos casos la regla es la mateixa: **quan un render i la seva
|
||||
referencia no coincideixen, cal preguntar-se si la referencia esta ensenyant un
|
||||
artefacte.**
|
||||
|
||||
`check_port.py` avisa de qualsevol literal per sobre de 65504 justament per aixo.
|
||||
|
||||
### El raymarching no hi cap --- mesurat, no suposat
|
||||
|
||||
Un shader que marxa raigs no entra en aquest aparell, i la manera de saber-ho no es
|
||||
intentar-ho. Mesurat el 2026-09-05 amb un shader de keim
|
||||
([MtGSWc](https://www.shadertoy.com/view/MtGSWc)), l'original del qual costa **234 crides
|
||||
a `map()` per pixel**:
|
||||
|
||||
| Versio | Crides a `map()` | A l'aparell |
|
||||
|---|---|---|
|
||||
| Original | 234 | impossible |
|
||||
| Reduida | 19 | **s'encalla, les figures perden definicio** |
|
||||
| Mes reduida | 13 | **tambe s'encalla** |
|
||||
| Redissenyada en 2D | 1 avaluacio | funciona |
|
||||
|
||||
Aquell `map()` portava a dins un `exp`, un `sin`, dos `mod` i quatre `length`. Per
|
||||
comparar: un bucle de 8 voltes amb un cos molt mes barat va a 13.2 fps.
|
||||
|
||||
**Aixi que la maniobra no es abaixar els passos.** No es questio d'afinar: no hi cap amb
|
||||
cap nombre de passos que valgui la pena. Cal redissenyar la idea en 2D --una avaluacio
|
||||
per pixel, sense marxa-- que es el que va funcionar aqui i abans amb un Mandelbox. I
|
||||
conve dir-ho aviat, abans de gastar la tarda en una traduccio que no pot correr.
|
||||
|
||||
`check_port.py` ja estima les operacions per pixel i avisa per sobre de 250. El llindar
|
||||
esta calibrat amb shaders reals d'aquest aparell: els que van arriben a 166 i el que
|
||||
s'encalla n'estima 360.
|
||||
|
||||
### Si es massa gran no dibuixa res, i s'ha de partir
|
||||
|
||||
Mesurat a la maquina el 2026-09-03. Un shader massa gran **compila, enllaca i despres no
|
||||
dibuixa**: pantalla negra o el fotograma anterior congelat, sense cap error enlloc -- ni
|
||||
al registre de GLSL ni a la interficie de r_e_c_u_r.
|
||||
|
||||
Dos punts mesurats, un a cada costat del limit, del mateix shader:
|
||||
|
||||
| | linies de codi | funcions | operacions | A la Pi |
|
||||
|---|---|---|---|---|
|
||||
| Cinc funcions de patro | 146 | 13 | 69 | **pantalla negra** |
|
||||
| Dues funcions de patro | 85 | 10 | 38 | funciona |
|
||||
|
||||
Per comparar, cap dels 146 shaders que porta la imatge de r_e_c_u_r passa de **90 linies
|
||||
ni de 23 operacions**. Tots porten un efecte per fitxer, i aixo no es una decisio
|
||||
estetica: es el que hi cap.
|
||||
|
||||
Aixi que quan un shader de Shadertoy fica diversos efectes diferents en un fitxer darrere
|
||||
d'un selector -- cosa molt habitual -- el port son diversos shaders, no un. Es perd el
|
||||
selector intern i es guanya un comandament, perque r_e_c_u_r ja deixa canviar de shader
|
||||
des de la interficie.
|
||||
|
||||
`check_port.py` avisa per sobre de 100 linies o 45 operacions.
|
||||
|
||||
### Dins d'un bucle, assigna des d'una funcio nomes a la declaracio
|
||||
|
||||
Comprovat a la maquina el 2026-09-03, despres d'una bisecció llarga.
|
||||
|
||||
Dins del cos d'un bucle, aixo genera **codi incorrecte** al compilador GLSL de la Pi:
|
||||
|
||||
```glsl
|
||||
vec3 c;
|
||||
c = el_meu_patro(p); // assignada des d'una funcio propia
|
||||
```
|
||||
|
||||
El shader **compila sense ni un sol avis** i s'executa, pero la sortida deixa de dependre
|
||||
d'`u_time` i dels comandaments: surt una imatge congelada, o negra. Ni el registre diu
|
||||
res, ni la interficie.
|
||||
|
||||
Inicialitza-la a la mateixa declaracio:
|
||||
|
||||
```glsl
|
||||
vec3 c = el_meu_patro(p);
|
||||
```
|
||||
|
||||
Donar-li un valor per defecte a la declaracio **no basta**: el problema es la
|
||||
reassignacio, no que falti l'inicialitzador. Si has de triar entre diverses funcions, mou
|
||||
l'eleccio a una funcio a part perque el cos del bucle tingui exactament una assignacio:
|
||||
|
||||
```glsl
|
||||
vec3 tria(int quin, vec2 p) {
|
||||
if (quin == 0) return patro_a(p);
|
||||
if (quin == 1) return patro_b(p);
|
||||
return patro_c(p);
|
||||
}
|
||||
...
|
||||
vec3 c = tria(quin, p);
|
||||
```
|
||||
|
||||
Les tres parts de la regla compten, i cadascuna es va provar per separat:
|
||||
|
||||
| Dins d'un bucle | Resultat a la maquina |
|
||||
|---|---|
|
||||
| `vec3 c = la_meva_funcio(p);` | **funciona** |
|
||||
| `vec3 c; c = vec3(1.0, 0.0, 0.0);` (expressio en linia) | **funciona** |
|
||||
| `vec3 c; c = la_meva_funcio(p);` | **congelat / negre** |
|
||||
| `vec3 c = vec3(0.0); c = la_meva_funcio(p);` | **congelat / negre** |
|
||||
|
||||
Fora d'un bucle, declarar sense inicialitzar no dona problema: 22 dels 124 shaders que
|
||||
porta la imatge de r_e_c_u_r ho fan. **Cap ho fa dins d'un bucle.**
|
||||
|
||||
`check_port.py` detecta exactament aquella combinacio: 0 falsos positius sobre els 141
|
||||
shaders que sabem que funcionen en un aparell real, i caça els 5 nostres que fallaven.
|
||||
|
||||
> Aquesta va costar dies, i val la pena entendre per que. Totes les senyals apuntaven en
|
||||
> la direccio equivocada: compila, s'executa, la interficie mostra el shader com si
|
||||
> estigues funcionant, i el codi es GLSL absolutament corrent que funciona en qualsevol
|
||||
> escriptori. L'unica manera de trobar-ho va ser bisecar de canvi en canvi contra un
|
||||
> shader que se sabia bo en aquell aparell concret.
|
||||
|
||||
### Un shader 0-input no pot fer servir `v_texcoord`
|
||||
|
||||
Trobat a la maquina el 2026-09-02, despres de dies al darrere. Es el parany mes car de
|
||||
tots els que ens han sortit.
|
||||
|
||||
Mira quina geometria dibuixa conjur (`ofxVideoArtTools/src/conjur.cpp`, `apply()`):
|
||||
|
||||
```cpp
|
||||
if(textures.size() > 0 ){
|
||||
textures[0].draw(0, 0, ofGetWidth(), ofGetHeight()); // genera texcoords
|
||||
} else {
|
||||
ofDrawRectangle(0, 0, ofGetWidth(), ofGetHeight()); // NO les genera
|
||||
}
|
||||
```
|
||||
|
||||
Sense textures d'entrada, la geometria es un `ofDrawRectangle`, que **no porta
|
||||
coordenades de textura**. L'atribut `texcoord` de `default.vert` queda indefinit, aixi que
|
||||
tant `tcoord` com `v_texcoord` arriben al fragment shader **constants**. Tots els pixels
|
||||
calculen el mateix i surt **una pantalla d'un color pla, sense cap missatge d'error**.
|
||||
|
||||
Cal treure la coordenada de `gl_FragCoord`:
|
||||
|
||||
```glsl
|
||||
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
|
||||
```
|
||||
|
||||
Els shaders del repositori original segueixen aquesta regla sense haver-la escrit mai.
|
||||
Comptat sobre un aparell real:
|
||||
|
||||
| | fa servir `v_texcoord` | fa servir `gl_FragCoord` |
|
||||
|---|---|---|
|
||||
| Els 5 `0-input` del repositori | **0** | 5 |
|
||||
| Els 12 `1-input` del repositori | 12 | **0** |
|
||||
|
||||
Disset de disset, sense excepcions. `check_port.py` ja ho detecta.
|
||||
|
||||
> Per que va costar tant trobar-ho: la fallada depen de l'**estat en execucio, no del
|
||||
> fitxer**. Si hi ha un video reproduint-se, `textures` no esta buit, la geometria es un
|
||||
> quadrilater amb textura i `v_texcoord` funciona perfectament — el mateix shader es
|
||||
> comporta be. Per aixo el mateix fitxer semblava funcionar un dia i no l'endema, i per
|
||||
> aixo el "color de la fallada" anava canviant: el que es veu es el que hi hagi a sota en
|
||||
> aquell moment.
|
||||
|
||||
### El fitxer ha de ser ASCII pur
|
||||
|
||||
Descobert per les males el 2026-08-29: dos shaders que renderitzaven perfectament a
|
||||
glslViewer donaven **pantalla verda a l'aparell, sense cap missatge d'error**. L'única
|
||||
cosa que tenien en comú eren comentaris amb accents, guions llargs i cometes
|
||||
tipogràfiques.
|
||||
|
||||
Els 23 shaders que porta r_e_c_u_r són ASCII pur. Ni un accent entre tots ells. El
|
||||
controlador de la Pi és més estricte que el d'un escriptori i rebutja els bytes no-ASCII
|
||||
**fins i tot dins de comentaris**, on un controlador d'escriptori simplement els ignora.
|
||||
|
||||
Escriu els comentaris en ASCII. `check_port.py` ho detecta.
|
||||
|
||||
> Aquest és especialment dolent perquè totes les senyals apunten en la direcció
|
||||
> equivocada: compila al portàtil, el revisor el donava per bo, i r_e_c_u_r el mostra com
|
||||
> si estigués corrent.
|
||||
|
||||
### r_e_c_u_r no et dirà que el shader ha fallat
|
||||
|
||||
`video_centre/shaders.py:18` documenta un estat `'!'` que significa error, però no hi ha
|
||||
ni una sola línia del codi que l'assigni. Un shader que no compila es mostra exactament
|
||||
igual que un que funciona.
|
||||
|
||||
Per veure l'error real de GLSL cal la sortida estàndard de c_o_n_j_u_r. Es llança amb
|
||||
`subprocess.Popen` sense redirecció (`actions.py:573`), així que la seva sortida va on
|
||||
vagi la consola de r_e_c_u_r. Per SSH:
|
||||
|
||||
```bash
|
||||
pkill c_o_n_j_u_r
|
||||
/home/pi/openframeworks10.1/apps/myApps/c_o_n_j_u_r/bin/c_o_n_j_u_r
|
||||
```
|
||||
|
||||
Després es carrega el shader des de la interfície i es llegeix el terminal.
|
||||
openFrameworks imprimeix el registre complet de GLSL, amb el número de línia de la
|
||||
fallada.
|
||||
|
||||
### Multipassada: el límit gran
|
||||
|
||||
Shadertoy permet `Buffer A/B/C/D`: passades que llegeixen el seu propi fotograma
|
||||
anterior. Tot el feedback, els autòmats cel·lulars, els fluids i les simulacions depenen
|
||||
d'això. **No hi ha equivalent directe en un shader de r_e_c_u_r.**
|
||||
|
||||
El que sí que hi ha, i dona molt de joc, és diferent:
|
||||
|
||||
- **Les 3 capes de shader.** S'apliquen en cadena, i la capa 1 rep a `u_tex0` la sortida
|
||||
de la capa 0 i a `u_tex1` l'entrada de la capa 0 (`ofApp.cpp:115-129`). Són diverses
|
||||
passades, però sense memòria del fotograma anterior.
|
||||
- **El mode `detour`** de r_e_c_u_r, que sí que és un bucle de realimentació de vídeo.
|
||||
L'ajust `SHADER_POSITION` decideix si els shaders van abans o després.
|
||||
|
||||
A la pràctica: un shader de Shadertoy amb Buffers no es porta. O es redissenya la idea al
|
||||
voltant de detour, o es descarta. Digues-ho aviat, abans de gastar una hora traduint una
|
||||
cosa que no pot funcionar.
|
||||
|
||||
### Precisió
|
||||
|
||||
`precision mediump float;` és el que porten tots els shaders del repositori. En `mediump`
|
||||
el rang útil és curt i `u_time` **creix sense límit**: al cap de pocs minuts apareixen
|
||||
bandes i salts en qualsevol cosa que faci `sin(u_time * alguna_cosa_gran)`.
|
||||
|
||||
Dos remeis, per ordre:
|
||||
|
||||
1. Embolcallar el temps. Si el shader té un cicle natural,
|
||||
`mod(u_time, durada_del_cicle)` dona exactament la mateixa seqüència sense créixer. Si
|
||||
no, `mod(u_time, 100.0)` sol ser prou.
|
||||
2. Demanar més precisió sense trencar la compatibilitat:
|
||||
|
||||
```glsl
|
||||
#ifdef GL_ES
|
||||
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
||||
precision highp float;
|
||||
#else
|
||||
precision mediump float;
|
||||
#endif
|
||||
#endif
|
||||
```
|
||||
|
||||
**[PER VERIFICAR A LA MÀQUINA]** si el `highp` en fragment shader està disponible i què
|
||||
costa.
|
||||
|
||||
### Rendiment
|
||||
|
||||
És una Raspberry Pi renderitzant vídeo alhora. Un raymarching de 128 passos que va suau
|
||||
al navegador aquí va a batzegades. El que més costa, per ordre: bucles d'antialiasing (el
|
||||
cost va al quadrat — `AA 4.` són 16 mostres per píxel), raymarching amb molts passos, i
|
||||
`pow`/`log`/`atan` dins de bucles.
|
||||
|
||||
Abaixa aquests números i **deixa un comentari dient com pujar-los**, per poder ajustar el
|
||||
shader a la màquina sense rellegir-lo sencer. Val més un que corre a 25 fps que un altre
|
||||
més bonic a 4.
|
||||
|
||||
**Mesurat a l'aparell (2026-09-04), i canvia la manera d'ajustar.** El sostre són **30.0
|
||||
fps**, i és un topall *de programari*, no de la pantalla: conjur fa `ofSetFrameRate(30)`
|
||||
amb `ofSetVerticalSync(false)` (`ofApp.cpp:11-12`). Així que un shader que marca 30.0 és
|
||||
al topall i potser li sobra marge de GPU; per sota de 30 el que es mesura és la màquina.
|
||||
El quadre és de 720x480 perquè `/boot/config.txt` porta `sdtv_mode=16` (NTSC progressiu)
|
||||
i conjur pren la mida de la pantalla. Un shader el bucle del qual feia 8 voltes, amb dos
|
||||
`smoothstep`+`distance` per volta, va mesurar:
|
||||
|
||||
| Voltes del bucle | fps | Interval entre fotogrames |
|
||||
|---|---|---|
|
||||
| 8 | 13.2 | 76 ms |
|
||||
| 10 | 6.6 | 152 ms |
|
||||
| 12 | 4.8 | 208 ms |
|
||||
| 16 | 3.9 | 256 ms |
|
||||
|
||||
D'aquí en surten dues coses, i cap de les dues es veu des de l'escriptori.
|
||||
|
||||
**És un graó, no una costa.** El cost per volta salta de 9.5 ms amb 8 voltes a 15.2 ms
|
||||
amb 10, i després es queda pla. En créixer el bucle desenrotllat es creua algun llindar de
|
||||
la GPU, i a partir d'aquí el shader **sencer** surt més car. Així que quan un port va
|
||||
just, «retallar-lo una mica» és la maniobra equivocada: cal esbrinar de quin costat del
|
||||
graó és i posar-se al bo. És el mateix tipus de llindar que el límit de mida que fa que un
|
||||
shader no dibuixi res: primer es posa lent, més enllà desapareix.
|
||||
|
||||
**Compara l'interval entre fotogrames amb la transició més curta que vulguis veure.** Si
|
||||
el shader té una lluïssor, un cop o qualsevol atac breu, mesura quant dura i posa-ho al
|
||||
costat de l'interval. Al shader de dalt la lluïssor durava 111 ms: amb 8 voltes (76 ms) es
|
||||
recull; amb 10 (152 ms) es perd més vegades de les que es veu, i aleshores el port sembla
|
||||
*mal fet* en comptes de lent. Una transició més curta que l'interval no es veu dèbil: **no
|
||||
es veu**, i res no ho avisa.
|
||||
|
||||
Per tenir aquests números en comptes d'endevinar-los, vegeu l'envoltori
|
||||
d'`instrumentacio/`: compta fotogrames a l'aparell i els escriu al mateix registre que les
|
||||
càrregues de shader.
|
||||
|
||||
### El tutorial oficial, i en què se n'aparta aquesta guia
|
||||
|
||||
cyberboy666 en va escriure un:
|
||||
[tutorial_converting_simple_1input_shader_from_shadertoy](https://git.terata.org/eessppeelllloo/r_e_c_u_r/wiki/tutorial_converting_simple_1input_shader_from_shadertoy).
|
||||
**Si estàs portant un shader 1-input, llegeix-lo.** És curt, és del mateix autor i és el
|
||||
que segueix la resta de la comunitat. El que ve ara no el corregeix —tot el que diu quadra
|
||||
amb el codi font— sinó que dibuixa on s'acaba el seu abast.
|
||||
|
||||
Està escrit en la **convenció antiga** (`tcoord`, `tex`, `tres`, `fparams`, `ftime`), que
|
||||
ve del mòdul Structure d'Erogenous Tones. Aquella convenció funciona —conjur enllaça els
|
||||
dos jocs de noms a cada fotograma, sense condició—, però dels 22 shaders que porta el
|
||||
mateix repositori de r_e_c_u_r, 20 són moderns, 1 antic i 1 mixt. Aquesta guia fa servir
|
||||
el joc modern perquè els shaders nous s'assemblin a ells.
|
||||
|
||||
Tres coses seves que convé endur-se:
|
||||
|
||||
- **Provar al sandbox abans que a l'aparell** (vegeu *Previsualitzar sense la Pi*). És el
|
||||
més útil que té, i que no fos a les nostres eines no és casualitat: va costar una sessió
|
||||
sencera de depuració descobrir el forat que tapa.
|
||||
- **Que el comandament a zero signifiqui «sense efecte».** El seu idioma per a un
|
||||
multiplicador és `(1.0 + f1)` en comptes de `f1`: en repòs multiplica per un, a fons
|
||||
duplica. Multiplicar pel paràmetre pelat anul·la la imatge en la posició de repòs.
|
||||
- Els literals numèrics necessiten punt decimal: `1.0`, mai `1`.
|
||||
|
||||
Quatre llocs on s'acaba el seu abast. No són errors del tutorial, són casos que no tracta,
|
||||
i cadascun d'ells dona pantalla plana o congelada sense cap missatge d'error:
|
||||
|
||||
- La seva idea central —*«`tcoord` ja ve normalitzada, no divideixis per la resolució»*—
|
||||
val **només mentre s'estigui dibuixant una textura**. En un shader generatiu sense vídeo
|
||||
carregat, `conjur::apply()` cau a `ofDrawRectangle()`, que no genera coordenades de
|
||||
textura, i les dues varyings arriben constants. Per a `0-input`,
|
||||
`gl_FragCoord.xy / u_resolution`. Vegeu *Un shader 0-input no pot fer servir
|
||||
`v_texcoord`*.
|
||||
- La seva plantilla declara `iparams` com a «4 ints coming in». Està mort: fixat a
|
||||
`(0,0,0,0)`.
|
||||
- El seu `float time = float(itime) + ftime;` no és un rellotge continu, perquè `itime` és
|
||||
`ceil()` i no `floor()`. Fes servir `u_time`.
|
||||
- El seu shader final no porta marca `//N-input`, i `shaders.py` sí que la llegeix.
|
||||
|
||||
Les seves marques `//f0::` són metadades del sandbox, no de r_e_c_u_r:
|
||||
`determine_shader_parameter_number` retorna 4 sense mirar res.
|
||||
|
||||
## Part 3 — el procediment
|
||||
|
||||
1. **Mirar la llicència primer.** Shadertoy publica sota
|
||||
[CC BY-NC-SA 3.0](https://creativecommons.org/licenses/by-nc-sa/3.0/) per defecte,
|
||||
llevat que l'autor digui una altra cosa al mateix codi. La clàusula **NC** vol dir que
|
||||
no es pot fer servir en un bolo pagat ni en res comercial. I compte: r_e_c_u_r és
|
||||
GPL-3.0, amb la qual NC **no** és compatible, així que un port derivat de material NC
|
||||
no es pot aportar de tornada al projecte.
|
||||
2. **Guardar l'original** amb una capçalera d'atribució (autor, URL, llicència, data de
|
||||
consulta). Si no tens l'URL, escriu `[PENDENT]` en comptes d'inventar-te-la: els
|
||||
identificadors de Shadertoy són opacs i un enllaç endevinat apunta a un altre shader.
|
||||
3. **Traduir el mecànic** amb la taula de la part 1.
|
||||
4. **Passar el revisor**, que fa aquella feina per tu:
|
||||
|
||||
```bash
|
||||
./check_port.py el_meu_shader.frag
|
||||
```
|
||||
|
||||
Marca `[BREAKS]` allò que no compila en GLSL ES 1.00, `[MISSING]` els requisits de
|
||||
r_e_c_u_r que no hi són i `[CHECK]` allò que depèn de la màquina. No és un compilador:
|
||||
que surti net no garanteix que compili a la Pi, però que surti brut garanteix que no.
|
||||
(Els seus missatges són en anglès; amb `--lang es` surten en castellà. Encara no parla
|
||||
català.)
|
||||
5. **Triar els params.** Quins tres o quatre números es converteixen en comandaments.
|
||||
6. **Previsualitzar en un escriptori abans de tocar la Pi** (vegeu més avall).
|
||||
7. **Guardar a `shaders/N-input/`** amb la marca correcta.
|
||||
8. **Copiar a la màquina** i provar de debò.
|
||||
|
||||
### Previsualitzar sense la Pi
|
||||
|
||||
Dues eines, i cacen coses diferents. Fes servir totes dues.
|
||||
|
||||
**El sandbox, per a compatibilitat.** <https://glsl.erogenous-tones.com> — *click to
|
||||
create new shader*, hi enganxes i compila a mesura que escrius. És WebGL 1, que és **el
|
||||
mateix perfil GLES 2.0 que corre la Pi**, així que rebutja allò que rebutjaria l'aparell.
|
||||
Això és justament el que no pot fer la previsualització d'escriptori de sota. Compte: porta
|
||||
la plantilla antiga, així que un shader escrit amb `u_x0`/`u_resolution` necessita que li
|
||||
adaptis els uniforms —o que els declaris com a variables soltes— per compilar-hi.
|
||||
|
||||
No veu el límit de mida ni pot reproduir els paranys que depenen de l'estat. Tanca el
|
||||
forat de sintaxi, no el de l'aparell.
|
||||
|
||||
**glslViewer, per iterar sobre la forma.** L'autor el recomana, del mateix que va escriure
|
||||
The Book of Shaders, i diu que els uniforms de conjur hi són compatibles
|
||||
(`notes_on_shader_formats.md`). Edites, guardes i la finestra s'actualitza sola.
|
||||
|
||||
```bash
|
||||
glslViewer shaders/0-input/el_meu_shader.frag -l
|
||||
```
|
||||
|
||||
Amb textura d'entrada, per als d'1 i 2 inputs:
|
||||
|
||||
```bash
|
||||
glslViewer shaders/1-input/el_meu_shader.frag una_imatge.png -l
|
||||
```
|
||||
|
||||
Per moure un paràmetre, s'escriu a la consola de glslViewer mentre corre:
|
||||
|
||||
```
|
||||
u_x0,0.35
|
||||
```
|
||||
|
||||
Per automatitzar —útil per generar captures de diversos shaders de cop— hi ha `--headless`
|
||||
i `-E`, que executa ordres i surt:
|
||||
|
||||
```bash
|
||||
glslViewer el_meu_shader.frag --headless -e "u_x0,0.6" -E "screenshot,sortida.png"
|
||||
```
|
||||
|
||||
### Comparar el port amb l'original, amb numeros
|
||||
|
||||
Renderitzar tots dos i mirar-los no basta per als canvis que importen. Treu PNG dels dos i
|
||||
resta'ls canal a canal, pero **clava abans el temps dins del shader**:
|
||||
|
||||
```bash
|
||||
sed -e 's/^uniform float u_time;/const float u_time = 3.0;/' \
|
||||
-e 's/^uniform float u_x\([0-3]\);/const float u_x\1 = 0.0;/' port.frag > fixat.frag
|
||||
glslViewer fixat.frag --headless -E "screenshot,sortida.png"
|
||||
```
|
||||
|
||||
El `-e "time,3.0"` de `glslViewer` **no** el clava: el mateix shader renderitzat dues
|
||||
vegades al mateix temps nominal donava fins a 55/255 de diferencia per canal en un instant
|
||||
de canvi rapid, mes que diverses de les diferencies reals que voliem mesurar. Amb el valor
|
||||
compilat a dins, dues passades surten identiques byte a byte i el soroll baixa a zero.
|
||||
|
||||
Es el que converteix un "es veu semblant" en un "0/255 de diferencia en tres instants, aixi
|
||||
que l'unica desviacio es la que vaig decidir a proposit".
|
||||
|
||||
### El parany de glslViewer: ofereix molt més del que hi ha a la màquina
|
||||
|
||||
glslViewer dona uns 40 uniforms (vegeu `UNIFORMS.md` al seu repositori). conjur n'enllaça
|
||||
**cinc**. Els únics presents a tots dos són `u_time`, `u_resolution`, `u_tex0` i `u_tex1`.
|
||||
|
||||
Tota la resta funciona a l'escriptori i **falla en silenci a la Pi**: l'uniform no
|
||||
s'enllaça, arriba a zero, i el shader es veu congelat o pla sense donar cap error. Els que
|
||||
més s'hi colen:
|
||||
|
||||
| Existeix a glslViewer | A r_e_c_u_r |
|
||||
|---|---|
|
||||
| `u_mouse` | **No.** L'error número u. Fer servir `u_x0`/`u_x1` |
|
||||
| `u_delta`, `u_frame`, `u_date` | **No** |
|
||||
| `u_pixelDensity`, `u_sceneFps`, `u_sceneMs` | **No** |
|
||||
| `u_view2d` (pan/zoom amb el ratolí) | **No** |
|
||||
| `u_camera*`, `u_light*`, `u_*Matrix` | **No** |
|
||||
|
||||
Al revés també: `u_x0`…`u_x3` són de conjur, no de glslViewer. Allà es fixen a mà des de la
|
||||
seva consola.
|
||||
|
||||
> Avís: glslViewer en un escriptori compila amb un GLSL més modern que el de la Pi. Que
|
||||
> funcioni allà **no garanteix** que compili a l'aparell. Serveix per iterar sobre la
|
||||
> forma. Per a compatibilitat, el sandbox de dalt; l'última paraula, la màquina.
|
||||
|
||||
### Copiar a la màquina
|
||||
|
||||
r_e_c_u_r llegeix shaders des d'un USB muntat, des de `/home/pi/r_e_c_u_r/Shaders` i des
|
||||
de `/home/pi/Shaders` (`data.py:34`). El menys invasiu és `/home/pi/Shaders`, que deixa net
|
||||
el repositori de r_e_c_u_r:
|
||||
|
||||
```bash
|
||||
rsync -av --dry-run shaders/ pi@IP_DE_LA_TEVA_PI:/home/pi/Shaders/
|
||||
```
|
||||
|
||||
Treure `--dry-run` quan la llista de fitxers sigui l'esperada.
|
||||
Reference in New Issue
Block a user