Warsztat.GDCompo!ProjektyMediaArtykułyQ&AForumOferty pracyPobieranie

Opisz napotkaną sytuację, a redakcja niezwłocznie znajdzie rozwiązanie!

wyślij anuluj

Generowanie cieni

Tekst został importowany z Warsztatowych artykułów. Jego oryginalnym autorem jest Gynvael. Jeżeli został importowany poprawnie, usuń ten szablon!

Dość długi czas próbowałem bezskutecznie wymyślić jak zrobić rzeczywisty cień obiektu w OpenGL-u. W końcu, dzięki bezwiednej pomocy ze strony mojej nauczycielki od matematyki, udało mi się takowy stworzyć. Jak? Przeczytaj tego tutorial, a się dowiesz.

Zacząć należało by od stworzenia odpowiedniego projektu, oraz załączenia odpowiednich bibliotek potrzebnych do stworzenia okna (jak to zrobić to każdy powinien wiedzieć, a jeżeli nie, to zapraszam do przeczytania kursu OpenGL-a zamieszczonego w dziale dla początkujących). Teraz musimy stworzyć obiekt, a najlepiej dwa, które będą rzucały cień. Nie wystarczy jednak narysować je na ekranie tak od ręki (tzn. glVertex...), trzeba utworzyć tablice i w niej zapisać koordynaty vertexów obiektu. W tym celu proponuje stworzyć następującego structa:

struct cKoordynaty { float x,y,z; };

Teraz należy się zastanowić co za figury mają rzucać cień. Ja wybrałem sześcian oraz czworościan (taki trójkąt 3D :) ). Teraz trzeba policzyć ile każda z tych figur będzie potrzebowała koordynatów aby ją opisać. W przypadku sześcianu będzie to 24 (6 ścian * 4 vertexy każda), a w przypadku czworościanu 12 (4 ściany * 3 vertexy każda). Tworzymy więc odpowiednie tablice.

cKoordynaty c6[24]; // Koordynatu 6-ścianu cKoordynaty c4[12]; // Koordynaty 4-ścianu

Następnie musimy wklepać (niestety ręcznie) koordynaty figur. Załóżmy że y=0.0f będzie uważane za podłogę, tzn. miejsce na który pada cień, oraz że obiekty będą obok siebie trochę nad podłogą. Załóżmy więc procedure cWgrajKoordynaty w której zapiszemy w tablicach koordynaty.

void cWgrajKoordynaty(void) {

Teraz musimy po kolei wpisywać wszystkie cztery wierzchołki na ścianę. Po prawej stronie, na rysunku, przedstawiłem przykładowe współrzędne punktów. Należy pamiętać że musimy wpisać wszystkie sześć ścian (ABCD, EFGH, ABFE, DCGH, ADHE, BCGF).

/* Szescian */ // Sciana Gorna c6[0].x=1; c6[0].y=4; c6[0].z=-1; c6[1].x=3; c6[1].y=4; c6[1].z=-1; c6[2].x=3; c6[2].y=4; c6[2].z= 1; c6[3].x=1; c6[3].y=4; c6[3].z= 1; // Sciana Dolna c6[4].x=1; c6[4].y=2; c6[4].z=-1; c6[5].x=3; c6[5].y=2; c6[5].z=-1; c6[6].x=3; c6[6].y=2; c6[6].z= 1; c6[7].x=1; c6[7].y=2; c6[7].z= 1; // Sciana Tylna c6[8].x=1; c6[8].y=4; c6[8].z=-1; c6[9].x=3; c6[9].y=4; c6[9].z=-1; c6[10].x=3; c6[10].y=2; c6[10].z=-1; c6[11].x=1; c6[11].y=2; c6[11].z=-1; // Sciana Przednia c6[12].x=1; c6[12].y=4; c6[12].z=1; c6[13].x=3; c6[13].y=4; c6[13].z=1; c6[14].x=3; c6[14].y=2; c6[14].z=1; c6[15].x=1; c6[15].y=2; c6[15].z=1; // Sciana Lewa c6[16].x=1; c6[16].y=4; c6[16].z=-1; c6[17].x=1; c6[17].y=4; c6[17].z=1; c6[18].x=1; c6[18].y=2; c6[18].z=1; c6[19].x=1; c6[19].y=2; c6[19].z=-1; // Sciana Prawa c6[20].x=3; c6[20].y=4; c6[20].z=-1; c6[21].x=3; c6[21].y=4; c6[21].z=1; c6[22].x=3; c6[22].y=2; c6[22].z=1; c6[23].x=3; c6[23].y=2; c6[23].z=-1;

Czas na czworościan.

/* Czworoscian */ // Podstawa c4[0].x=-3; c4[0].y=2; c4[0].z=1; c4[1].x=-2; c4[1].y=2; c4[1].z=-1; c4[2].x=-1; c4[2].y=2; c4[2].z=1; // Sciana Przednia c4[3].x=-2; c4[3].y=4; c4[3].z=0; c4[4].x=-1; c4[4].y=2; c4[4].z=1; c4[5].x=-3; c4[5].y=2; c4[5].z=1; // Sciana Prawa c4[6].x=-2; c4[6].y=4; c4[6].z=0; c4[7].x=-2; c4[7].y=2; c4[7].z=-1; c4[8].x=-1; c4[8].y=2; c4[8].z=1; // Sciana Lewa c4[9].x=-2; c4[9].y=4; c4[9].z=0; c4[10].x=-3; c4[10].y=2; c4[10].z=1; c4[11].x=-2; c4[11].y=2; c4[11].z=-1; }

No to najgorsza część roboty już za nami :). Najlepiej jest jeszcze dopisać wywołanie tej procedury np. do InitGL. Teraz trzeba zrobić prcedurkę która by rysowała oba obiekty z tablic. Nazwijmy ją cRysuj.

void cRysuj() { glBegin(GL_QUADS); for (int cA=0; cA<=23; cA++) glVertex3f(c6[cA].x, c6[cA].y, c6[cA].z); glEnd(); glBegin(GL_TRIANGLES); for (int cB=0; cB<=11; cB++) glVertex3f(c4[cB].x, c4[cB].y, c4[cB].z); glEnd(); }

Jak widać cała sztuczka polega na podawaniu pokolei vertexów z których buduje się kwadraciki (GL_QUADS) z ktrórych złożony jest sześcian, oraz trójkątów (GL_TRIANGLES) potrzebnych do budowy czworościanu.
Nadszedł czas aby zająć się źródłem światła. Bedzie ono się znajdować pomiędzy czworościanem i sześcianem, ale troszeczkę nad nimi. Powiedzmy że w punkcie (0,5,0). Na potrzeby światła musimy stworzyć zmienną swiatlo cKoordynaty swiatlo;

Teraz musimy nadać jej odpowiednie wartości. Proponuje zrobić to w procedurze cWgrajKoordynaty na samym końcu.

/* Swiatlo */ // Koordynaty Swiatla swiatlo.x=0; swiatlo.y=5; swiatlo.z=0;

Przedstawmy nasze swiatełko jako kwadracik (w zasadzie nie jest to potrzebne, ale miło jest wiedzieć gdzie jest źródło światła). W tym celu musimy zrobić sobie prostą procedurkę:

void cRysujSwiatlo() { glBegin(GL_QUADS); glVertex3f(swiatlo.x-0.2f ,swiatlo.y-0.2f, swiatlo.z); glVertex3f(swiatlo.x+0.2f ,swiatlo.y-0.2f, swiatlo.z); glVertex3f(swiatlo.x+0.2f ,swiatlo.y+0.2f, swiatlo.z); glVertex3f(swiatlo.x-0.2f ,swiatlo.y+0.2f, swiatlo.z); glEnd(); }
Procedura ta rysuje kwadracik na płaszczyźnie XY ze środkiem w miejscu gdzie jest źródło światła.
Miło było by również wiedzieć na co cień pada. W tym celu musimy zrobić podłogę :). Stwórzmy sobie zmienną cPodlogaY która będzie nam (czy też programowi) mówiła na jakiej wysokości znajduje się podłoga. float cPodlogaY;

Trzeba również zrobić procedurkę która nam tę podłogę narysuje:

void cRysujPodloge() { glBegin(GL_QUADS); glVertex3f(30,cPodlogaY, 30); glVertex3f(-30,cPodlogaY, 30); glVertex3f(-30,cPodlogaY, -30); glVertex3f(30,cPodlogaY, -30); glEnd(); }

OK. Mamy obiekty, mamy światło, mamy podłogę. Czas na to na co wszyscy czekali :) - cień. Najpierw trochę teorii. Musimy pomyśleć jak taki cień się zachowuje gdy światło jest wysoko nad obiektem, gdy jest nisko, oraz gdy go nie ma bezpośrednio nad obiektem, lecz jest gdzieś z boku. Teraz spróbujmy pomyśleć co to właściwie jest "cień" (dla programisty, nie dla filozofa :) ). Jest to płaski obiekt który powstaje gdy inny obiekt zasłoni źródło światła, jego krawędzie powstają dokładnie w miejcu w którym przecina się linia poprowadzona od światła poprzez krawędzie obiektu zasłaniającego światło do poziomu podłogi. Jak wyliczyć miejsce przecięcia tej linii z podłogą? Z proporcji. Jak to zrobić? Tak (najpierw dla 2D, dodanie trzeciego wymiaru to pestka):

Dane:
światlo.x, światlo.y - pozycja światła
punkt.x, punkt.y - współrzędne punktu dla którego liczymy cień
podłoga.y - wysokość na jakiej jest płaszczyzna na którą pada cień

Zmienne pomocnicze:
delta_p_ś.x, delta_p_ś.y - delta współrzędnych punktu i światła
delta_c_p.y - delta wysokości cienia i punktu

Szukane:
cień.x, cień.y - współrzędne cienia dla punktu
delta_c_p.x - delta współrzędnej X cienia i punktu

Obliczenia:
cień.y=podłoga.y //wkońcu cień pada na podłogę :)

delta_p_ś.x = punkt.x - światło.x // na rysunku X1
delta_p_ś.y = punkt.y - światło.y // na rysunku Y1

delta_c_p.y = cień.y - punkt.y // na rysunku Y2

współczynnik_proporcji = delta_c_p.y / delta_p_ś.y // na rysunku Y2/Y1
delta_c_p.x = delta_p_ś.x * współczynnik_proporcji // jest tak ponieważ zachodzi własność X2/X1=Y2/Y1

cień.x=punkt.x + delta_c_p.x

Analogicznie wyliczamy dla współrzędnej Z (oczywiście po wprowadzeniu trzeciego wymiaru):
cień.z=punkt.z + delta_c_p.z

Podsumujmy nasze obliczenia:
współczynnik_proporcji = (podłoga.y - punkt.y) / (punkt.y - światło.y)
cień.x = punkt.x + (punkt.x - światło.x) * współczynnik_proporcji
cień.y = podłoga.y
cień.z = punkt.z + (punkt.z - światło.z) * współczynnik_proporcji

Proste? Teraz trzeba odpowiednio zastosować to w kodzie. Musimy pamiętać że takie obliczenia muszą być wykonywane dla każdego punktu z tablicy. Stwórzmy więc jeszcze dwie tablice w których będziemy zapisywali wspóżędne cienia.

cKoordynaty cien6[24]; cKoordynaty cien4[12];

Teraz tworzymy procedurę która będzie liczyć gdzie będzie ten cień.

void cLiczCien() { for (int i=0; i<=23; i++) { float cWspolczynnikCienia=((c6[i].y - cPodlogaY) / (swiatlo.y - c6[i].y)); cien6[i].x=c6[i].x+(-(swiatlo.x - c6[i].x)) * cWspolczynnikCienia; cien6[i].y=cPodlogaY; cien6[i].z=c6[i].x+(-(swiatlo.z - c6[i].z)) * cWspolczynnikCienia; } for (i=0; i<=11; i++) { float cWspolczynnikCienia=((c4[i].y - cPodlogaY) / (swiatlo.y - c4[i].y)); cien4[i].x=c4[i].x+(-(swiatlo.x - c4[i].x)) * cWspolczynnikCienia; cien4[i].y=cPodlogaY; cien4[i].z=c4[i].x+(-(swiatlo.z - c4[i].z)) * cWspolczynnikCienia; } }

Trzeba jeszcze oczywiście ten cień narysować:

void cRysujCien() { glBegin(GL_QUADS); for (int cA=0; cA<=23; cA++) glVertex3f(cien6[cA].x, cien6[cA].y, cien6[cA].z); glEnd(); glBegin(GL_TRIANGLES); for (int cB=0; cB<=11; cB++) glVertex3f(cien4[cB].x, cien4[cB].y, cien4[cB].z); glEnd(); }

I.. w zasadzie to już koniec. Wystarczy wrzucić rysowanie wszystkiego w odpowiednią pętle, ustawić kolory, kąty itd. Np.:

int DrawGLScene(GLvoid) { cLiczCien(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0,0,-20); glRotatef(40,1,0,0); glColor3f(0.5f, 0.5f, 0.5f); cRysujPodloge(); glColor3f(0.2f, 0.2f, 0.2f); cRysujCien(); glColor3f(1.0f, 1.0f, 1.0f); cRysujSwiatlo(); glColor3f(0.2f, 0.2f, 1.0f); cRysuj(); return TRUE; }

Aby cała prezentacja cienia wyglądała lepiej możemy na wszystko narzucić odpowiednie teksturki, przezroczystość, oraz umożliwić poruszanie światła np. za pomocom strzałek, lub myszy.

Dołączone do artykułu źródła (cienie.zip, 82 kB) niestety zaginęły...

Autor: Gynvael
Data: 27 kwietnia 2001

Tekst dodał:
Adam Sawicki
28.03.2006 20:58

Ostatnia edycja:
Adam Sawicki
28.03.2006 20:58

Kategorie:

Aby edytować tekst, musisz się zalogować.

# Edytuj Porównaj Czas Autor Rozmiar
#1 edytuj 28.03.2006 20:58 Adam Sawicki 11.37 KB
Zwykły
Do sprawdzenia
Do akceptacji
  • Napisz komentarz:
    Aby dodać swój komentarz, musisz się zalogować.
Licencja Creative Commons

Warsztat używa plików cookies. | Copyright © 2006-2017 Warsztat · Kontakt · Regulamin i polityka prywatności
build #ff080b4740 (Tue Mar 25 11:39:28 CET 2014)