mario::konrad
programming / C++ / sailing / nerd stuff
OpenGL: Tutorial 6 - Spotlampe auf Heightmap
© 2004 / Mario Konrad

Beschreibung

Spotlampe auf Heightmap: ein Tutorial für dynamisches Licht und einem Terrain.

Platformen:

Download

Die auf dieser Seite aufgeführten Sourcecodes dürfen uneingeschränkt verwendet, kopiert, verändert und publiziert werden. Jegliche Haftung wird abgelehnt, die Verwendung des hier publizierten Materials geschieht auf eigenes Risiko.

Source: tutorial06.cpp

Paket: tutorial06.tgz

Build und Start

$ tar -xzf tutorial06.tgz
$ cd tutorial06
$ make
$ ./tutorial06

Das Demo

Dieses Demo zeigt ein Heightmap (Feld von Höhenwerten), welches von einer Lichtquelle, in diesem Fall eine Spotlampe, beschienen wird.

Das Handling der Lampe wird in einer eigenen Klasse abgehandelt. Alles Übrige ist wie bisher.

Das Programm

Die Klasse Light, welche alle nötigen Eigenschaften und Methoden kennt um verschiedene Lichtquellen zu erzeugen. In diesem konkreten Fall, brauchen wir eine Spotlampe.

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Light
{
    private:
        int id;
        float pos[4];
        float ambient[4];
        float diffuse[4];
        float specular[4];
        float attenuation[3];
        float spot_dir[3];
        float spot_exp;
        float spot_cutoff;
    public:
        Light(int);
        Light & setPos(float, float, float);
        Light & setSpotDir(float, float, float);
        void init();
        void set();
        void draw();
};

Light::Light(int id)
    : id(id)
{
    // ....
}

Light & Light::setPos(float p0, float p1, float p2)
{
    // ....
}

Light & Light::setSpotDir(float p0, float p1, float p2)
{
    // ....
}

Die Initialisierung der Lichtquelle, setzt alle möglichen Parameter.

80
81
82
83
84
85
86
87
88
89
90
91
void Light::init()
{
    glEnable(id);
    glLightfv(id, GL_AMBIENT, ambient);
    glLightfv(id, GL_DIFFUSE, diffuse);
    glLightfv(id, GL_SPECULAR, specular);
    glLightf(id, GL_SPOT_EXPONENT, spot_exp);
    glLightf(id, GL_SPOT_CUTOFF, spot_cutoff);
    glLightf(id, GL_CONSTANT_ATTENUATION, attenuation[0]);
    glLightf(id, GL_LINEAR_ATTENUATION, attenuation[1]);
    glLightf(id, GL_QUADRATIC_ATTENUATION, attenuation[2]);
}

Das Setzen einer Lichtquelle muss allerdings nur die Position der Quelle und die Richtung des Spots neu setzen.

93
94
95
96
97
98
99
void Light::set()
{
    glPushMatrix();
    glLightfv(id, GL_POSITION, pos);
    glLightfv(id, GL_SPOT_DIRECTION, spot_dir);
    glPopMatrix();
}

Die Methode draw zeichnet eine Linie von der Position der Lichtquelle in der Richtung des Spots. Dies wird zur Demonstration benötigt.

101
102
103
104
105
106
107
108
109
110
111
112
113
void Light::draw()
{
    float p[3];

    for (int i = 0; i < 3; ++i) p[i] = pos[i] + 10.0 * spot_dir[i];

    glLineWidth(1.0);
    glColor4f(1.0, 0.0, 0.0, 1.0);
    glBegin(GL_LINES);
        glVertex3fv(pos);
        glVertex3fv(p);
    glEnd();
}

Es folgen nun alle nötigen Definitionen und Deklarationen für das Heightmap: Breite (width), Höhe (height), anzahl Abteile in beiden Richtungen (nx, ny), die Heightmap selbst (h) und die zugehörigen Normalen (n).

115
116
117
118
119
120
121
static const float width = 20.0;
static const float height = 20.0;
static const int nx = 16;
static const int ny = 16;

float h[nx+1][ny+1][3];
float n[nx+1][ny+1][3];

Ferner benötigen wir noch eine Lichtquelle light0, den Zustand der Anzeige (Fullscreen oder Fenster) und Initialwerte für das Material der Heightmap.

123
124
125
126
127
128
129
130
Light light0(GL_LIGHT0);
int fullscreen = 0;

static float modelAmb[4] = {0.2, 0.2, 0.7, 1.0};
static float matAmb[4] = {0.2, 0.2, 0.3, 1.0};
static float matDiff[4] = {0.8, 0.8, 0.8, 1.0};
static float matSpec[4] = {0.4, 0.4, 0.4, 1.0};
static float matEmission[4] = {0.0, 0.0, 0.0, 1.0};

Die folgende Funktion berechnet die Heightmap. In diesem Fall ist es eine glatte Fläche. Um dies zu ändern müssen die Höhenwerte auf der Zeile 142 geändert werden, z.B. in Abhägigkeit der Koordinate: h[x][y][2] = sin(x);. Will man auch nocht ein korrektes Shading, so müssen die Normalen (Zeilen 144-146) berechnet werden.

132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
void calc_heightfield(void)
{
    float dw = width / nx;
    float dh = height / ny;
    for (int x = 0; x <= nx; ++x)
    {
        for (int y = 0; y <= ny; ++y)
        {
            h[x][y][0] = x * dw;
            h[x][y][1] = y * dh;
            h[x][y][2] = 0.0;

            n[x][y][0] = 0.0;
            n[x][y][1] = 0.0;
            n[x][y][2] = 1.0;
        }
    }
}

Zu der Zeichnungsfunktion display gibt es zwei Dinge anzumerken. Zum Einen sind es die Materialeigenschaften der Oberfläche (gegeben durch die Heigtmap, Zeilen 188-193), zum Andern sind es die Normalen die für jeden Punkt definiert werden (Zeilen 201-207).

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    gluLookAt(20.0, -20.0, 20.0, 0.0, 0.0,  0.0, 0.0, 1.0,  0.0);

    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    glEnable(GL_LIGHTING);
    light0.set();

    // surface
    glMaterialfv(GL_FRONT, GL_AMBIENT, matAmb);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, matDiff);
    glMaterialfv(GL_FRONT, GL_SPECULAR, matSpec);
    glMaterialfv(GL_FRONT, GL_EMISSION, matEmission);
    glMaterialf(GL_FRONT, GL_SHININESS, 10.0);
    glPushMatrix();
    glLineWidth(1.0);
    glTranslatef(-width/2.0, -height/2.0, 0.0);
    for (int x = 0; x < nx; ++x)
    {
        glBegin(GL_TRIANGLES);
        for (int y = 0; y < ny; ++y)
        {
            glNormal3fv(n[x][y]);       glVertex3fv(h[x][y]);
            glNormal3fv(n[x+1][y]);     glVertex3fv(h[x+1][y]);
            glNormal3fv(n[x+1][y+1]);   glVertex3fv(h[x+1][y+1]);

            glNormal3fv(n[x][y]);       glVertex3fv(h[x][y]);
            glNormal3fv(n[x+1][y+1]);   glVertex3fv(h[x+1][y+1]);
            glNormal3fv(n[x][y+1]);     glVertex3fv(h[x][y+1]);
        }
        glEnd();
    }
    glPopMatrix();

    glDisable(GL_LIGHTING);

    // bounding box
    glLineWidth(3.0);
    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glColor4f(0.8, 0.8, 0.8, 1.0);
    glBegin(GL_QUADS);
        glVertex3f(-width/2.0, -height/2.0, 0.0);
        glVertex3f( width/2.0, -height/2.0, 0.0);
        glVertex3f( width/2.0,  height/2.0, 0.0);
        glVertex3f(-width/2.0,  height/2.0, 0.0);
    glEnd();

    // light ray
    light0.draw();

    glFlush();
    glutSwapBuffers();
}

Die Funktion main beinhaltet eigentlich nichts Spektakuläres, Vollständigkeitshalber sind die Zeilen 253-263 zu erwähnen die Licht und Material konfigurieren.

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
int main(int argc, char ** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowPosition(0, 0);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Tutorial 06: Spotlight and Heightmap");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutKeyboardFunc(keyboard);
    glutIdleFunc(idle);
    glClearColor(0.0, 0.0, 0.0, 0.0);
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);

    glEnable(GL_LIGHTING);
    glEnable(GL_NORMALIZE);

    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, modelAmb);
    glLightModelf(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
    glLightModelf(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
    glMaterialfv(GL_FRONT, GL_AMBIENT, matAmb);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, matDiff);
    glMaterialfv(GL_FRONT, GL_SPECULAR, matSpec);
    glMaterialfv(GL_FRONT, GL_EMISSION, matEmission);
    glMaterialf(GL_FRONT, GL_SHININESS, 10.0);

    calc_heightfield();

    light0.init();

    glutMainLoop();
    return 0;
}