Kod

Tko želi detaljnije saznati kako konkretno stvaranje sjena izgleda, svakako će naći ono što traži ovdje. Kod ne bi smio biti težak za razumijevanje nikome tko se već susreo s Open GL-om.

Početak je standardan. Potrebno je uključiti zaglavlja koja će se koristiti u programu...

#include <stdio.h>
#include <stdlib.h>
#define _USE_MATH_DEFINES
#include <math3d.h>
#include <GL/glut.h>
#include <GL/glext.h>
#include <ctime>

... a nakon toga deklariraju se globalne varijable.

Možda i najvažnije od svega jest postaviti poziciju kamere i svjetla.

//Pozicije kamere i svjetla
float camx = 2.1f, camy = 7.0f, camz = 5.0f;
float cameraPosition[] = {camx, camy, camz};
float lightPosition[] = {-3.0f, 5.8f, 1.0f};
float center[] = {0.0f, 2.0f, 0.0f};

Varijable camx, camy i camz postavljaju se na neke početne vrijednosti, a kasnije se tipkama te vrijednosti mogu mijenjati, čime se dobiva pomak kamere. Polje lightPosition sadrži koordinate izvora svijetla. Razlog zašto se tu koristi samo polje, dok je za kameru potrebno i polje i posebno definirane varijable jest taj što to kasnije omogućava prebacivanje iz pogleda kamere u pogled svjetla na način da se ne zaboravi gdje je kamera bila prije, pa se moguće vratiti u prijašnji položaj kamere jednom tikpom (l). Polje center sadrži položaj fokusa kamere i svjetla.

Sljedeće što se deklarira i definira jesu osnovne boje koje će se koristiti kod osvjetljavanja.

//Boje
float bijelo[] = {1.0f, 1.0f, 1.0f, 1.0f};
float tamno[] = {0.2f, 0.2f, 0.2f, 1.0f};
float crno[] = {0.0f, 0.0f, 0.0f, 1.0f};

Nakon toga slijedi i deklaracija polja za slučajno generiranje objekata.

//Polja za slucajno generiranje objekata
int ro[15], rx[15], rz[15], rr[15], rv[15], rcr[15], rcg[15], rcb[15];

Polja su veličine 15 zato što će biti kreirano 15 slučajno odabranih objekata. U polju ro određuje se koji će se objekt iscrtati, rx i rz su koordinate objekta u ravnini površine svijeta, rr i rv su polumjer i visine objekata, dok rcr, rcg i rcb određuju boju svakog objekta.

Slijede deklaracija i definicija varijabilnih postavki čovječuljka.

//Proporcije lika
double prop = 0.7;

//Kretanje lika
double nn = 0.0, ld = 0.0;
double okret = 0.0;

//Duzina vrata
double duzina = 0.0;

Varijabla prop određuje veličinu, nn i ld su pomaci po osima x i z, okret je smjer u kojem će čovječuljak biti okrenut, dok duzina označava duljinu vrata, odnosno visinu na kojoj će se nalaziti glava.

//Metode rjesavanja akni
int cul = 1, cor = 1;

Nakon postavki čovječuljka, postavljaju se i indikatori rješavanja problema akni sjena. Kad cul ima vrijednost 1, uključuje se selektivno odbacivanje prednje strane pri generiranju pogleda svjetlosti, dok se kad cor ima vrijednost 1 uključuje i odmak kojim se također uklanjaju akne.

//Velicina mape sjene
const int shadowMapSize = 512;

//Texture
GLuint shadowMapTexture;

//Velicina prozora
int windowWidth, windowHeight;

//Matrice
M3DMatrix44f lightProjectionMatrix, lightViewMatrix;
M3DMatrix44f cameraProjectionMatrix, cameraViewMatrix;
M3DMatrix44f textureMatrix;

Ovdje se najprije postavlja veličina mape sjene. Što je ona veća, bit će manja pikselizacija sjena, no nauštrb performansi, a ni u samom izgledu ništa se ne dobiva povećanjem koje nadmašuje dimenzije vidokruga stvorenog virutalnog svijeta. Nakon toga slijedi deklaracija potrebnih varijabli za teksture, dimenzije prozora i matrice koje će se koristiti za transformaciju pogleda svjetla u pogled kamere.

Time je završeno postavljanje globalnih varijabli, sad još nedostaju metode koje će se koristiti.

//Pretvaranje u stupnjeve i radijane
double toRad(double stup) {
    return stup * M_PI / 180;
}

double toDeg(double rad) {
    return rad * 180 / M_PI;
}

//Normiranje vektora
void normiraj(double *v) {
    double d = sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    v[0] /= d;
    v[1] /= d;
    v[2] /= d;
}

//Izracun vektorskog produkta
void vprodukt(double *v, double a0, double a1, double a2,
        double b0, double b1, double b2) {
    v[0] = a1 * b2 - a2 * b1;
    v[1] = a2 * b0 - a0 * b2;
    v[2] = a0 * b1 - a1 * b0;
}

Prve dvije metode koriste se za pretvaranje stupnjeva u radijane i obrnuto, dok se preostale dvije bave vektorima koji će se koristiti kod postavljanja osvjetljenja pojedinog tijela.

//Kreiranje kocke
void kocka(double a) {
    glPushMatrix();
      glTranslated(-a / 2, -a / 2, -a / 2);
      glBegin(GL_QUADS);
        glNormal3d(0.0, 0.0, -1.0);
        glVertex3d(0.0, 0.0, 0.0);
        glVertex3d(0.0, a, 0.0);
        glVertex3d(a, a, 0.0);
        glVertex3d(a, 0.0, 0.0);

        glNormal3d(0.0, 0.0, 1.0);
        glVertex3d(0.0, 0.0, a);
        glVertex3d(a, 0.0, a);
        glVertex3d(a, a, a);
        glVertex3d(0.0, a, a);
      glEnd();

      glBegin(GL_QUAD_STRIP);
        glNormal3d(0.0, -1.0, 0.0);
        glVertex3d(0.0, 0.0, a);
        glVertex3d(0.0, 0.0, 0.0);
        glVertex3d(a, 0.0, a);
        glVertex3d(a, 0.0, 0.0);

        glNormal3d(1.0, 0.0, 0.0);
        glVertex3d(a, a, a);
        glVertex3d(a, a, 0.0);

        glNormal3d(0.0, 1.0, 0.0);
        glVertex3d(0.0, a, a);
        glVertex3d(0.0, a, 0.0);

        glNormal3d(-1.0, 0.0, 0.0);
        glVertex3d(0.0, 0.0, a);
        glVertex3d(0.0, 0.0, 0.0);
      glEnd();
    glPopMatrix();
}


//Kreiranje stosca - koristi se ugradjena metoda zbog kompliciranog postavljanja
// normala
void stozac(double r, double h, int n) {
    glPushMatrix();
      glRotated(-90.0, 1.0, 0.0, 0.0);
      glutSolidCone(r, h, n, n);
    glPopMatrix();
}

//Kreiranje valjka
void valjak(double r, double h, int n) {
    int i;
    double pomkut = 360.0 / n;

    glBegin(GL_TRIANGLE_FAN);
      glNormal3d(0.0, 1.0, 0.0);
      glVertex3d(0.0, h, 0.0);
      for (i = 0; i <= n; i++) {
        glVertex3d(r * sin(toRad(i * pomkut)), h, r * cos(toRad(i * pomkut)));
      }
    glEnd();

    glBegin(GL_TRIANGLE_FAN);
      glNormal3d(0.0, -1.0, 0.0);
      glVertex3d(0.0, 0.0, 0.0);
      for (i = 0; i <= n; i++) {
        glVertex3d(r * sin(toRad(-i * pomkut)), 0.0, r * cos(toRad(-i * pomkut)));
      }
    glEnd();

    glBegin(GL_QUAD_STRIP);
      for (i = 0; i <= n; i++) {
        double vv[3];
        vv[0] = r * sin(toRad(-i * pomkut));
        vv[1] = 0.0;
        vv[2] = r * cos(toRad(-i * pomkut));

        normiraj(vv);
        glNormal3dv(vv);
        glVertex3d(r * sin(toRad(-i * pomkut)), 0.0, r * cos(toRad(-i * pomkut)));
        glVertex3d(r * sin(toRad(-i * pomkut)), h, r * cos(toRad(-i * pomkut)));
      }
    glEnd();

}

//Kreiranje kugle, odnosno sfere
void kugla(double r, int m, int n, double k) {
    double pomi = 180.0 / n, pomj = 360.0 / m, vvv[3];
    int i, j;
    for (i = 0; i < n; i += 1) {
        glBegin(GL_QUAD_STRIP);
          for (j = 0; j <= m * k; j += 1) {
            if (j > m / 2.0) {
                vvv[0] = r * cos(toRad((i + 1) * pomi)) * sin(toRad(j * pomj));
                vvv[1] = r * cos(toRad(j * pomj));
                vvv[2] = r * sin(toRad((i + 1) * pomi)) * sin(toRad(j * pomj));
                normiraj(vvv);
                glNormal3dv(vvv);
                glVertex3d(r * cos(toRad((i + 1) * pomi)) * sin(toRad(j * pomj)), 
                           r * cos(toRad(j * pomj)), 
                           r * sin(toRad((i + 1) * pomi)) * sin(toRad(j * pomj)));
                vvv[0] = r * cos(toRad(i * pomi)) * sin(toRad(j * pomj));
                vvv[1] = r * cos(toRad(j * pomj));
                vvv[2] = r * sin(toRad(i * pomi)) * sin(toRad(j * pomj));
                normiraj(vvv);
                glNormal3dv(vvv);
                glVertex3d(r * cos(toRad(i * pomi)) * sin(toRad(j * pomj)), 
                           r * cos(toRad(j * pomj)), 
                           r * sin(toRad(i * pomi)) * sin(toRad(j * pomj)));
            }
            else {
                vvv[0] = r * cos(toRad(i * pomi)) * sin(toRad(j * pomj));
                vvv[1] = r * cos(toRad(j * pomj));
                vvv[2] = r * sin(toRad(i * pomi)) * sin(toRad(j * pomj));
                normiraj(vvv);
                glNormal3dv(vvv);
                glVertex3d(r * cos(toRad(i * pomi)) * sin(toRad(j * pomj)), 
                           r * cos(toRad(j * pomj)), 
                           r * sin(toRad(i * pomi)) * sin(toRad(j * pomj)));
                vvv[0] = r * cos(toRad((i + 1) * pomi)) * sin(toRad(j * pomj));
                vvv[1] = r * cos(toRad(j * pomj));
                vvv[2] = r * sin(toRad((i + 1) * pomi)) * sin(toRad(j * pomj));
                normiraj(vvv);
                glNormal3dv(vvv);
                glVertex3d(r * cos(toRad((i + 1) * pomi)) * sin(toRad(j * pomj)), 
                           r * cos(toRad(j * pomj)), 
                           r * sin(toRad((i + 1) * pomi)) * sin(toRad(j * pomj)));
            }
          }
        glEnd();
    }
}

Slijede metode kojima se iscrtavaju tijela. Sve se izvode "pješke", tj. posebno definiranim izračunom, dok se jedino za stožac koristi ugrađena metoda. Razlog toga jest problem s pravilinim odabirom i postavljanjem normala, zbog čega se osvjetljenje stožaca u nekim situacijama nije adekvatno ponašalo. Ugrađena metoda ima to dobro definirano, te je odlučeno kako će se upravo ona iskoristiti u ovoj prigodi.

//Kreiranje objekata
void objekt(void) {
				(...)
}

Metoda objekt vrlo je važna, jer se u njoj kreiraju objekti, odnosno bolje rečeno čitav svijet.

    //Podloga
    glPushMatrix();
      glTranslated(0.0, -0.5, 0.0);
      glScaled(1.0, 0.01, 1.0);
      glColor3d(0.2, 0.9, 0.3);
      kocka(100.0);
    glPopMatrix();

Prvo što se iscrtava jest podloga. Riječ je zapravo o običnoj spljoštenoj kocki, tj. o pravokutniku.

    //Slucajni objekti u okolini
    int i;
    for (i = 0; i <= 15; i++) {
        glPushMatrix();
          glTranslated((float) (rx[i] - 10), -0.1, (float) (rz[i]));
          glColor3d((double) (rcr[i] / 10.0), (double) (rcg[i] / 10.0), (double) (rcb[i] / 10.0));
          if (ro[i] == 0) {
            stozac((float) rr[i], (float) rv[i], 24);
          } else {
            valjak((float) rr[i], (float) rv[i], 24);
          }
        glPopMatrix();
    }

Nakon podloge, na njoj se crtaju slučajno odabrani objekti slučajnih dimenzija i slučajnih boja.

    //Covjeculjak
    glPushMatrix();
				(...)
    glPopMatrix();

Na red zatim dolazi čovječuljak.

      //Skaliranje lika
      glScaled(prop, prop, prop);

Prvo što se izvodi jest skaliranje, tj. primjenjivanje odabrane veličine čovječuljka.

      //Kretanje
      glTranslated(-ld, 0.0, -nn);
      glRotated(okret, 0.0, 1.0, 0.0);

Nakon skaliranja, izvršava se kretanje. Radi se o translaciji i o zakretanju koje će se nakon toga primijeniti na svim dijelovima kojima se čovječuljak iscrtava.

      //Tijelo
      glPushMatrix();
        glTranslated(0.0, 0.3, 0.0);
        glColor3d(0.8, 0.4, 0.3);
        valjak(0.5, 2.0, 40);
      glPopMatrix();

Tijelo je valjak koji je malo podignut od površine

      //kotaci i ruke
      glPushMatrix();

        glTranslated(0.0, 0.6, 0.0);
        glRotated(90.0, 1.0, 0.0, 0.0);

        //ruke
        glPushMatrix();
          glTranslated(0.0, 0.0, -1.4);
          glTranslated(0.0, -1.5, 0.0);
          glColor3d(0.7, 0.6, 0.5);
          valjak(0.05, 3.0, 12);
        glPopMatrix();

        // rotacija oko y osi za kretanje
        glPushMatrix();
          glRotated(-((ld - nn)*180.0) / (0.6 * M_PI), 0.0, 1.0, 0.0);

          //prvi kotac
          glTranslated(0.0, -0.7, 0.0);
          glColor3d(0.3, 0.3, 0.3);
          valjak(0.6, 0.2, 24);

          glPushMatrix();
            glTranslated(0.0, -0.05, 0.0);
            glColor3d(0.3, 0.4, 0.8);
            valjak(0.45, 0.1, 24);
          glPopMatrix();

        glPopMatrix();

        //drugi kotac
        glPushMatrix();
          glRotated(-((ld - nn)*180.0) / (0.6 * M_PI), 0.0, 1.0, 0.0);

          glTranslated(0.0, 0.5, 0.0);
          glColor3d(0.3, 0.3, 0.3);
          valjak(0.6, 0.2, 24);

          glPushMatrix();
            glTranslated(0.0, 0.15, 0.0);
            glColor3d(0.3, 0.4, 0.8);
            valjak(0.45, 0.1, 24);
          glPopMatrix();

        glPopMatrix();

      glPopMatrix();

Zatim se iscrtavaju kotači i ruke. Ruke su napravljene od jednog tankog valjka koji prolazi kroz tijelo. Kotači se sastoje od po dva valjka svaki.

      //vrat i glava
      glPushMatrix();

        //vrat + duzina
        glTranslated(0.0, 2.25, 0.0);
        glColor3d(0.9, 0.6, 0.4);
        valjak(0.1, 0.6 + duzina, 40);

        //glava + duzina
        glTranslated(0.0, 1.1 + duzina, 0.0);
        glColor3d(0.9, 0.6, 0.1);
        kugla(0.6, 48, 12, 1.0);

        //kapa
        glPushMatrix();
          glTranslated(0.0, 0.45, 0.0);
          glColor3d(0.1, 0.4, 0.6);
          stozac(0.4, 0.6, 40);
        glPopMatrix();

        //nos
        glRotated(-90.0, 0.0, 0.0, 1.0);
        glTranslated(0.0, 0.5, 0.0);
        glColor3d(0.9, 0.5, 0.1);
        stozac(0.2, 0.4, 40);

        //oci
        glTranslated(0.0, -0.2, 0.0);
        glTranslated(-0.3, 0.0, 0.0);
        glColor3d(0.0, 0.5, 0.5);

        glPushMatrix();
          glTranslated(0.0, 0.0, 0.2);
          valjak(0.1, 0.2, 20);
        glPopMatrix();

        glPushMatrix();
          glTranslated(0.0, 0.0, -0.2);
          valjak(0.1, 0.2, 20);
        glPopMatrix();

      glPopMatrix();

Na samom kraju, iscrtavaju se vrat i glava sa svim pripadajućim elementima. Vrat je napravljen od valjka, a glava od sfere. Kapa i nos su stošci, a oči su dva mala valjka.

Time završava stvaralački dio, a red dolazi na pripremanje sjena.

//Inicijalizacija
void Init(void) {
				(...)
}

Priprema se izvodi u metodi Init

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glShadeModel(GL_SMOOTH);
    glClearColor(0.69, 0.93, 0.93, 0.0);
    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    glClearDepth(1.0f);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_DEPTH_TEST);

    glEnable(GL_NORMALIZE);

Koristi se ModelView matrica i zaglađivanje, postavlja se pozadinska boja, čisti se spremnik dubine, a uključuje se i automatska normalizacija vektora. Zanimljivo je primijetiti i korekciju perspektivnog pogleda, koja na starijim verzijama Open GL-a doprinosi vizualnim kvalitetama prikaza.

    glGenTextures(1, &shadowMapTexture);
    glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapSize, shadowMapSize, 0,
                 GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);

    glMaterialfv(GL_FRONT, GL_SPECULAR, bijelo);
    glMaterialf(GL_FRONT, GL_SHININESS, 20.0f);

Generira se i tekstura na koju će se lijepiti mapa sjena, tj. pogled iz izvora svjetlosti. Postavljaju se i svojstva osvjetljenja materijala.

Nakon toga slijedi vrlo važan dio priče.

    glPushMatrix();

      glLoadIdentity();
      gluPerspective(90.0f, (float) windowWidth / windowHeight, 1.0f, 250.0f);
      glGetFloatv(GL_MODELVIEW_MATRIX, cameraProjectionMatrix);

      glLoadIdentity();
      gluLookAt(cameraPosition[0], cameraPosition[1], cameraPosition[2],
                center[0], center[1], center[2],
                0.0f, 1.0f, 0.0f);
      glGetFloatv(GL_MODELVIEW_MATRIX, cameraViewMatrix);

      glLoadIdentity();
      gluPerspective(90.0f, 1.0f, 1.0f, 250.0f);
      glGetFloatv(GL_MODELVIEW_MATRIX, lightProjectionMatrix);

      glLoadIdentity();
      gluLookAt(lightPosition[0], lightPosition[1], lightPosition[2],
                center[0], center[1], center[2],
                0.0f, 1.0f, 0.0f);
      glGetFloatv(GL_MODELVIEW_MATRIX, lightViewMatrix);

    glPopMatrix();

Ovdje se generiraju matrice projekcije i matrice pogleda. To se, doduše, moglo i kasnije učiniti neposredno prije upotrebe, no ovako je sve na jednom mjestu i kasnije je dovoljno samo te matrice učitati. Ono što je bitno jest da su te matrice razdvojene i spremeljene, naročito kod matrice iz pogleda svjetla koja će se kasnije transformacijama primijeniti na način da se dobije odgovarajuća slika sa sjenama.

Priprema je time završena, te se kreće u konkretni posao.

//Iscrtavanje
void Display(void) {
    Init();
				(...)
}

Taj će se dio obavljati u metodi Display, nakon što se pozove i izvede prethodno objašnjena metoda Init.

    //Prvi prolaz - pogled svjetla
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(lightProjectionMatrix);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(lightViewMatrix);

    glViewport(0, 0, shadowMapSize, shadowMapSize);

    //Odstupanje za rjesavanje akni
    if (cor == 1) {    
        glEnable(GL_POLYGON_OFFSET_FILL);
        glPolygonOffset(1.0f, 1.0f);
    }

    //Selektivno odbacivanje za rjesavanje akni
    if (cul == 1) {
        glEnable(GL_CULL_FACE);
        glCullFace(GL_FRONT);
    }

    //Micanje boja
    glShadeModel(GL_FLAT);
    glColorMask(0, 0, 0, 0);

Nakon što se učitaju odgovarajuće pripremljene matrice za pogled svjetla, potrebno je primijeniti i tehnike rješavanja problema akni, naravno ukoliko su uključene. Micanjem boja osiguravamo da se u ovom pogledu kreirani objekti ne iscrtavaju, a stavlja se i flat način sjenčanja kako bi se dobilo na performansama, jer s obzirom da se taj pogled ne prikazuje, nema smisla trošiti resurse na njegovo uljepšavanje.

    //Isrtavanje objekata u pogled svjetla
    objekt();

    glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, shadowMapSize, shadowMapSize);

    glCullFace(GL_BACK);

    glDisable(GL_POLYGON_OFFSET_FILL);
    glDisable(GL_CULL_FACE);
    glEnable(GL_DEPTH_TEST);

    glShadeModel(GL_SMOOTH);
    glColorMask(1, 1, 1, 1);

Kad se izvrši prethodna priprema, poziva se iscrtavanje svijeta. Iako se ne prikazuje na ekranu, to iscrtavanje je bitno kako bi se dobili podaci o koordinatama objekata iz pogleda svjetla. Umjesto prikaza na ekranu, to se iscrtavanje sprema u teksturu i čeka kasnije primjenu kod konačnog crtanja. Na kraju se vraćaju promijenjene postavke na vrijednosti koje će biti potrebne kod crtanja na ekran.

Generiranje neophodnih sastojaka za sjene time je završeno, pa slijedi prebacivanje u pogled kamere.

    //iscrtavanje s pozicije kamere - tamni pogled
    glClear(GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glLoadMatrixf(cameraProjectionMatrix);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(cameraViewMatrix);

    glViewport(0, 0, windowWidth, windowHeight);

    glLightfv(GL_LIGHT1, GL_POSITION, lightPosition);
    glLightfv(GL_LIGHT1, GL_AMBIENT, tamno);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, tamno);
    glLightfv(GL_LIGHT1, GL_SPECULAR, crno);
    glEnable(GL_LIGHT1);
    glEnable(GL_LIGHTING);

    objekt();

    glLightfv(GL_LIGHT1, GL_POSITION, lightPosition);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, bijelo);
    glLightfv(GL_LIGHT1, GL_SPECULAR, bijelo);

Prije nego što se krene na finalni dio, izvodi se međukorak s crtanjem zatamnjenog prikaza. Jasno, najprije je potrebno učitati matrice pogleda kamere, te smjestiti izvor svjetlosti na pripadajući poziciju. Tamnim abmijentalnim i difuznim svjetlom, dobiva se to da sjene nisu potpuno crne, već imaju određenu boju, čime je cjelokupni dojam puno realniji.

Nakon iscrtavanja objekata s tamnim pogledom, postavlja se prava svjetlost i napokon se nazire cilj

    //Izracun tekstura za konacnu projekciju
    M3DMatrix44f tempMatrix = {0.5f, 0.0f, 0.0f, 0.0f,
                               0.0f, 0.5f, 0.0f, 0.0f,
                               0.0f, 0.0f, 0.5f, 0.0f,
                               0.5f, 0.5f, 0.5f, 1.0f};

    m3dMatrixMultiply44(textureMatrix, tempMatrix, lightProjectionMatrix);
    m3dMatrixMultiply44(tempMatrix, textureMatrix, lightViewMatrix);
    m3dTransposeMatrix44(textureMatrix, tempMatrix);

Prvo što je potrebno učiniti jest izračunati konačnu matricu transformacije. Početna se matrica postavlja na polovične vrijednosti, jer će se obavaljati množenje s dvije matrice, pa bi se u slučaju jediničnih početnih vrijednosti na kraju završilo s dvostrukima i prikaz ne bi bio dobar. Prvo se množi s matricom projekcije, a zatim i s matricom pogleda. Obje te matrice dobivene su iz pogleda svjetla.

    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv(GL_S, GL_EYE_PLANE, &textureMatrix[0]);
    glEnable(GL_TEXTURE_GEN_S);

    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv(GL_T, GL_EYE_PLANE, &textureMatrix[4]);
    glEnable(GL_TEXTURE_GEN_T);

    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv(GL_R, GL_EYE_PLANE, &textureMatrix[8]);
    glEnable(GL_TEXTURE_GEN_R);

    glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGenfv(GL_Q, GL_EYE_PLANE, &textureMatrix[12]);
    glEnable(GL_TEXTURE_GEN_Q);

    glBindTexture(GL_TEXTURE_2D, shadowMapTexture);
    glEnable(GL_TEXTURE_2D);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL);

    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_INTENSITY);

Nakon što se matrica izračuna, ručno se postavljaju teksture. Tamo gdje će koordinate konačnog iscrtavanja biti bliže izvoru svjetlosti, one će se i iscrtati. U područjima gdje će udaljenost od izvora biti veća u odnosu na osvijetljene dijelove, neće se iscrtati osvijetljeni, već će ostati ovakav sjenoviti prikaz.

    glAlphaFunc(GL_GEQUAL, 0.99f);
    glEnable(GL_ALPHA_TEST);

Ovim dvjema linijama onemogućava se crtanje sjena kakve su dobivene iz pogleda svjetlosti, jer imaju parametar alpha manji od navedenog, već će umjesto toga ostati prikaz onog drugog, zatamnjenog iscrtavanja.

I napokon, dolazi se do posljednjeg dijela gdje će se iscrtati osvijetljena slika.

    //Iscrtavanje s pozicije kamere - svjetli pogled i objekt svjetla
    glMaterialfv(GL_FRONT, GL_EMISSION, bijelo);
    glColor3d(1.0, 1.0, 1.0);
    glPushMatrix();
      glTranslated(lightPosition[0], lightPosition[1], lightPosition[2]);
      glPushMatrix();
        glTranslated(0.4, -0.1, 0.0);
        glRotated(80.0, 0.0, 0.0, 1.0);
        stozac(0.2, 0.5, 24);
      glPopMatrix();
      glMaterialfv(GL_FRONT, GL_EMISSION, crno);
      glTranslated(0.0, -lightPosition[1], 0.0);
      valjak(0.05, lightPosition[1], 40);
    glPopMatrix();

    objekt();

    glutSwapBuffers();

Prvo se crtaju svjetlo i stup na kojem se ono nalazi, a nakon toga pomoću objekt() crtaju se i svi preostali objekti, tj. čitav preostali svijet. Za kraj, sa glutSwapBuffers cijela ova priča koja se isrctavala u buffer, prikazuje se na ekranu.

Osnovni i najkompliciraniji dio time je završen. Preostale su još metode koje su uglavnom standardne u programima ovakvog tipa.

//Promjena velicine prozora
void Reshape(int w, int h) {
    windowWidth = w, windowHeight = h;
    glPushMatrix();
      glLoadIdentity();
      gluPerspective(90.0f, (float) windowWidth / windowHeight, 1.0f, 250.0f);
      glGetFloatv(GL_MODELVIEW_MATRIX, cameraProjectionMatrix);
    glPopMatrix();
}

Reshape se poziva svaki puta kada se promijeni veličina prozora, pri čemu se ažurira matrica projekcije kamere.

//Postupanje s tipkama
void Keyboard(unsigned char key, int x, int y) {
    if (key == 27) exit(0);
    if (key == 'q') cameraPosition[1] += 0.2;
    if (key == 'Q') cameraPosition[1] -= 0.2;
    if (key == 'w') cameraPosition[0] += 0.2;
    if (key == 'W') cameraPosition[0] -= 0.2;
    if (key == 'e') cameraPosition[2] += 0.2;
    if (key == 'E') cameraPosition[2] -= 0.2;

    if (key == 'r') center[1] += 0.2;
    if (key == 'R') center[1] -= 0.2;
    if (key == 't') center[0] += 0.2;
    if (key == 'T') center[0] -= 0.2;
    if (key == 'z') center[2] += 0.2;
    if (key == 'Z') center[2] -= 0.2;

    if (key == 'a') lightPosition[1] += 0.1;
    if (key == 'A') lightPosition[1] -= 0.1;
    if (key == 's') lightPosition[0] += 0.1;
    if (key == 'S') lightPosition[0] -= 0.1;
    if (key == 'd') lightPosition[2] += 0.1;
    if (key == 'D') lightPosition[2] -= 0.1;

    if (key == 'y') {
        cul = 0;
        printf("Ispravljanje akni - odbacivanje iskljuceno \n");
    }
    if (key == 'Y') {
        cul = 1;
        printf("Ispravljanje akni - odbacivanje ukljuceno \n");
    }
    if (key == 'x') {
        cor = 0;
        printf("Ispravljanje akni - odstupanje iskljuceno \n");
    }
    if (key == 'X') {
        cor = 1;
        printf("Ispravljanje akni - odstupanje ukljuceno \n");
    }

    if (key == 'k') prop -= 0.05;
    if (key == 'K') prop += 0.05;

    if (key == '+') if (duzina < 5) duzina += 0.05;
    if (key == '-') if (duzina > -0.5) duzina -= 0.05;

    if (key == 'L') {
        camx = cameraPosition[0];
        camy = cameraPosition[1];
        camz = cameraPosition[2];
        cameraPosition[0] = lightPosition[0];
        cameraPosition[1] = lightPosition[1];
        cameraPosition[2] = lightPosition[2];
    }
    if (key == 'l') {
        cameraPosition[0] = camx;
        cameraPosition[1] = camy;
        cameraPosition[2] = camz;
    }
}

void Special(int c, int x, int y) {
    // pomak lika
    if (c == GLUT_KEY_UP) {
        nn += 0.2;
        okret = 90.0;
    }
    if (c == GLUT_KEY_DOWN) {
        nn -= 0.2;
        okret = 270.0;
    }
    if (c == GLUT_KEY_LEFT) {
        ld += 0.2;
        okret = 180.0;
    }
    if (c == GLUT_KEY_RIGHT) {
        ld -= 0.2;
        okret = 0.0;
    }
}

U gornje dvije metode definirana je interakcija između korisnika i svijeta koji gleda na ekranu. Prihvaća se svaki pritisak na tipku, a ovisno o tome što je pritisnuto, mijenjaju se zadane varijable.

void Timer(int id) {
    glutPostRedisplay();
    glutTimerFunc(40, Timer, 0);
}

Pomoću Timera realizirana je animacija. Svakih 40 milisekundi poziva se novo iscrtavanje na ekran.

int main(int argc, char** argv) {
    //Generiranje slucajnih brojeva za objekte
    srand((unsigned) time(0));
    int i;
    for (i = 0; i <= 15; i++) {
        ro[i] = rand() % 2;
        rx[i] = rand() % 35;
        rz[i] = -rand() % 35;
        rr[i] = rand() % 4;
        rv[i] = rand() % 5;
        rcr[i] = rand() % 11 + 1;
        rcg[i] = rand() % 11 + 1;
        rcb[i] = rand() % 11 + 1;
    }
    printf("ISCRTAVANJE SJENA POMOCU SPREMNIKA DUBINE\n");
    printf("AUTORI: Franjo Kovacic, Ivan Vuljak\n");
    printf("_________________________________________\n");
    printf("KONTROLE:\n");
    printf("\ty/Y, x/X   >>>   ispravljanje akni isklj./uklj. odbacivanje, odstupanje\n");
    printf("\tq/Q, w/W, e/E   >>>   x, y, z  koordinate kamere \n");
    printf("\ta/A, s/S, d/D   >>>   x, y, z  koordinate svjetla \n");
    printf("\tr/R, t/T, z/Z   >>>   x, y, z  koordinate centralne tocke pogleda\n");
    printf("\tL/l   >>>   prebacivanje kamere u polozaj svjetla/pocetni polozaj\n");
    printf("\tstrelice   >>>   kretanje lika\n");
    printf("\t+/-   >>>   duzina vrata\n");
    printf("\tk/K   >>>   povecavanje/smanjivanje lika\n");
    printf("\t[Esc]   >>>   izlaz\n");
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(700, 700);
    glutCreateWindow("Sjene");

    glutDisplayFunc(Display);
    glutReshapeFunc(Reshape);
    glutKeyboardFunc(Keyboard);
    glutSpecialFunc(Special);
    glutTimerFunc(50, Timer, 0);
    glutMainLoop();
    return 0;
}

Na samom koncu konca, nalazi se main funkcija. U njoj se prvo slučajnim odabirom generiraju vrijednosti globalnih varijabli za raspored slučajno odabranih objekata. Nakon toga se u konzolu ispisuju kontrole, te se pozivaju potrebne funkcije za inicijalizaciju i prikaz prozora i njegovog sadržaja.