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:
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