Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Fnord42

Frischling

  • »Fnord42« ist der Autor dieses Themas

Beiträge: 18

Beruf: Student

  • Private Nachricht senden

1

25.09.2011, 20:01

[OpenGL] Shadow-Cube-Maps für omnidirektionale Lichtquellen

Seid gegrüßt werte Entwickler,

ich versuche nun seid mehreren Tagen vergeblich meiner in C/-++ geschriebenen 3D-Engine die Kunst der Schatten zu lehren.
Ich verwende OpenGL 3.3 mit eigenen Shadern und bin dabei Shadowmapping mit Cubemaps für omnidirektionale Lichtquellen (Punkt-Lichter) zu implementieren.
Als Testszenario verwende ich ein generiertes Terrain über welchem eine einzige Lichtquelle schwebt.
Dabei gehe ich folgendermaßen vor:
1) Ich initialisiere ein Framebufferobjekt (FBO) mit jeweils einer Cubemap für Farbe und Tiefe, ich initialisiere VertexBuffer für das Terrain und dessen Vertexindizes und für einen Debugwürfel der mir meine Shadowcubemap anzeigt.
2) Ich rendere das Terrain aus Lichtperspektive 6 mal mit gebundenem Cubemap-FBO jeweils auf eine Fläche der Cubemaps
3) Ich rendere das Terrain auf das Default-FBO mit der Shadow-Cube-Map als Uniform und kombiniere die Terraintextur mit Licht falls das Fragment nicht im Schatten liegt.

Bei Schritt 2) verwende ich einen Shader der die Distanz von Licht zu Vertex-position in Worldspace auf [0.0, 1.0] normiert und in den Farbwerten der Cubemap speichert.
Bei Schritt 3) verwende ich einen Shader der den Vektor von Licht zu Vertex-position berechnet, die Richtung dieses Vektors für den Texture-lookup in der Shadow-Cube-Map verwendet, den Tiefenwert aus der Cubemap mit der auf [0.0, 1.0] normierten Länge des Vektors vergleicht und somit entscheidet ob die aktuelle Position im Schatten liegt oder nicht.

Leider sehen die erzeugten Schatten total falsch aus, obwohl die Shadow-Cube-Map richtig zu sein scheint (siehe Bilder am Ende des Posts).
Ich würde mich sehr freuen falls jemand mal über meine Shader schauen könnte, da ich nun seid Stunden vergeblich nach Fehler suche.

Dies sind die Shader mit denen ich die Cubemap erzeuge:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ===== Vertex-Shader =============================================
in vec4 position; // In worldspace

uniform mat4 light_vp_matrix; // View-Projection-matrix für die aktuelle Würfelseite der Cubemap, Far-plane: 40.0
uniform vec3 light_pos; // In worldspace

out vec4 var_light_to_vertex_ws;

void main(void)
{
  var_light_to_vertex_ws = position - vec4(light_pos, 1.0);
  gl_Position = light_vp_matrix * position;
}

// ===== Fragment-Shader =============================================
in vec4 var_light_to_vertex_ws;
out vec4 frag;

void main(void)
{
  float dist_from_light = length(var_light_to_vertex_ws.xyz)/40.0;
  frag = vec4(vec3(1.0, 1.0, 1.0) * dist_from_light, 1.0);
  // gl_FragDepth = dist_from_light; // Erzeugt die gleichen Ergebnisse, wie ohne diese Zeile.
}


Dies sind die Shader mit denen ich mein Terrain beleuchte:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
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
// ===== Vertex-Shader =============================================
in vec4 position;
uniform vec3 light_position_ws;
uniform mat4 mvp_matrix;
out vec4 var_light_to_vertex_ws;
...

void main(void)
{
  var_light_to_vertex_ws = position - vec4(light_position_ws, 1.0);
  gl_Position = mvp_matrix * position;
  ...
}

// ===== Fragment-Shader =============================================
in vec4 var_light_to_vertex_ws;
uniform samplerCube shadow_map;
out vec4 frag;
...

void main(void)
{
  // Calculate shadow-map-depth of this fragment.
  vec3 shadow_map_dir   = normalize(var_light_to_vertex_ws).xyz;
  shadow_map_dir.xz     = -shadow_map_dir.xz; // Seltsamerweise sieht der Schatten etwas richtiger aus wenn ich xz negiere.
  float shadow_map_depth = texture(shadow_map, shadow_map_dir).r;

  // Calculate depth of this fragment.
  float frag_depth      = length(var_light_to_vertex_ws.xyz)/40.0;

  float is_in_light = 0.0;
  if (shadow_map_depth < frag_depth + 0.0005)
        is_in_light = 1.0;

  ...
}


Vielen Dank für eure Zeit und Hilfe :)

Das Licht schwebt über der Mitte des Terrains @ (0.0, 6.0, 0.0)

(Link)


Das ist die Shadow-Cube-Map:

(Link)

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Fnord42« (25.09.2011, 20:11)


Fnord42

Frischling

  • »Fnord42« ist der Autor dieses Themas

Beiträge: 18

Beruf: Student

  • Private Nachricht senden

2

25.09.2011, 22:43

Das Problem scheint auf jeden fall mit dem Cubemap-Texture-Lookup zusammenzuhängen.
Das hier kommt dabei raus wenn ich die Fragmentfarbe auf vec3(1.0, 1.0, 1.0)*shadow_map_depth setzte:

(Link)


Ich kann mir nur leider nicht erklären was falsch läuft ?(

edit: eigentlich sollte das Bild wie der Cubemap-Würfel ein Bild drüber aussehen, also ohne die komischen Striche und plötzlichen Farbunterschiede.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Fnord42« (26.09.2011, 01:03)


David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

3

25.09.2011, 22:49

Vielleicht eine dumme Frage, aber wo setzt du denn überhaupt gl_FragColor?

Fnord42

Frischling

  • »Fnord42« ist der Autor dieses Themas

Beiträge: 18

Beruf: Student

  • Private Nachricht senden

4

25.09.2011, 23:39

Vielleicht eine dumme Frage, aber wo setzt du denn überhaupt gl_FragColor?
Der Aufruf für gl_FragColor steht eigentlich in dem Shader der das Terrain beleuchtet und ist mit dem ... rausgekürzt.
Dachte ich schneide den Code nur auf das potentiell problematische zu.
Beleuchtung und Texturierung funktionieren. Wenn is_in_light = 1.0 ist wird auf die Lichtfarbe der Diffuse-Anteil draufaddiert.
In meinem letzten post überspringe ich dies und setzte die Fragmentfarbe direkt auf den Tiefenwert d den ich aus der CubeMap erhalte, bzw auf vec4(d, d, d, 1.0).
Das Bild sollte also wie der Cubemap-Würfel ein Bild drüber aussehen, also ohne die komischen Striche und plötzlichen Farbunterschiede

edit: hier ist der ganze code falls den wer benötigt:
Renderfunktion:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
  glBindVertexArray(m_terrain_vao);

  // Bind buffer for vertex indices and set data.
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_terrain_indices_buffer);
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_terrain_indices.size()*sizeof(GLuint), &m_terrain_indices[0], GL_STATIC_DRAW);

  // Vec3f light_position(sin(0.001*g_now)*15.0, 6.0, [> 0.0); // <]cos(0.0005*g_now)*15.0);
  Vec3f light_position(0.0, 6.0, 0.0);

  // Create Matrices for the light
  Mat4f light_proj_matrix;
  light_proj_matrix.set_identity();
  light_proj_matrix.set_projection(90.0, 1.0, 1.0, 40.0);

  Mat4f light_vp_matrices[6];
  Mat4f light_v_matrices[6];
  for (size_t i = 0; i < 6; i++)
  {
    Vec3f cube_normal, cube_up;
    switch (i)
    {
    case 0: cube_normal.set( 1.0,  0.0,  0.0); cube_up.set( 0.0, -1.0,  0.0); break;
    case 1: cube_normal.set(-1.0,  0.0,  0.0); cube_up.set( 0.0, -1.0,  0.0); break;
    case 2: cube_normal.set( 0.0,  1.0,  0.0); cube_up.set( 0.0,  0.0,  1.0); break;
    case 3: cube_normal.set( 0.0, -1.0,  0.0); cube_up.set( 0.0,  0.0, -1.0); break;
    case 4: cube_normal.set( 0.0,  0.0,  1.0); cube_up.set( 0.0, -1.0,  0.0); break;
    case 5: cube_normal.set( 0.0,  0.0, -1.0); cube_up.set( 0.0, -1.0,  0.0); break;
    }
    light_v_matrices[i].set_identity();
    light_v_matrices[i].set_view(light_position, light_position + cube_normal, cube_up);
    light_vp_matrices[i] = light_proj_matrix * light_v_matrices[i];
  }

  // Render Shadow-Cube-Map
  glViewport(0, 0, 1024, 1024);
  m_shadow_shader.use();
  glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_fbo);
  for (size_t i = 0; i < 6; i++)
  {
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,   GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, m_shadow_depth_cubemap_id, 0);
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,  GL_TEXTURE_CUBE_MAP_POSITIVE_X+i, m_shadow_color_cubemap_id, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUniformMatrix4fv(m_shadow_shader.get_uniform_location("light_vp_matrix"),
                    1, false, (GLfloat*) &light_vp_matrices[i]);
    glUniformMatrix4fv(m_shadow_shader.get_uniform_location("light_v_matrix"),
                    1, false, (GLfloat*) &light_v_matrices[i]);
    glUniformMatrix4fv(m_shadow_shader.get_uniform_location("light_pos"),
                    1, false, (GLfloat*) &light_position);
    glDrawElements(GL_TRIANGLE_STRIP, m_terrain_indices.size(), GL_UNSIGNED_INT, 0);
  }
  
  // Draw Shadow-Cube-Map (die Debug-Skybox)
  glViewport(0, 0, (GLint)m_width, (GLint)m_height);
  glBindVertexArray(m_skybox_vao);
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  m_cubemap_shader.use();
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  Mat4f mvp = proj_matrix * view_matrix;
  glUniformMatrix4fv(m_cubemap_shader.get_uniform_location("mvp_matrix"),
                    1, false, (GLfloat*) &mvp);
  glUniform1i(m_cubemap_shader.get_uniform_location("tex"), 0);
  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_CUBE_MAP, m_shadow_color_cubemap_id);
  glDrawArrays(GL_TRIANGLE_STRIP, 0, 19);
 
  // Draw Terrain
  glBindFramebuffer(GL_FRAMEBUFFER, 0);
  glBindVertexArray(m_terrain_vao);
  m_texture_lightning_shader.use();
  Mat4f a = view_matrix.invert().get_transposed();
  Mat3f normal_matrix(
    a[0], a[4], a[ 8],
    a[1], a[5], a[ 9],
    a[2], a[6], a[10]
  );
  Vec3f light_position_ls = view_matrix * light_position;

  glUniformMatrix4fv(m_texture_lightning_shader.get_uniform_location("mvp_matrix"),
                    1, false, (GLfloat*) &mvp);
  glUniformMatrix4fv(m_texture_lightning_shader.get_uniform_location("mv_matrix"),
                    1, false, (GLfloat*) &view_matrix);
  glUniformMatrix3fv(m_texture_lightning_shader.get_uniform_location("normal_matrix"),
                    1, false, (GLfloat*) &normal_matrix);
  glUniform3fv(m_texture_lightning_shader.get_uniform_location("light_position"),
                    1, (GLfloat*) &light_position_ls);
  glUniform3fv(m_texture_lightning_shader.get_uniform_location("light_position_ws"),
                    1, (GLfloat*) &light_position);

  Vec4f ambient_color(0.1, 0.1, 0.0, 1.0);
  Vec4f diffuse_color(1.0, sin(g_now*0.001)*0.5+0.5, cos(g_now*0.01)*0.5+0.5, 1.0);
  Vec4f specular_color(1.0, 1.0, 1.0, 1.0);

  glUniform4fv(m_texture_lightning_shader.get_uniform_location("ambient_color"),
                    1, (GLfloat*) &ambient_color);
  glUniform4fv(m_texture_lightning_shader.get_uniform_location("diffuse_color"),
                    1, (GLfloat*) &diffuse_color);
  glUniform4fv(m_texture_lightning_shader.get_uniform_location("specular_color"),
                    1, (GLfloat*) &specular_color);

  glUniformMatrix4fv(m_texture_lightning_shader.get_uniform_location("light_vp_matrix"),
                    1, false, (GLfloat*) &light_v_matrices[5]);

  glActiveTexture(GL_TEXTURE0);
  glBindTexture(GL_TEXTURE_2D, map->_texture_ids[0]);
  glUniform1i(m_texture_lightning_shader.get_uniform_location("tex"), 0);

  glActiveTexture(GL_TEXTURE1);
  glBindTexture(GL_TEXTURE_CUBE_MAP, m_shadow_color_cubemap_id);
  glUniform1i(m_texture_lightning_shader.get_uniform_location("shadow_map"), 1);

  glDrawElements(GL_TRIANGLE_STRIP, m_terrain_indices.size(), GL_UNSIGNED_INT, 0);


Rest der Beleuchtungs-Shader (noch etwas messy vom ganzen debuggen ;) ):

C-/C++-Quelltext

1
2
3
4
5
6
7
8
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// ===== Vertex-Shader ===========================
#version 330

in vec4 position;
in vec3 normal;
in vec2 tex_coords;

uniform mat4 mvp_matrix;
uniform mat4 mv_matrix;
uniform mat3 normal_matrix;
uniform vec3 light_position;
uniform vec3 light_position_ws;
uniform mat4 light_vp_matrix;

out vec3 var_normal;
out vec3 var_light_dir;
out vec2 var_tex_coords;
out vec4 var_light_to_vertex_ws;
out vec4 var_position;

void main(void)
{
  /* var_light_to_vertex_ws = light_vp_matrix * position; */
  var_light_to_vertex_ws = position - vec4(light_position_ws, 1.0);

  var_tex_coords = tex_coords;

  // Get surface normal in eye coordinates
  var_normal = normal_matrix * normal;

  // Get vertex position in eye coordinates
  vec4 vPosition4 = mv_matrix * position;
  vec3 vPosition3 = vPosition4.xyz / vPosition4.w;

  // Get vector to light source
  var_light_dir = normalize(light_position - vPosition3);

  var_position = mvp_matrix * position;

  // Don’t forget to transform the geometry!
  gl_Position = var_position;
}

// ===== Fragment-Shader ==============================
#version 330

in vec3 var_normal;
in vec3 var_light_dir;
in vec2 var_tex_coords;
in vec4 var_light_to_vertex_ws;
in vec4 var_position;

uniform sampler2D tex;
uniform samplerCube shadow_map;
uniform vec4 ambient_color;
uniform vec4 diffuse_color;
uniform vec4 specular_color;

out vec4 frag;

void main(void)
{
  // Dot product gives us diffuse intensity
  float diff = max(0.0, dot(normalize(var_normal),
  normalize(var_light_dir)));

  // Add in ambient light
  frag = ambient_color;

  // Calculate shadow-map-depth of this fragment.
  vec3 shadow_map_dir   = normalize(var_light_to_vertex_ws).xyz;
  shadow_map_dir.xz     = -shadow_map_dir.xz;
  float shadow_map_depth = texture(shadow_map, shadow_map_dir).x;

  // Calculate depth of this fragment.
  float frag_depth      = length(var_light_to_vertex_ws.xyz)/40.0;

  // Multiply intensity by diffuse color
  if (shadow_map_depth < frag_depth + 0.0005)
    frag += vec4(diff * diffuse_color.xyz, 1.0);
  else
    diff = 0;

  // Specular Light
  vec3 vReflection = normalize(reflect(-normalize(var_light_dir),
  normalize(var_normal)));
  float spec = max(0.0, dot(normalize(var_normal), vReflection));

  // If the diffuse light is zero, don’t even bother with the pow function
  if (diff != 0)
  {
    float fSpec = pow(spec, 128.0);
    frag.rgb += vec3(fSpec, fSpec, fSpec);
  }

  frag = texture(tex, var_tex_coords) * frag;

// Diese Zeile erzeugt das 3. Bild in schwarz-weiß:
// frag = vec4(vec3(1.0, 1.0, 1.0)*shadow_map_depth, 1.0);
}

Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »Fnord42« (26.09.2011, 01:02)


Fnord42

Frischling

  • »Fnord42« ist der Autor dieses Themas

Beiträge: 18

Beruf: Student

  • Private Nachricht senden

5

26.09.2011, 12:19

Ich hab nun herausgefunden, dass die komischen Streifen von Rundungsfehler beim Cubemaplookup stammen.
Zeigt ein Richtungsvektor genau auf die Kante eines Objektes, so kann durch Rundungsfehler das falsche Pixel ausgewählt werden -> der Texturelookup liefert den Wert des Objekts dahinter.
Aber selbst wenn man von den Streifen absieht stimmen die Schatten nicht :(

David Scherfgen

Administrator

Beiträge: 10 382

Wohnort: Hildesheim

Beruf: Wissenschaftlicher Mitarbeiter

  • Private Nachricht senden

6

26.09.2011, 12:24

Hast du es schonmal mit einem Floating Point-Render-Target probiert?
Dann könntest du dir die Umrechnung auf [0...255] sparen.

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

7

26.09.2011, 12:36

Deine ShadowMap als Graustufen auf dem Terrain sieht für mich korrekt aus... die jeweils dunkleren Bereiche werden anscheinend korrekt auf dahinterliegende Berge projeziert. Ich vermute daher, dass der Fehler beim Tiefenvergleich im finalen Shader liegt. Normalerweise würde ich jetzt empfehlen, mal mit PIX durch einen der PixelShader durchzusteppen, aber das ist ja auf DirectX beschränkt. Für OpenGL gibt es evtl. was ähnliches. Und Du wirst, wie David schon schrieb, nicht um ein Texturformat mit höherer Genauigkeit drumrumkommen. Für Lichtquellen empfiehlt sich da 16Bit-Fließkomma, für Parallellichtquellen mit großer Ausdehnung braucht es nach meiner Erfahrung schon 32bit-Fließkomma.

[edit]Die komischen weißen Kanten auf den Hügeln sind übrigens normal. Die ShadowMap ist halt nicht so genau, als dass sie den letzten Pixel oben noch abdecken würde. Stattdessen siehst Du da Terrainteile, die von Pixeln der CubeMap lesen, die beim Shadow Pass vorher gar nicht bemalt wurden.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

Fnord42

Frischling

  • »Fnord42« ist der Autor dieses Themas

Beiträge: 18

Beruf: Student

  • Private Nachricht senden

8

26.09.2011, 12:55

ok, vielen Dank für eure Antworten. Sehr beruhigend zu hören, dass meine Shadowmap normal aussieht :D
Das Format das ich verwende ist GLfloat der ja eigentlich 2Byte groß ist oder? bzw das ist meine Textureinitialisierung:

C-/C++-Quelltext

1
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_RGBA, width, width, 0, GL_RGBA, GL_FLOAT, 0);

Sind das nicht für jeden Kanal 16bit-Fließkommazahlen? Werde mal GL_DOUBLE versuchen.

edit:
@David: Falls du mit Floating-Point-Rendertarget menst, dass ich eine Textur mit Fließkommazahlformat an meine FBO binde dann ja. Auf [0..255] rechne ich hier nicht um. Sowohl beim Shadow-Map erstellen als auch beim Vergleichswert erstellen im nächsten Pass, normiere ich die Vektorlänge auf [0, 1]. Sollte ich hier irgendwo auf [0..255] umrechnen?

Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von »Fnord42« (26.09.2011, 13:04)


Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

9

26.09.2011, 13:04

Das kann ich im Detail nicht bewerten, da ich keine Ahnung von OpenGL habe. Normalerweise sind floats aber 32bit und doubles sogar 64bit. RGBA ist aber auf jeden Fall falsch - Du brauchst für eine einfache ShadowMap nur einen Kanal.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

Fnord42

Frischling

  • »Fnord42« ist der Autor dieses Themas

Beiträge: 18

Beruf: Student

  • Private Nachricht senden

10

26.09.2011, 13:16

Das kann ich im Detail nicht bewerten, da ich keine Ahnung von OpenGL habe. Normalerweise sind floats aber 32bit und doubles sogar 64bit. RGBA ist aber auf jeden Fall falsch - Du brauchst für eine einfache ShadowMap nur einen Kanal.
Ähh, ja natürlich 32 bzw. 64bit ^^
Die RGBA-Cubemap stammt noch aus Debugzeiten. Ich hab im Moment sowohl einen Depthbuffer als auch einen Farbbuffer, die beide mit Tiefenwerte beschrieben werden.
Später werde ich natürlich nur Depthbuffer verwenden.
Werde mal noch weiterforschen woran es genau liegen könnte, schonmal gut es auf den Tiefenvergleich eingrenzen zu können, vielen Dank :)

Werbeanzeige