A háromdimenziós grafikus színterekben egy pixel színét az árnyalás és az árnyékolás együttes hatása alakítja ki. Eddigi OpenGL példáinkban csak az árnyalással foglalkoztunk. Az árnyalás a tárgyak képét alkotó pixelek színének kiszámítása a fényforrások és az objektum felületi színjellemzőinek figyelembevételével. Az árnyékolás alatt a tárgyak által a színtér más objektumaira vetett árnyékainak modellezését értjük. Ezt az OpenGL alapvetően nem támogatja. Ezért magunknak kell kidolgozni a szükséges eljárásokat. Az árnyékszámítás egy nagyon érdekes terület a számítógépes grafikán belül. Sokféle megközelítés létezik:
- Vetített sík-árnyékok: síkfelületek esetén működik megfelelően
- Fénytérképek: csak statikus színtér esetén alkalmazható
- Árnyéktestek: az árnyéktestek kiszámítása költséges feladat
- Árnyéktérképek: rögzített pontosság miatt pontatlanságok
Az l fényforrást a p ponttal összekötő egyenes egyenlete:
Az összefüggést a sík egyenletébe helyettesítve kifejezhetjük a metszéspontot, azaz a p vetített p' képének megfelelő ? értéket:
Az alfát visszahelyettesítve megkapjuk a p' síkra vetített pontot:amelyet a következő alakban is felírhatunk
ahol gamma a fényforrás-sík távolság
Az egyenlet mindkét oldalát h-val szorozva, majd átrendezve kapjuk:
Vegyük észre, hogy p'h és h a p lineáris függvénye, így a leképezés egy projektív transzformációval is leírható. Ezt a projektív transzformációt a Tshadow 4×4-es mátrixszal szorozva végezzük el:
ahol az objektum az S síkra vetítő árnyék mátrix:
A vetítés után kapott felületi pont Descartes-koordinátáit homogén osztással számíthatjuk ki. Ha a síkra vetített, azaz kilapult objektumot sötét színnel jelenítjük meg, akkor olyan hatást érünk el, mintha az objektum árnyékát is kiszámoltuk volna. Ennél a módszernél tehát az árnyékokkal együttes megjelenítéshez minden objektumot kétszer kell megjeleníteni. Egyszer vetítés nélkül egyszer pedig vetítve, árnyékként. Nagy modellek esetén ez nem túl jó megoldás.
A megvalósításhoz szükséges két metódus pedig a következő:
a végeredmény pedig a lenti képen látható. Valósidőben még látványosabb a dolog, az egérrel dönthető a sík és forgatható a teáskanna, a kurzor billentyűkkel pedig a fényforrás pozíciója állítható.
A vetítés után kapott felületi pont Descartes-koordinátáit homogén osztással számíthatjuk ki. Ha a síkra vetített, azaz kilapult objektumot sötét színnel jelenítjük meg, akkor olyan hatást érünk el, mintha az objektum árnyékát is kiszámoltuk volna. Ennél a módszernél tehát az árnyékokkal együttes megjelenítéshez minden objektumot kétszer kell megjeleníteni. Egyszer vetítés nélkül egyszer pedig vetítve, árnyékként. Nagy modellek esetén ez nem túl jó megoldás.
A megvalósításhoz szükséges két metódus pedig a következő:
private float[] buildShadowMatrix(float[] lgtPosition, float[] plane)
{
float dotp;
float[] matrix = new float[16];
// Calculate the dot-product between the plane and the light's position
dotp = plane[0] * lgtPosition[0] +
plane[1] * lgtPosition[1] +
plane[1] * lgtPosition[2] +
plane[3] * lgtPosition[3];
// First column
matrix[00] = dotp - lgtPosition[0] * plane[0];
matrix[04] = 0.0f - lgtPosition[0] * plane[1];
matrix[08] = 0.0f - lgtPosition[0] * plane[2];
matrix[12] = 0.0f - lgtPosition[0] * plane[3];
// Second column
matrix[01] = 0.0f - lgtPosition[1] * plane[0];
matrix[05] = dotp - lgtPosition[1] * plane[1];
matrix[09] = 0.0f - lgtPosition[1] * plane[2];
matrix[13] = 0.0f - lgtPosition[1] * plane[3];
// Third column
matrix[02] = 0.0f - lgtPosition[2] * plane[0];
matrix[06] = 0.0f - lgtPosition[2] * plane[1];
matrix[10] = dotp - lgtPosition[2] * plane[2];
matrix[14] = 0.0f - lgtPosition[2] * plane[3];
// Fourth column
matrix[03] = 0.0f - lgtPosition[3] * plane[0];
matrix[07] = 0.0f - lgtPosition[3] * plane[1];
matrix[11] = 0.0f - lgtPosition[3] * plane[2];
matrix[15] = dotp - lgtPosition[3] * plane[3];
return (matrix);
}
A fenti eljárás feltölti a Shadow Mátrix-ot az elméletnek megfelelően. A következő eljárás pedig a sík együtthatóit adja vissza (A, B, C, D): // Desc: find the plane equation given 3 points
private void findPlane(float[] v0, float[] v1, float[] v2, ref float[] plane)
{
float[] vec0 = new float[3];
float[] vec1 = new float[3];
// Need 2 vectors to find cross product
vec0[0] = v1[0] - v0[0];
vec0[1] = v1[1] - v0[1];
vec0[2] = v1[2] - v0[2];
vec1[0] = v2[0] - v0[0];
vec1[1] = v2[1] - v0[1];
vec1[2] = v2[2] - v0[2];
// Find cross product to get A, B, and C of plane equation
plane[0] = +(vec0[1] * vec1[2] - vec0[2] * vec1[1]);
plane[1] = -(vec0[0] * vec1[2] - vec0[2] * vec1[0]);
plane[2] = +(vec0[0] * vec1[1] - vec0[1] * vec1[0]);
plane[3] = -(plane[0] * v0[0] + plane[1] * v0[1] + plane[2] * v0[2]);
}
A virtuális színtér kirajzolásának menete a következő: private void Render()
{
//
// Define the plane of the planar surface that we want to cast a shadow on...
//
float[] shadowPlane = new float[4];
float[] v0 = new float[3];
float[] v1 = new float[3];
float[] v2 = new float[3];
// To define a plane that matches the floor, we need to 3 vertices from it
v0[0] = floor[0].vX;
v0[1] = floor[0].vY;
v0[2] = floor[0].vZ;
v1[0] = floor[1].vX;
v1[1] = floor[1].vY;
v1[2] = floor[1].vZ;
v2[0] = floor[2].vX;
v2[1] = floor[2].vY;
v2[2] = floor[2].vZ;
findPlane(v0, v1, v2, ref shadowPlane);
//
// Build a shadow matrix using the light's current position and the plane
//
shadowMatrix = buildShadowMatrix(lgtPosition, shadowPlane);
// A képernyő és a mélység puffer ürítése
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
//
// Place the view
//
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glLoadIdentity();
Gl.glTranslatef(0.0f, -2.0f, -15.0f);
Gl.glRotatef(-spinViewXY[1], 1.0f, 0.0f, 0.0f);
Gl.glRotatef(-spinViewXY[0], 0.0f, 1.0f, 0.0f);
//
// Render the floor...
//
DrawFloor();
//
// Create a shadow by rendering the teapot using the shadow matrix.
//
Gl.glDisable(Gl.GL_DEPTH_TEST);
Gl.glDisable(Gl.GL_LIGHTING);
Gl.glColor3f(0.2f, 0.2f, 0.2f); // Shadow's color
Gl.glPushMatrix();
{
Gl.glMultMatrixf(shadowMatrix);
// Teapot's position & orientation (needs to use the same transformations used to render the actual teapot)
Gl.glTranslatef(0.0f, 2.5f, 0.0f);
Gl.glRotatef(-spinTeapot[1], 1.0f, 0.0f, 0.0f);
Gl.glRotatef(-spinTeapot[0], 0.0f, 1.0f, 0.0f);
Glut.glutSolidTeapot(1.0d);
}
Gl.glPopMatrix();
Gl.glEnable(Gl.GL_DEPTH_TEST);
Gl.glEnable(Gl.GL_LIGHTING);
//
// Render the light's position as a sphere...
//
Gl.glDisable(Gl.GL_LIGHTING);
Gl.glPushMatrix();
{
// Place the light...
Gl.glLightfv(Gl.GL_LIGHT0, Gl.GL_POSITION, lgtPosition);
// Place a sphere to represent the light
Gl.glTranslatef(lgtPosition[0], lgtPosition[1], lgtPosition[2]);
Gl.glColor3f(1.0f, 1.0f, 0.5f);
Glut.glutSolidSphere(0.1, 8, 8);
}
Gl.glPopMatrix();
Gl.glEnable(Gl.GL_LIGHTING);
//
// Render normal teapot
//
Gl.glPushMatrix();
{
// Teapot's position & orientation
Gl.glTranslatef(0.0f, 2.5f, 0.0f);
Gl.glRotatef(-spinTeapot[1], 1.0f, 0.0f, 0.0f);
Gl.glRotatef(-spinTeapot[0], 0.0f, 1.0f, 0.0f);
Glut.glutSolidTeapot(1.0d);
}
Gl.glPopMatrix();
//A grafikus csővezeték ürítése
Gl.glFlush();
}
private void DrawFloor()
{
Gl.glColor3f(1.0f, 1.0f, 1.0f);
Gl.glInterleavedArrays(Gl.GL_N3F_V3F, Vertex.Stride, floor);
Gl.glDrawArrays(Gl.GL_QUADS, 0, 4);
}
További részletek megtalálhatóak a forráskódban. A teljes forrás pedig letölthető innen:a végeredmény pedig a lenti képen látható. Valósidőben még látványosabb a dolog, az egérrel dönthető a sík és forgatható a teáskanna, a kurzor billentyűkkel pedig a fényforrás pozíciója állítható.











És mi a helyzet ha nem egy plane-re akarok shadowot rakni, hanem egy terrainre? vagy egy gömb árnyékát rávetíteni egy másik gömbre?
VálaszTörlésKedves blogger a síkra vetített árnyékok módszere rendkívül régi és csak speciális esetekben működik. A te kérdésedre válaszolva a Shadow Mappin és Shadow Volume módszereket kellene megismerned. Sajnos vagy szerencsére az árnyékvetés kérdése nem egyértelmű, így sokféle megközelítés létezik.
VálaszTörlésLink:
http://www.codesampler.com/oglsrc/oglsrc_8.htm#ogl_shadow_volume
http://sakura7.blog.hu/2010/02/24/shadow_map_ismet