Amint az elnevezés is tükrözi, az alapjait egy Phong nevű illető rakta le. Egyébként azt hiszem ázsiai volt, és a Phong csak a kiejtés szerinti neve. Na de inkább nézzük a tényeket.

A Phong Shading lényege hogy a fényerősség minden egyes pontban kiszámítható:

L: Beeső fény
R: Visszavert fény
N: Normálvektor (felületre merőleges)
V: Nézőpont

Phong összefüggése szerint: C=Kd*Cos(a)+Ks*Cos(c)n

Ahol: 

C: a pont fény erje
Kd: az anyagra jellemző visszaverődési tényező
Ks: az anyagra jellemző fényszórási tényező
n: tetszőleges kitevő
 

a: A Beeső fény és a normálvektor által bezárt szög. Természetesen egyenlő a visszavert fény, és a normálvektor által bezárt szöggel (b).
c: A nézőpont vektora, és a visszavert fény által bezárt szög

Ha segítségül hívjuk a skaláris szorzatot, ami ugye a négyjegyű függvénytáblában is benne van, akkor:

Cos(a)=L*N/(|L|*|N|), és Cos(c)=R*V/(|R|*|V|)

N,L,R,V 3D-s vektorok, |N|,|R|,|V|,|L|, pedig a hosszuk.

Ennek megfelelően:

|N|=sqrt(N.x*N.x+N.y*N.y+N.z*N.z)
|V|=sqrt(V.x*V.x+V.y*V.y+V.z*V.z)

és így tovább..

Ezekkel a gyökös kifejezésekkel kellene osztani.

Ezt realtime számolgatni nem kellemes dolog. Ezért a vektorokat egységnyi hosszúságúnak hozzuk létre. Az egységgel való osztás ugye pedig igen gyors művelet, mert nem kell elvégezni. Tehát két egységvektor közti szög koszinuszát egyszerűen a két vektor szorzatával meg tudjuk kapni. Dot Productnak is nevezik, ezért a továbbiakban dot-tal jelölöm.

Így a képletet egyszerűsödik:

C=Kd*(N dot L)+Ks*(R dot V)n

A realtime számoláshoz ez még mindig bonyolult

Ezért a nézőpontot a fényforrásnak tekintjük, így a (V dot R) hasonló lesz (N dot L)-hez. 

Ezért:

C=Kd*(N dot L)+Ks*(N dot L)n

Avagy:

C=Kd*Cos(a)+Ks*Cos(a)n

Ez a két egyenlőség már használhatóbb. A Phong shading-get sokféleképpen lehet csinálni, itt is két út áll előttünk. A normálvektort számoljuk pontról pontra, vagy a fény beesési szögét. Általában az előbbit szokták használni, ezen megyünk tovább. Ennek is sokféle variációja van, egyet megnézünk.

Tehát felmerül a kérdés, hogyan lehet minden pontban normálvektort számolni. Mert matematikailag ugye a pontnak nincsen normálvektora. Nyilván való, hogy nem minden térbeli pontban számolunk vektort, hanem minden képernyőpontban, pixelenként. Egy pixel által reprezentált rész a 3D-s modellben több térbeli pontot, egy nagyon kicsi síkot jelent. Annak pedig már lenne normálvektora. De ugye ezek a normálvektorok az egész síkon ugyanazok lennének, mivel egy sík normálvektora minden pontban azonos. Tehát ezek a számolgatott vektorok csak odaképzeltek, valójában akkor léteznének, ha a sík kicsit gömbölyödne.

Ezeket a vektorokat a sarkok normálvektorainak segítségével közelítjük. 

Egy sarokpont normálvektorát a körülötte lévő síkok normálvektorainak átlagaként kapjuk meg:

Tehát: 
Np1=(N1+N2+N3+N4)/4
Np2=(N2+N1+N5+N6)/4

És így tovább…

Az átlgolást minden sarokpontra ejátszuk, így megvan minden síkra a sarokpontjainak a normálvektora. 
A sík belsejében a normálvektorok kiszámolása:
 
 

Tehát először a szélekre kell kiszámolni a vektorokat, hogy a scanline-on belül a scanline két végén ismert értékekkel közelíthessünk. A széleken lévő vektorokat pedig a sarokpontok segítségével közelítjük. Egy ilyen közelített vektor az ábrán az Ne. Kiszámolása az ábra szerint:

Ne=Np4+(e.y-p4.y)*(Np1-Np4)/(p1.y-p4.y)

Ahol:
 

e.y: a szélen lévő pont y képernyő-koordinátája
p1.y: az 1. sarokpont y képernyő-koordinátája
p4.y: a 4. sarokpont y képernyő-koordinátája

A külön x és y vektorösszetevőkre:

Ne.x=Np4.x+(e.y-p4.y)*(Np1.x-Np4.x)/(p1.y-p4.y)
Ne.y=Np4.y+(e.y-p4.y)*(Np1.y-Np4.y)/(p1.y-p4.y)

A z irányú összetevőt felesleges számolnuk a később ismertetett okok miatt. Assembly-ben ezt fel lehet gyorsítani, ha lépésközöket számolunk, ugyanúgy, mint texture maping-nél.

Stepx=(Np1.x-Np4.x)/(p1.y-p4.y)
Stepy=(Np1.y-Np4.y)/(p1.y-p4.y)

Nincs más dolgunk, mint pontról pontra ezeket az értékeket adogatva hozzá az előzőhez megkapjuk a normálvektort.

Ne2.x=Ne1.x+StepX
Ne2.y=Ne1.Y+StepY

Stb.

Tehát egy vektor közelítése a sík szélén két összeadással helyettesíthető. Ezért Lineáris Interpolációnak nevezzük. Ezeket a közelített értékeket egy tömbben eltároljuk, és a scnaline rajzolásánánál csak innen kell elővennünk. A scanline-on belül a közelítés szintén lineáris interpolációval történik. Ismerjük a két végpontot, csak a közti vektorokat kell meghatározni.

Ns=Ne0+(s.x-e0.x)*(Ne1-Ne0)/(e1.x-e0.x)

Ahol:
 
 

Ns: az illető pont normálvektora
Ne0: normálvektor a scanline kezdőpontján
Ne1: normálvektor a scanline végpontján
s.x: x koordináta az illető pontban
e0.x: x koordináta a scanline elején
e1.x: x koordináta a scanline végén

x és y vektorösszetevőkre:

Ns.x=Ne0.x+(s.x-e0.x)*(Ne1.x-Ne0.x)/(e1.x-e0.x)
Ns.y=Ne0.y+(s.x-e0.x)*(Ne1.y-Ne0.y)/(e1.x-e0.x)

A z összetevőt itt sem számoljuk

És itt is lépésközök számolhatóak.

StepX=(Ne1.x-Ne0.x)/(e1.x-e0.x)
StepY=(Ne1.y-Ne0.y)/(e1.x-e0.x)

Tehát ugyanúgy Lineárisan interpolálunk, mint a mezei texture maping-ben, csak itt tulajdonképpen vektorokkal csináljuk, nem textúra-koordinátákkal. Elvileg!

Gyakorlatilag:

Említettem, és remélem fel is tűnt, hogy habár a vektorok 3D-sek, tehát van x,y,z összetevőjük, csak az x és y összetevőt számoljuk.

Mert a tudjuk, a vektoraink mindenhol egységnyi hosszúak, tehát:

1=sqrt(z*z+y*y+x*x)

tehát:

z=sqrt(1-x*x-y*y)

Az x és az y már meghatározza a z-t. És ha ez így van, írjuk vissza az egyenletbe. Az egyenlet 3 vektorösszetevővel:

C=Kd*(Nx*Lx+Ny*Ly+Nz*Lz)+Ks*(Nx*Lx+Ny*Ly+Nz*Lz)n

Ha a fényt pont szemben irányítjuk, akkor:

Lx=0, Ly=0, Lz=1
C=Kd*(Nz)+Ks*(Nz)n

Az z meghatározható x és y függvényében:

C=Kd*sqrt(1-Nx*Nx-Ny*Ny)+Ks*sqrt(1-Nx*Nx-Ny*Ny) n

Tehát a fényerő meghatározható a pontbeli normálvektor x és y összetevője segítségével. Ezt persze nem realtime fogjuk számolgatni, eltároljuk az értékeket egy mátrixban. (LookUp Table) Így ugyanott tartunk, mint egy texture mapping-nél. A két koordináta meghatároz a táblázatban egy pontot, ami a pont színét tartalmazza. Tehát egy texture maping rutint kell egy kicsit átírni, és meg is van.

Egy pár gyakorlati buktató:

A normálvektorok hossza nem lehet 1, hiszen az összetevői 1-nél kisebbek lennének. Mi meg nem fogunk lebegőpontosan számolni. Ezért írtam többhelyen 1 helyett egységnyit. Azt jelenti, hogy mondjuk a vektorok összetevőit 65536-al felszorozva tároljuk, így a hossza is ennyi lesz. Tehát az egység hosszúság metematikailag 65536 lesz. 

Aztán az sem biztos, hogy az így felszorzott normálvektor koordináta pont a Lookup Table szerinti koordinátát fog tartalmazni. Ha 65536-al szoroztunk fel, Hozzá kell adni 65536-t, mert maximum ennyi lehet mínuszban egy összetevő értéke. Ezt pedig le kell osztani annyival, hogy a lookup table-be essen.

Pl. a programban 128*128-as a táblázat, ezért 4-el kell osztani a normálvektor összetevőit, hogy jó legyen. Mert 2*65536/4=128*256 256-al felszorzott értéket kapunk, az asm rutin pedig ilyet használ.

Meg a többire majd rájön magától, aki próbál ilyet csinálni.

A program pascal és assembly. Az asm rutin majdnem ugyanaz, mint a texture maping, ezért nem igényel különösebb magyarázatot. Az objektumot úgy 'vettem kölcsön' (loptam), de szép.