Problema di mappatura dell'ombra omnidirezionale WebGL


9

Prima di tutto, voglio dire che ho letto molti post sulla mappatura dell'ombra usando mappe di profondità e cubemap e capisco come funzionano e inoltre, ho esperienza di lavoro con loro usando OpenGL, ma ho un problema con l'implementazione Tecnica di mappatura dell'ombra omnidirezionale che utilizza una sorgente luminosa a punto singolo nel mio motore di grafica 3D chiamato "EZ3". Il mio motore utilizza WebGL come API grafica 3D e JavaScript come linguaggio di programmazione, questo è per la tesi di laurea in Informatica.

Fondamentalmente è così che ho implementato il mio algoritmo di mappatura delle ombre, ma mi concentrerò solo sul caso delle luci puntiformi perché con loro posso archiviare la mappatura delle ombre omnidirezionale.

Innanzitutto, attivo la selezione del viso in questo modo:

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;
  }

In secondo luogo, creo un programma di profondità per registrare i valori di profondità per ogni faccia della mappa cubica, questo è il mio codice del programma di profondità in 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);
}

Frammento 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);
}

Terzo, questo è il corpo della mia funzione JavaScript che "archivia" la mappatura dell'ombra omnidirezionale

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 ...
    }
  }

In quarto luogo, è così che computo la mappatura dell'ombra omnidirezionale usando la mia mappa cubica di profondità nel mio Shader di vertice principale e Shader di frammenti:

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);
}

Frammento 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;
}

Infine, questo è il risultato che sto ottenendo, la mia scena ha un piano, un cubo e una sfera. Inoltre, la sfera luminosa rossa è la fonte di luce puntiforme:

Problema di mappatura dell'ombra omnidirezionale

Come puoi vedere, mi sembra che la cubemap del framebuffer della profondità della luce puntiforme non stia facendo una buona interpolazione tra le loro facce.

Fino ad ora, non ho idea di come risolverlo.


Sembrava una buona domanda: l'hai eliminata perché hai trovato la soluzione? In tal caso, è possibile ripristinarlo e pubblicare una risposta con la soluzione. La risposta alla tua domanda è incoraggiata e guadagni reputazione sia per la domanda che per la risposta. Inoltre può aiutare qualcun altro che ha un problema simile in futuro ...
trichoplax,

1
Ciao @trichoplax in realtà ho trovato la soluzione, condividerò la risposta con tutti rispondendo alla mia domanda. Onestamente ho eliminato la mia domanda perché pensavo che a nessuno importasse di questo problema.
czapata91,

1
A proposito, invece di modificare la domanda con "RISOLTO" nel titolo, è preferibile accettare la propria risposta. (Il sito potrebbe farti aspettare un giorno dopo la pubblicazione per farlo; non ricordo.)
Nathan Reed,

Hey! @NathanReed cambierò il titolo, grazie a tale proposito :)
czapata91

Risposte:


7

SOLUZIONE

Dopo un paio di giorni mi sono reso conto che stavo calcolando la mia matrice di proiezione usando un angolo FOV in gradi e dovrebbe essere in radianti . Ho fatto la conversione e ora tutto funziona alla grande. L'interpolazione tra le facce della cubemap del mio framebuffer di profondità è ora perfetta. Per questo motivo, è importante gestire l'angolo di ogni singola funzione trigonometrica in radianti.

Inoltre, mi sono reso conto che puoi calcolare la tua matrice di visualizzazione come ho detto nella domanda e in questo modo:

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

Questo approccio significa che il tuo punto di vista è posizionato al centro del punto luce e fai il rendering in ciascuna direzione della tua mappa cubica, ma quali sono queste direzioni? bene, queste direzioni vengono calcolate aggiungendo ogni obiettivo che ho nel blocco di interruttori (in base alla faccia di ciascuna mappa cubica) con la posizione del tuo punto luce .

Inoltre, non è necessario capovolgere Y-Coordinate della matrice di proiezione , in questo caso è ok inviare la matrice di proiezione prospettica di pointlight al tuo shader GLSL senza ridimensionarlo di (1, -1, 1) perché sto lavorando con trame che non hanno una coordinata Y capovolta , penso che dovresti capovolgere la coordinata Y della matrice di proiezione del tuo punto luce solo se stai lavorando con la coordinata Y di una trama capovolta , questo per avere un corretto effetto di mappatura dell'ombra omnidirezionale.

Infine, lascio qui la versione finale del mio algoritmo Omnidirezionale Shadow Mapping sul lato CPU / GPU. Sul lato CPU spiegherò ogni passo che devi fare per calcolare una corretta mappa d'ombra per la faccia di ogni cubemap. D'altro canto, nel lato GPU, spiegherò il mio shader di vertice / frammento del mio programma di profondità e la funzione di mappatura dell'ombra omnidirezionale nel mio shader di frammento principale, questo al fine di aiutare qualcuno che potrebbe apprendere questa tecnica o risolvere dubbi futuri su questo algoritmo :

processore

  // 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);
    }
  }

Sulla funzione renderMeshDepth ho:

  // 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

Programma di profondità Vertex Shader:

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

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

Programma di profondità Frammento Shader:

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);
}

Funzione di mappatura dell'ombra omnidirezionale nel mio shader di frammento principale:

// 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;
}

Qui hai un rendering finale dell'algoritmo

inserisci qui la descrizione dell'immagine

Divertiti a programmare una grafica meravigliosa, buona fortuna :)

CZ

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.