Masalah Pemetaan Bayangan Omnidirectional WebGL

9

Pertama-tama, saya ingin mengatakan bahwa saya telah membaca banyak posting tentang pemetaan bayangan menggunakan peta kedalaman dan cubemaps dan saya mengerti bagaimana mereka bekerja dan juga, saya memiliki pengalaman kerja dengan mereka menggunakan OpenGL, tetapi, saya memiliki masalah dalam mengimplementasikan Teknik Pemetaan Bayangan Omnidirectional menggunakan sumber cahaya satu titik di mesin grafis 3D saya bernama "EZ3". Mesin saya menggunakan WebGL sebagai API grafik 3D dan JavaScript sebagai bahasa pemrograman, ini untuk tesis sarjana saya di bidang Ilmu Komputer.

Pada dasarnya ini adalah bagaimana saya menerapkan algoritma pemetaan bayangan saya, tetapi saya hanya akan fokus pada case point lights karena dengan mereka saya dapat mengarsipkan pemetaan bayangan omnidirectional.

Pertama, saya aktif memusnahkan wajah depan:

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

Kedua, saya membuat program kedalaman untuk merekam nilai kedalaman untuk setiap wajah cubemap, ini adalah kode program kedalaman saya di 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);
}

Shader fragmen:

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

Ketiga, ini adalah fungsi fungsi JavaScript saya yang "memetakan" pemetaan bayangan omnidirectional

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

Keempat, inilah cara saya menghitung Pemetaan Bayangan Omnidirectional menggunakan cubemap kedalaman saya di Vertex Shader & Fragment Shader utama saya:

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

Shader fragmen:

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

Akhirnya, ini adalah hasil yang saya dapatkan, adegan saya memiliki pesawat, kubus dan bola. Selain itu, bola merah terang adalah sumber cahaya titik:

Masalah Pemetaan Bayangan Omnidirectional

Seperti yang Anda lihat, saya tampak seperti cubemap framebuffer kedalaman titik cahaya itu tidak melakukan interpolasi yang baik di antara wajah mereka.

Sampai sekarang, saya tidak tahu bagaimana menyelesaikan ini.

czapata91
sumber
Ini sepertinya pertanyaan yang bagus - apakah Anda menghapusnya karena Anda menemukan solusinya? Jika demikian, Anda dapat membatalkan penghapusan dan mengirim jawaban dengan solusi Anda. Menjawab pertanyaan Anda sendiri dianjurkan dan Anda mendapatkan reputasi untuk pertanyaan dan jawabannya. Plus itu dapat membantu orang lain yang memiliki masalah serupa di masa depan ...
trichoplax
1
Halo @trichoplax sebenarnya saya menemukan solusinya, saya akan membagikan jawabannya dengan semua orang yang menjawab pertanyaan saya sendiri. Jujur saya menghapus pertanyaan saya karena saya pikir tidak ada yang peduli dengan masalah ini.
czapata91
1
BTW, alih-alih mengedit pertanyaan dengan "ASK" pada judul, lebih baik menerima saja jawaban Anda sendiri. (Situs ini mungkin membuat Anda menunggu sehari setelah memposting untuk melakukan itu; saya tidak ingat.)
Nathan Reed
Hei! @NathanReed Saya akan mengubah judul, terima kasih tentang itu :)
czapata91

Jawaban:

7

LARUTAN

Setelah beberapa hari saya menyadari bahwa saya sedang menghitung matriks proyeksi saya menggunakan sudut FOV dalam derajat dan itu harus dalam radian . Saya membuat pertobatan dan sekarang semuanya bekerja dengan baik. Interpolasi di antara wajah-wajah cubemap framebuffer kedalaman saya sekarang sempurna. Untuk alasan ini, penting untuk menangani setiap sudut fungsi trigonometri tunggal dalam radian.

Selain itu, saya menyadari bahwa Anda dapat menghitung matriks tampilan Anda seperti yang saya katakan dalam pertanyaan dan dengan cara ini:

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

Pendekatan ini berarti bahwa sudut pandang Anda ditempatkan di tengah titik cahaya dan Anda hanya merender di setiap arah cubemap Anda, tetapi yang mana arahnya? baik, arah ini dihitung menambahkan setiap target yang saya miliki di blok switch (sesuai dengan wajah masing-masing cubemap) dengan posisi titik cahaya Anda .

Selain itu, Anda tidak perlu membalik Y-Koordinat dari matriks proyeksi , Dalam hal ini, tidak apa-apa mengirimkan matriks proyeksi sudut pandang pointlight ke GLSL shader Anda tanpa mengubah skala dengan (1, -1, 1) karena saya bekerja dengan tekstur yang tidak memiliki Koordinat Y terbalik , saya pikir Anda harus membalik Koordinat Y dari matriks proyeksi titik Anda hanya jika Anda bekerja dengan Koordinat Y tekstur membalik , ini agar memiliki efek pemetaan bayangan omnidirectional yang benar.

Akhirnya, saya akan meninggalkan di sini versi final dari algoritma Omnidirectional Shadow Mapping saya di sisi CPU / GPU. Di sisi CPU saya akan menjelaskan setiap langkah yang harus Anda lakukan untuk menghitung peta bayangan yang benar untuk wajah setiap cubemap. Di sisi lain di sisi GPU, saya akan menjelaskan fungsi vertex / fragmen shader program saya dan fungsi pemetaan bayangan omnidirectional di shader fragmen utama saya, ini untuk membantu seseorang yang dapat mempelajari teknik ini, atau menyelesaikan keraguan di masa depan tentang algoritma ini :

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

Pada fungsi renderMeshDepth saya sudah:

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

Program kedalaman 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 Fragment Shader Program Kedalaman:

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

Fungsi Pemetaan Bayangan Omnidirectional di shader fragmen utama saya:

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

Di sini Anda memiliki render akhir algoritma

masukkan deskripsi gambar di sini

Bersenang-senang mengode gambar yang indah, semoga berhasil :)

CZ

czapata91
sumber