Tutorial 8

W tej lekcji dowiemy się czym jest blending i bufor głębokości.


Bufor głębokości

Kiedy zlecasz WebGL narysowanie czegoś, przechodzi on przez kilka etapów, jak pamiętasz z lekcji 2. Od najwyższego poziomu:
  1. Wywołanie vertex shadera
  2. Iterpolacja powierzchni między wierzchołkami
  3. Dla każdego fragmentu wywoływany fragment shader
  4. Zapisanie do bufora ramki.

Ramka jest tym co jest ostatecznie wyświetlane. Ale co sięstanie jeśli chcesz narysować dwa obiekty? Na przykład kwadrat w punkcie (0,0,-5), a następnie kolejny kwadrat tego samego rozmiaru w punkcie (0,0,-10)? Oczywiście nie chcesz rysować drugiego kwadratu bo jest zupełnie przykryty przez pierwszy, więc po co? Od tego właśnie jest bufor głębokości. Gdy obraz jest przekazywany do bufora, oprócz wartości RGBA, dostaje również wartość głębokości, która jest związana z wartością Z.

Mówiąc "związana z" musimy sobie coś wyjaśnić. WebGL wszystkie wartości Z oczekuje z zakresu 0-1, gdzie 0 to najbliżej, a 1 najdalej. Należy pamiętać, żę im większa wartość Z-bufora tym obiekt jest dalej, czyli przeciwnie do naszego naturalnego zapisu. Zapewne pamiętasz instrukcję z pierwszej lekcji:

  gl.enable(gl.DEPTH_TEST);

Mówi ona WebGL co robić podczas rysowania nowego obiektu i znaczy mniej więcej "Podczas rysowania miej na uwadzę bufor głębokości". Opcje możemy oczywiście zmieniać i służy nam do tego funkcja depthFunc. Przykładowo:

  gl.depthFunc(gl.LESS);

mówi coś w stylu "Jeśli nowy obiekt ma mniejszą wartość Z niż stary to narysuj nowy".


Blending

Blending jest alternatywą tego procesu. Przy depth-testing używamy funkcji głębokości aby zdecydować czy zastępować istniejący fragment nowym czy nie. Przy blendingu używamy funkcji które mieszają kolory z obu istniejących obiektów. Przejdźmy teraz do naszego przykładu.

Na dobry początek sprawdzamy czy opcja blendingu jest włączona

  var blending = document.getElementById("blending").checked;

Jeśli tak, to używamy funkcji, która wymiesza dwa fragmenty:

  if (blending) {
      gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

Parametry w tej funkcji określają w jaki sposób będą mieszane kolory. Wygląda dziwnie ale nie jest to trudne. Najpierw zdefiniujmy dwa terminy: source fragment to ten który rysujemy właśnie teraz, a destination fragment to ten który już jest w buforze ramki. Pierwszy parametr określa współczynnik źródła (source factor) a drugi przeznaczenia (destination factor). Sposób w jaki WebGL wylicza składowe RGBA przedstawia się następująco:

Małe wyjaśnienie oznaczeń:
(Rd, Gd, Bd, Ad) - składowe tego co już jest w buforze (destination fragment)
(Rs, Gs, Bs, As) - składowe nowego obiektu (source framgent).

Współczynniki określane w funkcji blendFunc:
(Sr, Sg, Sb, Sa) - source
(Dr, Dg, Db, Da) - destination

I tak dla naszego przykładu składowa R będzie miała następującą postać:
Rresult = Rs * As + Rd

W ten sposób uzyskujemy efekt przezroczystości. Pamiętajmy jednak, że blending nie jest tym samym co przezroczystość. Jest to jedynie sposób (jeden z wielu) na jej osiągnięcie. Idąc dalej:

  gl.enable(gl.BLEND);

Blending jest domyślnie wyłączony dlatego też konieczne jest go włączenie. Co ciekawe konieczne też będzie wyłączenie bufora głębokości:

  gl.disable(gl.DEPTH_TEST);

Jest to konieczne. Ponieważ bez tego nie zadziała nam blending. Jeśli najpierw narysowana zostanie przednia ściana kostki, a potem dopiero tylna to przy włączonym buforze tak naprawdę nie zostanie ona narysowania i nie będzie czego mieszać.

        gl.uniform1f(shaderProgram.alphaUniform, parseFloat(document.getElementById("alpha").value));

Tąinstrukcją pobieramy użytkownika parametr APLHA i przekazujemy do shadera. Robimy tak gdyż nasza tekstura nie posiada tego paramteru. Na koniec wyłączamy blending i uruchamiamy na nowo bufor głębokości.

  } else {
      gl.disable(gl.BLEND);
      gl.enable(gl.DEPTH_TEST);
    }

Jeszcze jedna mała zmiana w fragment shaderze:

  precision mediump float;

  varying vec2 vTextureCoord;
  varying vec3 vLightWeighting;

  uniform float uAlpha;

  uniform sampler2D uSampler;

  void main(void) {
     vec4 textureColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t));
     gl_FragColor = vec4(textureColor.rgb * vLightWeighting, textureColor.a * uAlpha);
  }

To już wszystkie zmiany w kodzie. Tradycyjnie możesz cały kod pobrać przyciskiem poniżej.

Wynik końcowy



Włącz blending
Poziom przezroczystości
Włącz światło

Światło kierunkowe:

Kierunek: X: Y: Z:
Kolor: R: G: B:

Światło otoczenia:

Kolor: R: G: B: