Egérkezelés és fények

1. Kezdjünk új OpenGL projektet, de üres main.cpp helyett ezzel a file-lal!

2. Írjuk meg, hogy egér és billentyűzet segítségével be lehessen járni a színteret! Ehhez:

3. Hozzunk létre egy nagyon egyszerű kamera osztályt:

struct camera
{
    camera(float x=0, float y=0, float z=0, float tPitch=0, float tYaw=0) : pitch(tPitch), yaw(tYaw)
    {
        pos[0] = x; pos[1] = y; pos[2] = z;
    }

    void setView()
    {
        glRotatef(pitch, 1,0,0);
        glRotatef(yaw, 0,0,1);
        glTranslatef(-pos[0], -pos[1], -pos[2]);
    }

    float pos[3];
    float pitch, yaw;
};

Nyilvántartjuk a kamera pozícióját és két tengelymentén vett elfordulását.

4. Szükségünk lesz az új osztály egy konkrét példányára, ez legyen egy globális változó:

camera cam(0, -12, 5, -75, 0);

5. A display-ben használjuk is ezt a gluLookAt helyett!

//...
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
cam.setView();
//...

6. WASD mozgassa a kamerát a globális X,Y tengelyek mentén (jobbra-balra, előre-hátra); R,F a globális Z tengely mentén (fel-le). Ehhez adjuk hozzá a billentyűzetkezelő switch-éhez a következőket:

    case 'w':
        cam.pos[1] += 1;
        break;
    case 's':
        cam.pos[1] -= 1;
        break;
    case 'a':
        cam.pos[0] -= 1;
        break;
    case 'd':
        cam.pos[0] += 1;
        break;
    case 'r':
        cam.pos[2] += 1;
        break;
    case 'f':
        cam.pos[2] -= 1;
        break;

7. A GLUT külön eseménykezelővel dolgozza fel az egérkattintásokat, az egérmozgást lenyomott gomb mellett és az egérmozgást gomblenyomás nélkül. Nekül ezek közül az első kettőre lesz szükségünk, hogy az kamerát egérrel fogatni tudjuk. Bal-klikkre kezdődjön a mozgatás, és a jobbra-balra mozgatás forgasson a Z tengely mentén, a fel-le mozgatás forgasson az X mentén. Ehhez kellenek a következő globális változók és a két eseménykezelő:

float pX, pY;
bool dragging = false;
void mouseMove(int x, int y)
{
    if (dragging)
    {
        cam.yaw += (x-pX)/10;
        cam.pitch += (y-pY)/10;
        pX = x;
        pY = y;
    }
}

void mouseClick(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON)
    {
        pX = x;
        pY = y;
        dragging = (state == GLUT_DOWN);
    }
}

8. Jelezzük a GLUT-nak a main-ben, hogy használja is ezeket az eseménykezelőket!

glutMouseFunc(mouseClick);
glutMotionFunc(mouseMove);

1. Adjunk fényeket a színtérhez! A fények beállításához szükséges lépéseket fogjuk össze egyetlen függvénybe, amit a main-ben meghívunk!

void setLights()
{
    float lightAmbient[]= { 0.5f, 0.5f, 0.5f, 1.0f };
    float lightDiffuse[]= { 1.0f, 1.0f, 1.0f, 1.0f };
    float lightPosition[]= { 0.0f, 0.0f, 8.0f, 1.0f };

    glLightfv(GL_LIGHT1, GL_AMBIENT, lightAmbient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, lightDiffuse);
    glLightfv(GL_LIGHT1, GL_POSITION,lightPosition);
    glEnable(GL_LIGHT1);
    glEnable(GL_LIGHTING);
}

Az egyes fényforrások tulajdonságait a glLight függvényekkel állíthatjuk. Ezek első paramétere a fényforrás azonosítója, második az állítani kívánt paraméter, harmadik pedig az új érték, vagy értékekre mutató pointer (glLigthfv/glLigthiv) esetén. Itt megadjuk a fény ambiens és szórt színét, és a fényforrás helyét. A pozíció negyedik (w) komponense határozza meg a fényforrás típusát: ha 0 akkor irányfényforrás lesz, ha 1 akkor spot.

A tulajdonságok megadása mellett be kell kapcsolni az adott fényforrást, és engedélyezni kell magát a megvilágítást.

Az eredmény:

Szin és normálok nélkül

2. A megadott színek nem látszanak a modellünkön, mivel az árnyalás számításhoz az OpenGL glMaterial-lal megadott értékeket vár, de mi csak glColor-t használtunk. Ha nincs szükség a glMaterial lehetőségeire (külön ambiens/szórt/csillanási színek, fényesség, stb.) akkor a következő beállításokkal aglColor is használható helyette (a kód a setLights-ba kerül):

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

A glColorMaterial megadja, hogy milyen színekhez és lapokhoz használja az OpenGL a glColor-ral megadott színeket. A glEnable itt engedélyezi a glColor értékek használatát.

Az eredmény:

Normálok nélkül

3. A színek ugyan újra megjelentek, de a megvilágítás eredménye nem látszik - pontosabban látszik, csak nem jó -, mert nincsenek megadva a felületi normálvektorok. Ehhez használjuk a következő bővített ship függvényt:

void ship(void)
{
    static float vx[][3] = {0,0,0,
            4.2f, 0, 0.7f,
            2.550074f, -1.138804f, 1,
            -1.7f, 0, 5,
            -1.7f, 0, 0.05f,
            -1.8f, 0, 0.05f,
            -1.799999f, 0, 5,
            0.231852f, -0.517638f, 6.1f,
            2.550074f, -1.138804f, 1,
            -1.7f, 0, 1,
            -1.7f, 0, 4.7f,
            0, 0.6f, 0,
            0, -0.6f, 0,
            -2, -0.531865f, 0.05f,
            -2, 0.531865f, 0.05f,
            -4, 0, 0.2f,
            2, 0.52754f, 0.05f,
            2, -0.52754f, 0.05f,
            4, -0.368479f, 0.2f,
            4, 0.368479f, 0.2f,
            0, -1, 0.5f,
            -2, -0.886441f, 0.55f,
            0, 1, 0.5f,
            -2, 0.886441f, 0.55f,
            -4.5f, 0, 0.7f,
            2, -0.879233f, 0.55f,
            2, 0.879233f, 0.55f,
            4.2f, -0.614131f, 0.7f,
            4.2f, 0.614131f, 0.7f };

    static float normals[][3] = {0,0,0,
            0, 1, 0,
            -0.258819f, -0.965926f, 0,
            0.000641f, 0.446059f, -0.894986f,
            0.000641f, -0.446059f, -0.894986f,
            -0.128086f, -0.470473f, -0.873043f,
            -0.128086f, 0.470473f, -0.873043f,
            0.077944f, 0.46324f, -0.882778f,
            0.077944f, -0.46324f, -0.882778f,
            0.477126f, 0.356426f, -0.803278f,
            0.477126f, -0.356426f, -0.803308f,
            0.001129f, -0.798791f, -0.601581f,
            -0.17542f, -0.829585f, -0.530076f,
            -0.17542f, 0.829585f, -0.530076f,
            0.001129f, 0.798791f, -0.601581f,
            -0.332286f, 0, -0.943144f,
            -0.551714f, 0, -0.83401f,
            0.088168f, -0.820826f, -0.564287f,
            0.088168f, 0.820826f, -0.564287f,
            0.649464f, -0.519669f, -0.555071f,
            0.649464f, 0.519669f, -0.555071f, };

    glBegin(GL_QUADS);
        // Arboc
    glColor3f(0.95703125f, 0.8671875f, 0.69921875f);
    glNormal3fv(normals[1]);    glVertex3fv(vx[3]);
    glNormal3fv(normals[1]);    glVertex3fv(vx[6]);
    glNormal3fv(normals[1]);    glVertex3fv(vx[5]);
    glNormal3fv(normals[1]);    glVertex3fv(vx[4]);

        // Vitorla
    glColor3f(1, 1, 1);
    glNormal3fv(normals[2]);    glVertex3fv(vx[9]);
    glNormal3fv(normals[2]);    glVertex3fv(vx[8]);
    glNormal3fv(normals[2]);    glVertex3fv(vx[7]);
    glNormal3fv(normals[2]);    glVertex3fv(vx[10]);

        // Torzs
    glColor3f(0.75f, 0.25f, 0);
    glNormal3fv(normals[3]);    glVertex3fv(vx[11]);
    glNormal3fv(normals[4]);    glVertex3fv(vx[12]);
    glNormal3fv(normals[5]);    glVertex3fv(vx[13]);
    glNormal3fv(normals[6]);    glVertex3fv(vx[14]);
    glNormal3fv(normals[3]);    glVertex3fv(vx[11]);
    glNormal3fv(normals[7]);    glVertex3fv(vx[16]);
    glNormal3fv(normals[8]);    glVertex3fv(vx[17]);
    glNormal3fv(normals[4]);    glVertex3fv(vx[12]);
    glNormal3fv(normals[7]);    glVertex3fv(vx[16]);
    glNormal3fv(normals[9]);    glVertex3fv(vx[19]);
    glNormal3fv(normals[10]);    glVertex3fv(vx[18]);
    glNormal3fv(normals[8]);    glVertex3fv(vx[17]);
    glNormal3fv(normals[4]);    glVertex3fv(vx[12]);
    glNormal3fv(normals[11]);    glVertex3fv(vx[20]);
    glNormal3fv(normals[12]);    glVertex3fv(vx[21]);
    glNormal3fv(normals[5]);    glVertex3fv(vx[13]);
    glNormal3fv(normals[6]);    glVertex3fv(vx[14]);
    glNormal3fv(normals[13]);    glVertex3fv(vx[23]);
    glNormal3fv(normals[14]);    glVertex3fv(vx[22]);
    glNormal3fv(normals[3]);    glVertex3fv(vx[11]);
    glNormal3fv(normals[15]);    glVertex3fv(vx[15]);
    glNormal3fv(normals[16]);    glVertex3fv(vx[24]);
    glNormal3fv(normals[13]);    glVertex3fv(vx[23]);
    glNormal3fv(normals[6]);    glVertex3fv(vx[14]);
    glNormal3fv(normals[5]);    glVertex3fv(vx[13]);
    glNormal3fv(normals[12]);    glVertex3fv(vx[21]);
    glNormal3fv(normals[16]);    glVertex3fv(vx[24]);
    glNormal3fv(normals[15]);    glVertex3fv(vx[15]);
    glNormal3fv(normals[8]);    glVertex3fv(vx[17]);
    glNormal3fv(normals[17]);    glVertex3fv(vx[25]);
    glNormal3fv(normals[11]);    glVertex3fv(vx[20]);
    glNormal3fv(normals[4]);    glVertex3fv(vx[12]);
    glNormal3fv(normals[3]);    glVertex3fv(vx[11]);
    glNormal3fv(normals[14]);    glVertex3fv(vx[22]);
    glNormal3fv(normals[18]);    glVertex3fv(vx[26]);
    glNormal3fv(normals[7]);    glVertex3fv(vx[16]);
    glNormal3fv(normals[10]);    glVertex3fv(vx[18]);
    glNormal3fv(normals[19]);    glVertex3fv(vx[27]);
    glNormal3fv(normals[17]);    glVertex3fv(vx[25]);
    glNormal3fv(normals[8]);    glVertex3fv(vx[17]);
    glNormal3fv(normals[9]);    glVertex3fv(vx[19]);
    glNormal3fv(normals[20]);    glVertex3fv(vx[28]);
    glNormal3fv(normals[19]);    glVertex3fv(vx[27]);
    glNormal3fv(normals[10]);    glVertex3fv(vx[18]);
    glNormal3fv(normals[7]);    glVertex3fv(vx[16]);
    glNormal3fv(normals[18]);    glVertex3fv(vx[26]);
    glNormal3fv(normals[20]);    glVertex3fv(vx[28]);
    glNormal3fv(normals[9]);    glVertex3fv(vx[19]);
    glEnd();

    glBegin(GL_TRIANGLES);
        // Torzs meg mindig
    glNormal3fv(normals[5]);    glVertex3fv(vx[13]);
    glNormal3fv(normals[15]);    glVertex3fv(vx[15]);
        glNormal3fv(normals[6]);    glVertex3fv(vx[14]);
    glEnd();

    glBegin(GL_LINES);
        // Kotelzet
        glColor3f(0, 0.25f, 0.75f);
        glVertex3fv(vx[1]);glVertex3fv(vx[2]);
    glEnd();
}

A glNormal hívásokkal határozható meg a következő csúcs(ok)hoz tartozó felületi normális. A glColor-hoz hasonlóan a a következő glVertex utasításokkal megadott csúcspontokhoz tartozó tulajdonságokat ad meg, és addig marad érvényben, amíg újabb glNormal hívás nem érkezik.

Így a látvány már majdnem hibátlan:

Egyoldalas

5. A felületi normálisoknak (vagy röviden normál vektoroknak) a pontos számításokhoz egységnyi hosszúnak kell lenniük. Ha nem ilyenek az adataink (most ilyenek), akkor az OpenGL-lel elvégeztethető a normalizálás, de ez elég költséges és kerülendő. Ez a következő hívással adható meg:

glEnable(GL_NORMALIZE);

Ami most számunkra érdekesebb, hogy a lapok hátoldala is látszik, és ezeket az OpenGL úgy tekinti, mintha nem érné őket a fény. (A legtöbb esetben ez a helyes megközelítés, mivel a modellek többsége ténylegesen zárt test, és nem csak lapokból áll.) További beállításokkal elérhető, hogy ezekre is helyes árnyalás számolódjon. Kód a setLights-ba:

glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

Így összesen a színterünk:

Kétoldalas