Computergrafik

Beleuchtung Standard-Codesequenz

Lichtquellen zeichnen | | Non-Photo-Realistic Rendering

Annahme: Framework-Funktionen werden verwendet, es gibt eine Initialisierungs-Funktion und eine Render-Funktion, die für jeden Frame aufgerufen wird.

Die hier vorgestellte Shader-Kombination ermöglicht beliebig viele Lichtquellen, direktionale und Punktlichtquellen, mit Abschwächung. Sie benutzt Uniform Arrays, um mehrere Lichtquellen zu verwalten. Die können wie einzelne Variablen auch benutzt werden. Die Abschwächung wird über Vorfaktoren für die drei Möglichkeiten Konstant (keine Abschwächung), Linear und Quadratisch bestimmt.

Es werden die Standard-Matrizen mv, mvp und mvit benutzt, die automatisch mit lglProjection() und lglModelView() berechnet und gesetzt werden. Arbeitet man mit anderen Variablen, muss man diese selber berechnen und mit lglUniformfv() bzw. glUniformMatrix4fv() setzen.

Aufgeführt sind nur die Änderungen zur Standard-Codesequenz:

Shader

Vertex-Shader

#version 120

uniform mat4 mvp;       // ModelViewProjection
uniform mat4 mv;        // ModelView
uniform mat4 mvit;      // inverse transposed ModelView

attribute vec4 vertex_position;
attribute vec3 vertex_normal;

// Lights
const   int  max_numlights = 4;
uniform int  numlights;
uniform vec4 lightpos [max_numlights];

varying vec3 normal, view;
varying vec3 light[max_numlights];

void main () {
    // Vertex trafo -> projected coords
    gl_Position  = mvp * vertex_position;
    // Normals trafo -> eye coords (unprojected)
    normal  = normalize(mat3 (mvit) * vertex_normal);
    // View vector: normalized reverted transformed vertex in eye coords
    // we ignore normalizing the view vector in the fragment shader,
    // thus we do it here
    view    = normalize (- vec3 (mv * vertex_position));
    // Light vector in eye coords
    // Loop over all lights, lights with homogenous component '0' are directional
    // Mulitplication is faster than if/else
    for (int i=0; i < numlights; i++)
        light[i] = vec3 (lightpos[i] - lightpos[i].w * mv * vertex_position);
}

Fragment-Shader

#version 120

// Ambient, emissive, diffuse, specular material colors
uniform vec4 Ca;
uniform vec4 Ce;
uniform vec4 Cd;
uniform vec4 Cs;
uniform float kse = 100;        // specular exponent

// Lights
const   int  max_numlights = 4;
uniform int  numlights;
uniform vec4 lightcol[max_numlights];
uniform vec4 lightatt[max_numlights];   // attenuation: constant, linear, quadratic, max. value

varying vec3 normal, view;
varying vec3 light[max_numlights];

void main () {
    // the normal vector needs to be normalized for C1 smoothness
    vec3 n = normalize (normal);
    // we ignore normalizing the view vector - apparently not necessary
    vec4 col = Ce;
    // Loop over all lights
    for (int i=0; i < numlights; i++) {
        float invdist = 1 / length (light[i]);
        vec3 l = light[i] * invdist;
        float atten = min (lightatt[i].x + lightatt[i].y * invdist +
                           lightatt[i].z * invdist*invdist, lightatt[i].w);
        // Diffuse light
        float Id  = max (dot (l, n), 0);
        // Specular light
        vec3  h   = normalize (l + view);
        float Is  = pow (max (dot (h, n), 0), kse);
        // Final Blinn-Phong Lighting
        col += lightcol[i] * (Ca + atten * (Cd*Id + Cs*Is));
    }
    // In-Pixel Blooming: If max > 1, bloom out to other channels
    vec4 bloom = max (col - vec4(1), 0);
    col += vec4 ((bloom.x+bloom.y+bloom.z));
    // Alpha is now by definition alpha of diffuse color
    col.a = Cd.a;
    gl_FragColor = col;
}

Init-Funktion

  • Shader (und Locations und VBOs) wie in Einfache Standard-Codesequenz
  • Uniform-Arrays bzw. Elemente können komplett mit lglUniformfv("array", ...) oder einzeln mit lglUniformfv("array[1]", ... gesetzt werden.
  • Initialisieren der Uniforms die selten (nie?) geändert werden
  • Alles weitere wie gewohnt

Render-Funktion

  • Projektions- und View-Matrizen berechnen, trackball
    mat4 proj = mat4::perspective(fovy, aspect, nearp, farp);
    mat4 view = mat4::lookat(vec3(0,0,10), vec3(0,0,0), vec3(0,1,0));
    mat4 mv   = view * lglGetManip();
  • Lichter konfigurieren: Position in Augenkoordinaten berechnen (am besten die ModelView-Matrix für die spätere Proxy Geometrie) und setzen, dafür braucht man ggf. bereits manche ModellView-Matrix (Lichtquelle am Roboterarm?)
    mat4 mv_light0 = rotate    (view, lightangle2, vec3 (0, 0, 1));
    mv_light0      = translate (mv_light0, vec3 (2.2, 0, .8));
    vec4 light_eye = mv_light0 * vec4(0,0,0,1);
    lglUniformfv ("lightpos[0]", light_eye);
  • Für jede Geometrie:
    • ModellView, ModellViewProjection, Invers Transponierte ModellView berechnen und setzen
      mat4f mv   = translate (view, vec3 (0, 0, -1));
      mat4f mvp  = proj * mv;
      mat4f mvit = inverse (transpose (mv));
      lglUniformfv ("mv",   (float *) mv);
      lglUniformfv ("mvp",  (float *) mvp);
      lglUniformfv ("mvit", (float *) mvit);
    • Erinnerung: Wenn nur Translationen und Rotation eingesetzt wurden, kann anstatt mvit auch direkt mv benutzt werden!
    • oder vereinfacht (bei Benutzung von mv, mvp und mvit)
      lglProjection (proj);    // Nur 1x nötig
      lglModelView  (mv);      // Je Änderung
    • Materialparameter setzen
      lglUniformfv ("Ca",  vec4( 0,  0, .3, 0));
      lglUniformfv ("Ce",  vec4( 0,  0,  0, 0));
      lglUniformfv ("Cd",  vec4(.3, .5,  0, 0));
      lglUniformfv ("Cs",  vec4(.8, .8, .8, 0));
      lglUniformf  ("kse", 100);
    • Geometrie zeichnen

Hinweis: Die Lichtquellen selber zeichnet man über Proxy-Geometrien.

Lichtquellen zeichnen | | Non-Photo-Realistic Rendering

Options: