Problème de mappage omnidirectionnel WebGL

9

Tout d'abord, je tiens à dire que j'ai lu beaucoup de messages sur la cartographie des ombres à l'aide de cartes de profondeur et de cubemaps et je comprends comment ils fonctionnent et j'ai également une expérience de travail avec eux en utilisant OpenGL, mais j'ai un problème de mise en œuvre Technique omnidirectionnelle de mappage d'ombres utilisant une source de lumière ponctuelle dans mon moteur graphique 3D nommé "EZ3". Mon moteur utilise WebGL comme API graphique 3D et JavaScript comme langage de programmation, c'est pour ma thèse de licence en informatique.

Fondamentalement, c'est ainsi que j'ai mis en œuvre mon algorithme de mappage d'ombres, mais je ne me concentrerai que sur le cas des lumières ponctuelles, car avec elles, je peux archiver le mappage d'ombres omnidirectionnel.

Tout d'abord, j'active l'abattage de la face avant comme ceci:

if (this.state.faceCulling !== Material.FRONT) {
    if (this.state.faceCulling === Material.NONE)
      gl.enable(gl.CULL_FACE);

    gl.cullFace(gl.FRONT);
    this.state.faceCulling = Material.FRONT;
  }

Deuxièmement, je crée un programme de profondeur afin d'enregistrer des valeurs de profondeur pour chaque face cubemap, voici mon code de programme de profondeur dans GLSL 1.0:

Vertex Shader:

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

void main() {
  gl_Position = uProjection * uModelView * vec4(position, 1.0);
}

Fragment Shader:

precision highp float;

vec4 packDepth(const in float depth) {
  const vec4 bitShift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0);
  const vec4 bitMask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0);
  vec4 res = mod(depth * bitShift * vec4(255), vec4(256)) / vec4(255);
  res -= res.xxyz * bitMask;
  return res;
}

void main() {
  gl_FragData[0] = packDepth(gl_FragCoord.z);
}

Troisièmement, c'est le corps de ma fonction JavaScript qui «archive» le mappage omnidirectionnel des ombres

program.bind(gl);

  for (i = 0; i < lights.length; i++) {
    light = lights[i];

    // Updates pointlight's projection matrix

    light.updateProjection();

    // Binds point light's depth framebuffer

    light.depthFramebuffer.bind(gl);

    // Updates point light's framebuffer in order to create it 
    // or if it's resolution changes, it'll be created again.

    light.depthFramebuffer.update(gl);

    // Sets viewport dimensions with depth framebuffer's dimensions

    this.viewport(new Vector2(), light.depthFramebuffer.size);

    if (light instanceof PointLight) {

      up = new Vector3();
      view = new Matrix4();
      origin = new Vector3();
      target = new Vector3();

      for (j = 0; j < 6; j++) {

    // Check in which cubemap's face we are ...

        switch (j) {
          case Cubemap.POSITIVE_X:
            target.set(1, 0, 0);
            up.set(0, -1, 0);
            break;
          case Cubemap.NEGATIVE_X:
            target.set(-1, 0, 0);
            up.set(0, -1, 0);
            break;
          case Cubemap.POSITIVE_Y:
            target.set(0, 1, 0);
            up.set(0, 0, 1);
            break;
          case Cubemap.NEGATIVE_Y:
            target.set(0, -1, 0);
            up.set(0, 0, -1);
            break;
          case Cubemap.POSITIVE_Z:
            target.set(0, 0, 1);
            up.set(0, -1, 0);
            break;
          case Cubemap.NEGATIVE_Z:
            target.set(0, 0, -1);
            up.set(0, -1, 0);
            break;
        }

    // Creates a view matrix using target and up vectors according to each face of pointlight's
    // cubemap. Furthermore, I translate it in minus light position in order to place
    // the point light in the world's origin and render each cubemap's face at this 
    // point of view

        view.lookAt(origin, target, up);
        view.mul(new EZ3.Matrix4().translate(light.position.clone().negate()));

    // Flips the Y-coordinate of each cubemap face
    // scaling the projection matrix by (1, -1, 1).

    // This is a perspective projection matrix which has:
    // 90 degress of FOV.
    // 1.0 of aspect ratio.
    // Near clipping plane at 0.01.
    // Far clipping plane at 2000.0.

        projection = light.projection.clone();
        projection.scale(new EZ3.Vector3(1, -1, 1));

    // Attaches a cubemap face to current framebuffer in order to record depth values for the face with this line
    // gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X + j, id, 0);

        light.depthFramebuffer.texture.attach(gl, j);

    // Clears current framebuffer's color with these lines:
    // gl.clearColor(1.0,1.0,1.0,1.0);
    // gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        this.clear(color);

    // Renders shadow caster meshes using the depth program

        for (k = 0; k < shadowCasters.length; k++)
          this._renderShadowCaster(shadowCasters[k], program, view, projection);
      }
    } else {
       // Directional light & Spotlight case ...
    }
  }

Quatrièmement, c'est ainsi que je calcule la cartographie omnidirectionnelle des ombres en utilisant mon cubemap de profondeur dans mon Vertex Shader & Fragment Shader principal:

Vertex Shader:

precision highp float;

attribute vec3 position;

uniform mat4 uModel;
uniform mat4 uModelView;
uniform mat4 uProjection;

varying vec3 vPosition;

void main() {
  vPosition = vec3(uModel * vec4(position, 1.0));

  gl_Position = uProjection * uModelView * vec4(position, 1.0);
}

Fragment Shader:

float unpackDepth(in vec4 color) {
    return dot(color, vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0 ));
}

float pointShadow(const in PointLight light, const in samplerCube shadowSampler) {
    vec3 direction = vPosition - light.position;
    float vertexDepth = clamp(length(direction), 0.0, 1.0);
    float shadowMapDepth = unpackDepth(textureCube(shadowSampler, direction));

    return (vertexDepth > shadowMapDepth) ? light.shadowDarkness : 1.0;
}

Enfin, c'est le résultat que j'obtiens, ma scène a un plan, un cube et une sphère. En outre, la sphère lumineuse rouge est la source lumineuse ponctuelle:

Problème de mappage d'ombre omnidirectionnel

Comme vous pouvez le voir, il me semble que le cubemap de framebuffer de profondeur de lumière ponctuelle ne fait pas une bonne interpolation entre leurs faces.

Jusqu'à présent, je ne sais pas comment résoudre ce problème.

czapata91
la source
Cela semblait une bonne question - l'avez-vous supprimée parce que vous avez trouvé la solution? Si c'est le cas, vous pouvez la restaurer et publier une réponse avec votre solution. Répondre à votre propre question est encouragé et vous gagnez en réputation à la fois pour la question et la réponse. De plus, cela peut aider quelqu'un d'autre qui a un problème similaire à l'avenir ...
trichoplax
1
Bonjour @trichoplax en fait j'ai trouvé la solution, je vais partager la réponse avec tous ceux qui répondent à ma propre question. Honnêtement, j'ai supprimé ma question parce que je pensais que personne ne se souciait de ce problème.
czapata91
1
BTW, au lieu de modifier la question avec "RESOLU" dans le titre, il est préférable d'accepter simplement votre propre réponse. (Le site pourrait vous faire attendre un jour après avoir posté pour le faire; je ne me souviens pas.)
Nathan Reed
Hey! @NathanReed Je vais changer le titre, merci à ce sujet :)
czapata91

Réponses:

7

SOLUTION

Après quelques jours, j'ai réalisé que je calculais ma matrice de projection en utilisant un angle de champ de vision en degrés et qu'elle devrait être en radians . J'ai fait la conversion et maintenant tout fonctionne très bien. L'interpolation entre les faces du cubemap de mon framebuffer de profondeur est maintenant parfaite. Pour cette raison, il est important de gérer l'angle de chaque fonction trigonométrique en radians.

De plus, j'ai réalisé que vous pouvez calculer votre matrice de vue soit comme je l'ai dit dans la question et de cette façon:

view.lookAt(position, target.add(position.clone()), up);

Cette approche signifie que votre point de vue est placé au centre du projecteur et que vous effectuez un rendu dans chaque direction de votre cubemap, mais quelles sont ces directions? Eh bien, ces directions sont calculées en ajoutant chaque cible que j'ai dans le bloc de commutation (en fonction de la face de chaque cubemap) avec la position de votre projecteur .

De plus, il n'est pas nécessaire de retourner la coordonnée Y de la matrice de projection , dans ce cas, il est correct d'envoyer la matrice de projection en perspective du projecteur à votre shader GLSL sans la mettre à l'échelle par (1, -1, 1) car je travaille avec les textures qui n'ont pas de coordonnée Y inversée , je pense que vous ne devriez inverser la coordonnée Y de la matrice de projection de votre point lumineux que si vous travaillez avec la coordonnée Y d'une texture inversée , ceci afin d'avoir un effet de mappage d'ombre omnidirectionnel correct.

Enfin, je vais laisser ici la version finale de mon algorithme Omnidirectional Shadow Mapping côté CPU / GPU. Côté CPU, j'expliquerai chaque étape que vous devez faire pour calculer une carte d'ombre correcte pour le visage de chaque cubemap. D'un autre côté côté GPU, j'expliquerai le vertex / fragment shader de mon programme de profondeur et la fonction de mappage omnidirectionnel des ombres dans mon fragment shader principal, ceci afin d'aider quelqu'un qui pourrait apprendre cette technique, ou résoudre les doutes futurs sur cet algorithme. :

CPU

  // Disable blending and enable front face culling.

  this.state.disable(gl.BLEND);

  this.state.enable(gl.CULL_FACE);
  this.state.cullFace(gl.FRONT);

  // Binds depth program

  program.bind(gl);

  // For each pointlight source do

  for (i = 0; i < lights.length; i++) {
    light = lights[i];

    // Get each pointlight's world position

    position = light.worldPosition();

    // Binds pointlight's depth framebuffer. Besides, in this function,
    // viewport's dimensions are set according to depth framebuffer's dimension.

    light.depthFramebuffer.bind(gl, this.state);

    // Updates point light's framebuffer in order to create it 
    // or if it's resolution have changed, it'll be created again.

    light.depthFramebuffer.update(gl);

    // Check in which cubemap's face we are ...

    for (j = 0; j < 6; j++) {
      switch (j) {
        case Cubemap.POSITIVE_X:
          target.set(1, 0, 0);
          up.set(0, -1, 0);
          break;
        case Cubemap.NEGATIVE_X:
          target.set(-1, 0, 0);
          up.set(0, -1, 0);
          break;
        case Cubemap.POSITIVE_Y:
          target.set(0, 1, 0);
          up.set(0, 0, 1);
          break;
        case Cubemap.NEGATIVE_Y:
          target.set(0, -1, 0);
          up.set(0, 0, -1);
          break;
        case Cubemap.POSITIVE_Z:
          target.set(0, 0, 1);
          up.set(0, -1, 0);
          break;
        case Cubemap.NEGATIVE_Z:
          target.set(0, 0, -1);
          up.set(0, -1, 0);
          break;
      }

      // Creates a view matrix using target and up vectors 
      // according to each face of pointlight's cubemap.

      view.lookAt(position, target.add(position.clone()), up);

      // Attaches cubemap's face to current framebuffer 
      // in order to record depth values in that direction.

      light.depthFramebuffer.texture.attach(gl, j);

      // Clears color & depth buffers of your current framebuffer

      this.clear();

      // Render each shadow caster mesh using your depth program

      for (k = 0; k < meshes.length; k++)
        this._renderMeshDepth(program, meshes[k], view, light.projection);
    }
  }

Sur la fonction renderMeshDepth, j'ai:

  // Computes pointlight's model-view matrix 

  modelView.mul(view, mesh.world);

  // Dispatch each matrix to the GLSL depth program

  program.loadUniformMatrix(gl, 'uModelView', modelView);
  program.loadUniformMatrix(gl, 'uProjection', projection);

  // Renders a mesh using vertex buffer objects (VBO)

  mesh.render(gl, program.attributes, this.state, this.extensions);

GPU

Programme de profondeur Vertex Shader:

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

void main() {
  gl_Position = uProjection * uModelView * vec4(position, 1.0);
}

Shader de fragment de programme de profondeur:

precision highp float;

// The pack function distributes fragment's depth precision storing 
// it throughout (R,G,B,A) color channels and not just R color channel 
// as usual in shadow mapping algorithms. This is because I'm working
// with 8-bit textures and one color channel hasn't enough precision 
// to store a depth value.

vec4 pack(const in float depth) {
  const vec4 bitShift = vec4(255.0 * 255.0 * 255.0, 255.0 * 255.0, 255.0, 1.0);
  const vec4 bitMask = vec4(0.0, 1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0);

  vec4 res = fract(depth * bitShift);
  res -= res.xxyz * bitMask;

  return res;
}

void main() {
  // Packs normalized fragment's Z-Coordinate which is in [0,1] interval.

  gl_FragColor = pack(gl_FragCoord.z);
}

Fonction Omnidirectionnelle Shadow Mapping dans mon shader de fragment principal:

// Unpacks fragment's Z-Coordinate which was packed 
// on the depth program's fragment shader.

float unpack(in vec4 color) {
   const vec4 bitShift = vec4(1.0 / (255.0 * 255.0 * 255.0), 1.0 / (255.0 * 255.0), 1.0 / 255.0, 1.0);
   return dot(color, bitShift);
}

// Computes Omnidirectional Shadow Mapping technique using a samplerCube
// vec3 lightPosition is your pointlight's position in world coordinates.
// vec3 vPosition is your vertex's position in world coordinates, in code
// I mean this -> vPosition = vec3(uModel * vec4(position, 1.0));
// where uModel is your World/Model matrix.

float omnidirectionalShadow(in vec3 lightPosition, in float bias, in float darkness, in samplerCube sampler) {
    vec3 direction = vPosition - lightPosition;
    float vertexDepth = clamp(length(direction), 0.0, 1.0);
    float shadowMapDepth = unpack(textureCube(sampler, direction)) + bias;

    return (vertexDepth > shadowMapDepth) ? darkness : 1.0;
}

Ici vous avez un rendu final de l'algorithme

entrez la description de l'image ici

Amusez-vous à coder de beaux graphiques, bonne chance :)

CZ

czapata91
la source