Cieniowanie Gouarauda

Autor: Wojciech Muła
Dodany:31.07.2002
Aktualizacja:9.08.2002

Każdy z wierzchołków charakteryzują następujące parametry:

podział trójkąta

Zanim trójkąt zostanie wyświetlony jest on dzielony na dwie części, w taki sposób jak na obok (odcinek pomiędzy V2 i V2' jest najdłuższym scanlinem w całym trójkącie). Wypełnienie trójkątów V1, V2, V2' oraz V2, V2', V3 jest bardzo proste.

Obliczenie współrzędnych xL i xR oraz kolorów cL, cR wymaga wyłącznie dodawania przyrostów dXdY i dcdY (dXdY = dX/dY, dcdY = dc/dY).

xL = ydXdYL xR = ydXdYR

cL = ydcdYL cR = ydcdYR

Dla każdej linii należy policzyć przyrost dcdX.

scanline

dcdX = (cRcL)/(xRxL)

Po uproszczeniu powyższego równania zmienna y zostanie wyrugowana — współczynnik dcdX jest stały dla całego trójkąta (dla obu połówek!).

float dcdX;

void fill_scanline(int xl, int xr, int y, float cl) {
 while (xl <= xr) {
    putpixel(xl++, y, int(cl));
    cl += dcdX;
   }
}

Ponieważ wartości składowych kolorów są z przedziału 0..255, oraz w każdy niezdegnerowanym trójkącie V2'.x − V2.x > 0 to |dcdX| ≤ 255. Można zatem wartość dcdX zapisać w formacie fixed-point 8:8 i przy użyciu rozkazów MMX obliczać jednocześnie kolory czterech sąsiednich pikseli. Poniżej fragment kodu dla obrazów grayscale.

segment .text
; edi - adres xl
; ecx - szerokość scanlina w pixelach

; mm6 = | cl*4 | cl*3 | cl*2 | cl*1 | -- liczby fixed-point 8:8
; mm7 = |dcdX*4|dcdX*3|dcdX*2|dcdX*1| -- liczby fixed-point 8:8
fill_scanline_mmx:
    push ecx

    movq mm0, mm6     ; kopia robocza kolorów

    shr  ecx, 8   ; liczba 8-bajtowych bloków
    jz   .skip1

  .loop1:
    movq     mm1, mm0
    psrlw    mm1, 8   ; podziel przez 8
    packuswb mm1, mm1 ; spakuj

    movd    [edi], mm1
    add      edi , byte 8

    paddw    mm0, mm7 ; uaktualnij kolory
    loop .loop1

  .skip1:
    pop  ecx

    and  ecx, 0x7
    jz   .skip2

    movq     mm1, [ecx-8+mask]  ; załaduj maskę
    maskmovq mm0, mm1               ; (mm1[i] & 0x80) ? [ds:edi] = mm0[i], i=0..7
  .skip2:
    ret

segment .data

mask:
    db 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; 1
    db 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ; 2
    db 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 ; 3
    db 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 ; 4
    db 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00 ; 5
    db 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00 ; 6
    db 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00 ; 7

Wartość rejestru mm7 jest ustalana na początku procedury wypełniającej trójkąt, z kolei zawartość rejestru mm6 jest obliczana w każdej pętli:

// wartości ustalane na początku pętli:
// mm5 = |4*dcdY_{L}|4*dcdY_{L}|4*dcdY_{L}|4*dcdY_{L}|
// mm6 = | 4*c_{L}  | 3*c_{L}  | 2*c_{L}  | 1*c_{L}  |
for (y=V1.y; y < V2.y; y++)
   {
        asm {
                ; parametry
                call  fill_scanline_mmx

        paddw mm6, mm5
        }
   }

Przy dużych wartościach współczynnika dcdX jego wartość pomnożona przez 2, 3 i 4 nie mieści się w zakresie fixed-point 8:8. Jednakże tak duże wartości występują przy wąskich trójkątach, tj. gdy V'2X − V2X < 4, oraz gdy abs(V'2C − V2C) = 255. Wtedy pola na których występuje przekorczenie zakresu i tak nie są wyświetlane.