Speilrefleksjon (specular reflection)
Det siste leddet i likninga:
Totalrefleksjon = ambient refleksjon + diffuse refleksjon + specular refleksjon.
omhandler specular lightning eller speilrefleksjon.
Bruk av ambient og diffuse refleksjon gjør at modeller fremstår med 3D-effekt. Overflaten til modellene vil imidlertid være «matte» (som kalk). I dette kapitlet skal vi se hvordan 3D-modeller også kan ha glatte og skinnende overflater.
Dersom man bruker specular belysning sammen med ambient og diffuse belysning vil ulike deler av modellen kunne bli skinnende (dvs. med annen/lysere farge enn diffus/ambient-fargen). Hvilken del av modellen som skinner vil avhenge av hvilken vinkel modellen betraktes fra, dvs. kameraets plassering er avgjørende.
En rød kule som belyses med hvitt lys vil, avhengig av kameraplassering, få en lys skinnende flekk som oppfattes som om kula er glatt og skinnende.
Eksempel:
En kule med speilbelysning
Når vi bruker speilbelysning vil lyset reflekteres fra modellens overflate på samme måte som lys reflekteres fra et speil. Mestparten av lyset reflekteres i en spesifikk retning og kameraets plassering er derfor avgjørende.
Geometrien for speilrefleksjon kan illustreres slik:
Geometrien for speilrefleksjon
Figuren viser de ulike vektorene som er involvert for å kunne beregne speilrefleksjon. Lysvektoren, l, indikerer retningen til lyskilden. Normalvektoren, n, står vinkelrett på flaten.
Dersom flaten er veldig reflekterende vil mesteparten av lyset reflekteres i retning av refleksjonsvektoren r. Mindre skinnende flater gjør at lyset spres mer rundt vektoren r.
Vektoren v peker i retning av kameraet. Kameraet vil motta mest reflektert lys når vinkelen mellom v og r er null. Ettersom vinkelen mellom v og r øker vil kameraet motta mindre og mindre lys. Denne reduksjonen beregnes slik: max(cosθ, 0)α, dvs. prikkproduktet mellom v og r opphøyd i α, der α indikerer graden av gjenskinn (shininess). Verdien til α påvirker hvor raskt intensiteten avtar etter som vinkelen mellom r og v øker.
En høy verdi på α gir mye refleksjon og betyr at reduksjonen i intensitet vil skje raskere etter som vinkelen mellom r og v øker. Lavere verdi på α indikerer mindre gjenskinn og betyr at reduksjonen vil skje mer gradvis etter som vinkelen mellom r og v øker. Se (Anyuru, A., 2012) for mer informasjon om dette.
Speilrefleksjon beregnes slik:
Refleksjonvektoren r beregnes slik:
Vi slipper imidlertid å beregne denne vektoren manuelt siden OGSL har en innebygd funksjon, reflect(), som beregner denne vektoren.
NB! reflect() funksjonen forutsetter at lysvektoren peker i samme retning som lyset, dvs. i motsatt retning av det som er angitt i figuren over. Når vi bruker reflect() snur vi lysvekteoren ved å sette minus foran.
Eksempel, utsnitt fra verteksshaderen:
. . .
//Beregn refleksjonsvektoren, r, for specular belysning:
vec3 reflectedVector = normalize(reflect(-lightDirection, normalVector));
. . .
//Beregn refleksjonsvektoren, r, for specular belysning:
vec3 reflectedVector = normalize(reflect(-lightDirection, normalVector));
. . .
Her antar vi at lightDirection er vektoren mellom lyskilde og aktuell verteks, beregnet på samme måte som tidligere. Når vi bruker den sammen med reflect() snus retninga ved å sette minus foran.
Komplett verteks- og fragmentshaderene kan nå se slik ut:
<script id="specular-gourad-vertex-shader" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec3 aVertexNormal; //Normalvektor.
uniform mat4 uModelMatrix; //model/world-matrisa brukes til lyskalk.
uniform mat3 uNormalMatrix; //Transformerer normalvektoren vha. denne.
uniform mat4 uModelViewMatrix;
uniform mat4 uProjectionMatrix;
uniform vec3 uCameraPosition; //==> Kameraets posisjon.
uniform vec3 uLightPosition; //Lysets posisjon.
//MERK: FARGENE ER HER GITT SOM vec4
uniform vec4 uAmbientLightColor;
uniform vec4 uDiffuseLightColor;
uniform vec4 uSpecularLightColor;
uniform float uShininess; //Bestemmer hvor skinnende objektet blir. Større verdi=høyere gjennskinn og mer fokusert "flekk".
uniform float uIntensity; //Bestemmer intensiteten på gjennskinn.
varying vec4 vLightWeighting;
void main() {
//Transformer normalvektoren til world-koordinater:
vec3 normalVector = normalize(uNormalMatrix * aVertexNormal);
//Transformer til world-koordinater:
vec4 vertexPositionInWorldCoords = uModelMatrix * vec4(aVertexPosition, 1.0);
vec3 viewDirection = normalize(uCameraPosition - vec3(vertexPositionInWorldCoords));
vec3 lightDirection = normalize(uLightPosition - vec3(vertexPositionInWorldCoords));
// DIFFUST LYS: Beregn prikkprodukt av lysvektor og normalvektor:
float diffuseLightWeightning = max(dot(normalVector, lightDirection), 0.0);
vec4 diffuseReflection = diffuseLightWeightning * uDiffuseLightColor;
// SPECULAR LYS:
vec4 specularReflection;
if (dot(normalVector, lightDirection) < 0.0) {
// Lyskilden er på feil side?
specularReflection = vec4(0.0, 0.0, 0.0, 0.0);// Ingen specular refleksjon.
} else {
vec3 reflectedVector = normalize(reflect(-lightDirection, normalVector));
float cosAngle = max(0.0, dot(reflectedVector, viewDirection));
specularReflection = uIntensity * uSpecularLightColor * pow(cosAngle, uShininess);
}
//Summer alle refleksjonskomponenter og send til fragmentshader:
vLightWeighting = uAmbientLightColor + diffuseReflection + specularReflection;
//Transformer vertex:
gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
}
</script>
Det som har med speilbelysning å gjøre er uthevet. Dersom vinkelen mellom normalvektoren og lysretninga er mindre enn 0 vil ikke lyskilden belyse objektet og det er da heller ingen speilbelysning. Ellers beregnes refleksjonsvektoren vha. reflect() funksjonen som vist over. Basert på dette beregnes så specular reflection.
Legg merke til at vLightWeighting utgjør summen av ambient, diffuse og specular refleksjon. vLightWeighting er en varying og vil interpoleres på veien fra verteksshaderen til fragmentshaderen. I fragmentshaderen defineres en tilsvarende varying. Komplett fragmentshader ser slik ut:
<script id="specular-gourad-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 vLightWeighting;
void main() {
gl_FragColor = vLightWeighting;
}
</script>
Dette kalles Gourad shading siden lysberegninga utføres av verteks-shaderen. Alternativet er å videresende normalvektoren som en varying til fragmentshaderen slik at vi får en interpolert normalvektor per fragment. Resterende lysparametre sendes som uniforms direkte til fragmentshaderen. Fragmentshaderen kan nå gjøre lysberegning per fragment. Dette kalles Phong shading. Se mer info. lenger ned.
Du finner komplett kode blant kodeeksemplene.
Belysning av kulemodell.
Dersom vi har en kulemodell med tilhørende vertekser og normalvektorer kan vi gi denne farge kun ved bruk av lysparametrene som vist i foregående eksempler. Det er relativt greit å «håndkode» alle vertekser og trekanter som trengs for å lage en kule. Vi må også beregne normalvektor per verteks. Alternativt kan man lage en kule vha. et 3D-verktøy og få verktøyet til å gjøre beregningene normalvektorene. Se eget avsnitt om Blender og threejs lenger ned.
Normalvektoren for verteksene til en kule er enkelt å "beregne". Denne vil, for hver verteks, være en vektor som peker fra origo til verteksen.
Du finner komplett kode blant kodeeksemplene. Resultatet blir f.eks. som vist i figuren under.
Her er «shininess» parametret i shaderen satt lik 2.
Ingen kommentarer:
Legg inn en kommentar