Point lights shadows via GL_TEXTURE_CUBE_MAP_ARRAY: refactor for one pass tehnique

I had implemented point light shadow mapping via GL_TEXTURE_CUBE_MAP_ARRAY.

It was working fine with rendering per cube face, but it was too slow.

Now I’m trying refactor renderer for one pass technique using geometry shader.

But something is wrong.

If I’m using glFramebufferTexture while rendering, then I have correct shadows for only first light, second one hasn’t shadow at all.
If I use glFramebufferTextureLayer (which working correct with rendering per face), then I have noisy mess for all lights.

map configuration:


    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, getId());
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_DEPTH_COMPONENT, getWidth(), getHeight(), 6 * lightsNumber, 0, GL_DEPTH_COMPONENT, GL_FLOAT, null);
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0);

Tank for answer!

I mean: How should I attach GL_TEXTURE_CUBE_MAP_ARRAY to binded buffer, if I need to render to all cube faces of current layer at once?

You have two options: glFramebufferTextureLayer() will attach a single face of a single cube map, so you need 6lightNumber rendering passes. glFramebufferTexture() will attach all faces of all cube maps (6lightNumber faces in total) to create a layered framebuffer, with rendering directed to a specific layer by setting gl_Layer in the geometry shader. You can use as many passes as you want; if you want to use one pass per cube map, you need to pass the index into the geometry shader as a uniform, so that you can set gl_Layer appropriately (it must be set to 6*cube+face).

There is no mechanism to directly bind a subset of layer-faces to a framebuffer. However, in OpenGL 4.3 onwards, you can use glTextureView() to create a texture which is an alias of some or all of another texture’s data. The underlying texture must be an immutable-format texture (i.e. it must have been created with glTexStorage* rather than glTexImage*).

If I use glFramebufferTexture() then I have depth map for first light only:


    glBindFramebuffer(GL_FRAMEBUFFER, shadowBuffer.getDepthMapFBO());
    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowBuffer.getTexture().getId(), lightNumber);

    glClear(GL_DEPTH_BUFFER_BIT);

    renderMeshes(scene, transformation);

    depthShaderProgram.unbind();
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

May be something wrong with geometry shader?


#version 430

layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;

uniform mat4 projLightViewMatrix[6];

void main() {
    for(int face = 0; face < 6; ++face) {
        gl_Layer = face;
        for(int i = 0; i < 3; ++i) {
            gl_Position = projLightViewMatrix[face] * gl_in[i].gl_Position;
            EmitVertex();
        }
        EndPrimitive();
    }
}

rendering pass per cube face:
[ATTACH=CONFIG]1766[/ATTACH]

rendering via GS by one pass per cubemap:
[ATTACH=CONFIG]1767[/ATTACH]


    glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowBuffer.getTexture().getId(), lightNumber);

The last parameter to glFramebufferTexture() is the mipmap level. I’m fairly sure that you shouldn’t be passing 0 here rather than [var]lightNumber[/var].


    for(int face = 0; face < 6; ++face) {
        gl_Layer = face;

You’re only rendering to the first 6 layers, i.e. the 6 faces of the first cube map in the array.

gl_Layer needs to be 6*cube+face to select a specific face of a specific cube map.

Thanks!

I set mipmap level to 0.
I fill all matrices for two lights:


#version 430

layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;

uniform mat4 projLightViewMatrix[12]; // just two light
uniform int lightNumber;

void main() {
  for (int lIdx = 0; lIdx < lightNumber; ++lIdx) {
    for(int face = 0; face < 6; ++face) {
        gl_Layer = lIdx * 6 + face;
        for(int i = 0; i < 3; ++i) {
            gl_Position = projLightViewMatrix[gl_Layer] * gl_in[i].gl_Position;
            EmitVertex();
        }
        EndPrimitive();
    }
  }
}


But it looks as other layer except 1st cube is empty.
(I also tried to use matrices of 1st cube for second light, and I should saw incorrect shadows, but I saw nothing.)

I mean It looks as shadows for layers of second cube were not been rendered anyway.

I have no fragment shader for depth rendering.

May be one pass technique requires it for some reason?

I could find any examples for both of them: “Texture Cube Array for several lights” with “one pass algorithm using GS” at the same time.

VS:


#version 430

layout (location=0) in vec3 position;


struct VertexUniform {
  mat4 modelMatrix;
};

layout (std430, binding = 0) buffer VertexUniformBlock {
  VertexUniform vertexUniform[];
};

void main() {
    gl_Position = vertexUniform[gl_InstanceID].modelMatrix * vec4(position, 1.0f);
}

GS:


#version 430

layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;

uniform mat4 projLightViewMatrix[60]; // just two light
uniform int lightNumber;

void main() {
  for (int lIdx = 0; lIdx < lightNumber; lIdx++) {
    for(int face = 0; face < 6; face++) {
      for(int i = 0; i < 3; i++) {
        gl_Layer = lIdx * 6 + face;
        gl_Position = projLightViewMatrix[gl_Layer] * gl_in[i].gl_Position;
        EmitVertex();
      }
      EndPrimitive();
    }
  }
}


Have no FS.

Are those shaders correct?

I don’t notice anything wrong with the code.

Ok.
But your answer let me think that problem it not located in the shaders.

Will try to investigate to “texture binding” and “buffer building” direction.

Thank you a lot!!!

May be here you can notice something wrong?


int texId = glGenTextures();
int depthMapFBO = glGenFramebuffers();
glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texId);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_DEPTH_COMPONENT, getWidth(), getHeight(), 6 * lightsNumber, 0, GL_DEPTH_COMPONENT, GL_FLOAT, (ByteBuffer) null);
glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, 0);

glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texId, 0);

int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
   throw new IllegalStateException("Could not create FrameBuffer: " + status);
}

glBindFramebuffer(GL_FRAMEBUFFER, 0);

...
// filling uniforms here
...


glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, texId, 0);
glClear(GL_DEPTH_BUFFER_BIT);
renderMeshes(scene, transformation);

depthShaderProgram.unbind();
glBindFramebuffer(GL_FRAMEBUFFER, 0);

My app working ok on intel GPU (without shadows for not first lights),
But on AMD GPU my OS freeze when I use more then one light.
This fact let me think that something wrong with actions video memory.
I suspect incorrect configuration in the code above.

OS Debian linux on both PC.

drivers:

  • intel PC: i915
  • ATI PC: andgpu

I try to replace glTexImage3D(…) by
glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_DEPTH_COMPONENT, getWidth(), getHeight(), 6 * lightsNumber);

and it produces GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT.

It is working fine with glTexImage3D only if lightsNumber = 1;

[QUOTE=nimelord;1291299]I try to replace glTexImage3D(…) by
glTexStorage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_DEPTH_COMPONENT, getWidth(), getHeight(), 6 * lightsNumber);

and it produces GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT.
[/QUOTE]
The second parameter (levels) must be at least 1.

And DEPTH_COMPONENT is ambiguous.

I changed glTexImage3D by glTexStorage3D using your notices.
The behavior the same: shadows cast for 1st light only.

Also I run it on win7 with AMD driver:
[ATTACH=CONFIG]1770[/ATTACH]

On this time I have no even point light itself and some of lighted textures became transparent:
[ATTACH=CONFIG]1771[/ATTACH]

I have confused completely. :slight_smile:
May be my scene FS incorrect?


#version 430

const int NUM_CASCADES = 3;


in vec2 gTexCoord;
in vec3 gMvVertexNormal;
in vec3 gMvVertexPos;
in vec4 gMlightviewVertexPos[NUM_CASCADES];
in mat4 gModelViewMatrix;
in vec4 gWPosition;


out vec4 fragColor;


struct Attenuation {
  float constant;
  float linear;
  float exponent;
  float distance;
};


struct PointLight {
  vec4 colour;
  vec4 position;
  float intensity;
  Attenuation att;
};


struct SpotLight {
  PointLight pl;
  vec4 conedir;
  float cutoff;
  mat4 projectionMatrix;
  mat4 lightViewMatrix;
};


struct DirectionalLight {
  vec4 colour;
  vec3 direction;
  float intensity;
};


struct Material {
  vec4 ambient;
//  vec4 diffuse;
//  vec4 specular;
  int hasTexture;
  int hasNormalMap;
  float reflectance;
};


struct Fog {
  int activeFog;
  vec4 colour;
  float density;
};


struct DistanceDiffusing {
  float density;
  float noEffectDistance;
};


uniform sampler2D texture_sampler;
uniform sampler2D normalMap;
uniform sampler2DArray dlShadowMap;
uniform samplerCubeArray plShadowMap;
uniform sampler2DArray slShadowMap;

uniform mat4 viewMatrix;
uniform vec3 ambientLight;
uniform float specularPower;
uniform Material material;
uniform DirectionalLight directionalLight;
uniform Fog fog;
uniform DistanceDiffusing distanceDiffusing;
uniform float cascadeFarPlanes[NUM_CASCADES];
uniform mat4 plProjectionMatrix;

layout (std430, binding = 1) buffer PointLightsBlock {
  PointLight pointLights[];
};

layout (std430, binding = 2) buffer SpotLightsBlock {
  SpotLight spotLights[];
};


vec4 ambientC;
vec4 diffuseC;
vec4 speculrC;


void setupColours(Material material, vec2 textCoord) {
  if (material.hasTexture == 1) {
    ambientC = texture(texture_sampler, textCoord);
    diffuseC = ambientC;
    speculrC = ambientC;
  } else {
    ambientC = material.ambient;
    diffuseC = ambientC;
    speculrC = ambientC;
  }
}


vec4 calcLightColour(vec4 light_colour, float light_intensity, vec3 position, vec3 to_light_dir, vec3 normal) {
  vec4 diffuseColour = vec4(0, 0, 0, 0);
  vec4 specColour = vec4(0, 0, 0, 0);

  // Diffuse Light
  float diffuseFactor = max(dot(normal, to_light_dir), 0.0);
  diffuseColour = diffuseC * light_colour * light_intensity * diffuseFactor;

  // Specular Light
  vec3 camera_direction = normalize(-position);
  vec3 from_light_dir = -to_light_dir;
  vec3 reflected_light = normalize(reflect(from_light_dir , normal));
  float specularFactor = max( dot(camera_direction, reflected_light), 0.0);
  specularFactor = pow(specularFactor, specularPower);
  specColour = speculrC * light_intensity  * specularFactor * material.reflectance * light_colour;

  return (diffuseColour + specColour);
}


vec4 calcPointLight(PointLight light, vec3 position, vec3 normal) {
  vec4 pLightPosition = viewMatrix * vec4(light.position.xyz, 1.0);
  vec3 light_direction = pLightPosition.xyz - position;
  float distance = length(light_direction);
//  if (distance > light.att.distance) {
//    return vec4(0,0,0,0);
//  }


  vec3 to_light_dir  = normalize(light_direction);
  vec4 light_colour = calcLightColour(vec4(light.colour.xyz, 1.0), light.intensity, position, to_light_dir, normal);

  // Apply Attenuation
  float attenuationInv = light.att.constant + light.att.linear * distance + light.att.exponent * distance * distance;
  return light_colour / attenuationInv;
}


vec4 calcDirectionalLight(DirectionalLight light, vec3 position, vec3 normal, vec3 normalizedDirection) {
  return calcLightColour(light.colour, light.intensity, position, -normalizedDirection, normal);
}

vec4 calcSpotLight(SpotLight light, vec3 position, vec3 normal) {

  // thare is may be optimization: calcPointLight do the same calculation: viewMatrix * vec4(light.pl.position, 1.0)
  vec4 sLightPosition = viewMatrix * vec4(light.pl.position.xyz, 1.0);
  vec3 light_direction = sLightPosition.xyz - position;
  vec3 to_light_dir  = normalize(light_direction);
  vec3 from_light_dir  = -to_light_dir;
  vec4 conedir = viewMatrix * vec4(light.conedir.xyz, 0.0);
  float spot_alfa = dot(from_light_dir, normalize(conedir.xyz));

  vec4 colour = vec4(0, 0, 0, 0);

  if ( spot_alfa > light.cutoff ) {
    colour = calcPointLight(light.pl, position, normal);
    colour *= (1.0 - (1.0 - spot_alfa)/(1.0 - light.cutoff));
  }
  return colour;
}

vec4 calcFog(float distance, vec4 colour, Fog fog, vec3 ambientLight, DirectionalLight dirLight) {
  vec4 fogColor = fog.colour * (vec4(ambientLight, 1) + dirLight.colour * dirLight.intensity);
  float ddFactor = distance * fog.density;
  float fogFactor = 1.0 / exp(ddFactor * ddFactor);
  fogFactor = clamp( fogFactor, 0.0, 1.0 );

  vec4 resultColour = mix(fogColor, colour, fogFactor);
  return resultColour;
}


vec4 calcDistanceDiffusing(float distance, vec4 colour, DistanceDiffusing distanceDiffusing) {
  if (distance <= distanceDiffusing.noEffectDistance) {
    return colour;
  }
  float ddFactor = (distance - distanceDiffusing.noEffectDistance) * distanceDiffusing.density;
  float distanceTransparencyFactor = 1 / exp(ddFactor * ddFactor);
  distanceTransparencyFactor = clamp( distanceTransparencyFactor, 0.0, 1.0 );

  vec4 resultColour = vec4(colour.xyz, distanceTransparencyFactor);
  return resultColour;
}


vec3 calcNormal(Material material, vec3 normal, vec2 text_coord, mat4 modelViewMatrix) {
  vec3 newNormal = normal;
  if ( material.hasNormalMap == 1 ) {
    newNormal = texture(normalMap, text_coord).rgb;
    newNormal = normalize(newNormal * 2 - 1);
    newNormal = normalize(modelViewMatrix * vec4(newNormal, 0.0)).xyz;
  }
  return newNormal;
}


float calcPlShadow(vec3 position, PointLight light, int idx) {


  mat4 plLightViewMatrix = mat4(1,0,0,0,
                    0,1,0,0,
                    0,0,1,0,
                    -light.position.x,-light.position.y,-light.position.z,1);

  vec4 position_ls = plLightViewMatrix * gWPosition;
  vec4 abs_position = abs(position_ls);
  float fs_z = -max(abs_position.x, max(abs_position.y, abs_position.z));
  vec4 clip = plProjectionMatrix * vec4(0.0, 0.0, fs_z, 1.0);
  float depth = (clip.z / clip.w) * 0.5 + 0.5;
  float result = texture(plShadowMap, vec4(position_ls.xyz, idx)).r;
  //float result = texture(plShadowMap, vec4(position_ls.xyz, 0 + 1)).r;

  if (result >= depth - 0.0002) {
    return 1.0;
  } else {
    return 0.0;
  }
}


float calcSlShadow(vec3 position, SpotLight light, int idx) {
  vec4 position_ls = light.lightViewMatrix * gWPosition;
  vec4 abs_position = abs(position_ls);
  float fs_z = -abs_position.z;
  vec4 clip = light.projectionMatrix * vec4(0.0, 0.0, fs_z, 1.0);
  vec4 projCoords = (light.projectionMatrix * position_ls) / clip.w * 0.5 + 0.5;
  float depth = (clip.z / clip.w) * 0.5 + 0.5;
  float result = texture(slShadowMap, vec3(projCoords.xy, idx)).r;
  if (result >= depth - 0.0002) {
    return 1.0;
  } else {
    return 0.0;
  }
}

float calcShadow(vec4 position, int idx, vec3 normal, vec3 lightNormal) {

  vec3 projCoords = position.xyz;
  // Transform from screen coordinates to texture coordinates
  projCoords = projCoords * 0.5 + 0.5;

  //float bias = 0.005 * tan(acos(dot( normal, lightNormal )));
  //bias = clamp(bias, 0, 0.01);
  float bias = 0.0005;

  float shadowFactor = 0.0;
  vec2 mapSize = textureSize(dlShadowMap, 0).xy;
  vec2 inc = 1.0 / mapSize;
  for(int row = -1; row <= 1; row++) {
    for(int col = -1; col <= 1; col++) {
      float textDepth = texture(dlShadowMap, vec3((projCoords.xy + vec2(row, col) * inc), idx)).r;
      shadowFactor += projCoords.z - bias > textDepth ? 1.0 : 0.0;
    }
  }
  shadowFactor /= 9.0;
  if(projCoords.z > 1.0) {
    shadowFactor = 1.0;
  }
  if(shadowFactor < 1.0 / 9.0) {
    shadowFactor = 0.0;
  }
  return 1 - shadowFactor;
}

void main() {

  setupColours(material, gTexCoord);

  vec3 currNomal = calcNormal(material, gMvVertexNormal, gTexCoord, gModelViewMatrix);

  vec3 normalizedDirLightDirection = normalize((viewMatrix * vec4(directionalLight.direction, 0)).xyz);
  vec4 directionalLightResult = calcDirectionalLight(directionalLight, gMvVertexPos, currNomal, normalizedDirLightDirection);

  vec4 aroundLight = vec4(0,0,0,0);



  for (int i=0; i<pointLights.length(); i++) {
    float shadow = 1;
    vec4 currPointLight = calcPointLight(pointLights[i], gMvVertexPos, currNomal);
    shadow = calcPlShadow(gMvVertexPos, pointLights[i], i);
    aroundLight += (currPointLight * shadow);
    //aroundLight += currPointLight;
  }

  for (int i=0; i<spotLights.length(); i++) {
    float shadow = 1;
    vec4 currSpotLight = calcSpotLight(spotLights[i], gMvVertexPos, currNomal);
    shadow = calcSlShadow(gMvVertexPos, spotLights[i], i);
    aroundLight += (currSpotLight * shadow);
  }
  int idx;
  for (int i=0; i<NUM_CASCADES; i++) {
    if ( abs(gMvVertexPos.z) < cascadeFarPlanes[i] ) {
      idx = i;
      break;
    }
  }
  float shadow = calcShadow(gMlightviewVertexPos[idx], idx, currNomal, normalizedDirLightDirection);
  fragColor = clamp(ambientC * vec4(ambientLight, 1) + directionalLightResult * shadow + aroundLight, 0, 1);
  float distance = length(gMvVertexPos);
  if ( fog.activeFog == 1 ) {
    fragColor = calcFog(distance, fragColor, fog, ambientLight, directionalLight);
  }
  fragColor = calcDistanceDiffusing(distance, fragColor, distanceDiffusing);
}

Another one interesting fact: if I render “pass per light”, every pass for 6 faces of current point light, then it working fine.

So rendering into layers of texture cube array is working well via GS:


#version 430

layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;

uniform mat4 projLightViewMatrix[6];
uniform int lightNumber;

void main() {
  for(int face = 0; face < 6; face++) {
    int layer = lightNumber * 6 + face;
    for(int i = 0; i < 3; i++) {
      gl_Layer = layer;
      gl_Position = projLightViewMatrix[face] * gl_in[i].gl_Position;
      EmitVertex();
    }
    EndPrimitive();
  }
}

Oh, my god!!!

All this time I was using that:


layout (triangle_strip, max_vertices=18) out;

18 it is only for one cube, if I need n cubes I must using 18 * n.
What a simple point!

Thank you guys for your support!

PS: AMDGPU driver freeze all graphic UI if I emit more vertex than “max_vertices”.