Fundamentalny problem
Gdy shader GLSL się psuje, zwykle nie dostajesz komunikatu o
błędzie. Dostajesz czarny ekran, jednolity kolor albo wzór, który w
ogóle nie przypomina tego, czego się spodziewałeś. Shader działa na
GPU — nie ma tam breakpointów, nie ma console.log, nie
ma stosów wywołań.
Jest jednak jeden wynik, który możesz zbadać: kolor piksela. Niemal wszystko w debugowaniu GLSL sprowadza się do zakodowania podejrzanych wartości jako kolorów i przyjrzenia się rezultatowi.
Technika 1: Kolor jako wartość
Najbardziej przydatna technika. Zakoduj to, co budzi Twoje podejrzenia, jako kolor fragmentu:
// Podejrzenie: czy vNormal jest poprawny?
// Normalne są w zakresie [-1,1] — przemapuj na [0,1] do wyświetlenia
gl_FragColor = vec4(vNormal * 0.5 + 0.5, 1.0);
// Podejrzenie: czy UV jest poprawne?
gl_FragColor = vec4(vUv, 0.0, 1.0);
// Podejrzenie: czy float mieści się w prawidłowym zakresie?
// Zakoduj jako wartość kanału zielonego
gl_FragColor = vec4(0.0, clamp(myFloat, 0.0, 1.0), 0.0, 1.0);
Jeśli normalne na kuli są skierowane poprawnie, zobaczysz czerwony po prawej stronie, zielony na górze i niebieski od strony zwróconej ku Tobie. Jeśli widzisz jednolity szary lub purpurowy kolor — normalne są zepsute.
Technika 2: Komentowanie krok po kroku
Błąd shadera rzadko znajduje się tam, gdzie się go spodziewasz. Pracuj wstecz: zakomentuj końcowy efekt, dodaj bazowy kolor i stopniowo odkomentowuj, aż coś się zepsuje:
// Krok 1: wiadomo, że działa — po prostu biały
gl_FragColor = vec4(1.0);
// Krok 2: dodajemy oświetlenie — nadal działa?
// gl_FragColor = vec4(vec3(diffuse), 1.0);
// Krok 3: dodajemy teksturę — nadal działa?
// gl_FragColor = vec4(texture2D(map, vUv).rgb * diffuse, 1.0);
// Krok 4: dodajemy Fresnela — nadal działa?
// float f = fresnel(vViewDir, vNormal);
// gl_FragColor = vec4(mix(deep, sky, f), 1.0);
Technika 3: Sprawdzanie błędów kompilacji
Three.js loguje błędy kompilacji shadera do konsoli, ale tylko wtedy, gdy shader faktycznie się kompiluje. Literówka, która jest poprawną składnią GLSL, nie wygeneruje błędu — po prostu da zły wynik. Zawsze sprawdzaj:
// Wymuś sprawdzenie błędu kompilacji po zlinkowaniu programu
const gl = renderer.getContext();
const program = material.program?.program;
if (program && !gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Shader link failed:', gl.getProgramInfoLog(program));
}
Technika 4: SpectorJS
SpectorJS to rozszerzenie przeglądarki, które przechwytuje klatkę WebGL i pokazuje każde wywołanie rysowania, każdą wartość uniformu i każdą teksturę. To najbliższy odpowiednik debugera GPU dostępny w przeglądarce.
Korzystam z niego zawsze, gdy trik „kolor-jako-wartość” nie zawęża problemu wystarczająco szybko. Możliwość sprawdzenia dokładnej tekstury podpiętej pod uniform typu sampler zaoszczędziła mi godziny pracy.
Technika 5: Precyzja
Na mobilnych GPU (i niektórych zintegrowanych układach graficznych
desktopowych) precyzja mediump może powodować
nieoczekiwane obcinanie wartości lub utratę dokładności. Jeśli coś
działa na desktopie, ale psuje się na urządzeniach mobilnych, dodaj:
// Zawsze deklaruj precyzję w shaderach fragmentów
precision highp float;
precision highp int;
Najszybsza pętla debugowania: Dodaj
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); (jednolity
czerwony) na samym końcu shadera fragmentów. Jeśli nie widzisz
czerwieni, shader w ogóle nie jest używany — sprawdź przypisanie
materiału, a nie kod shadera.