WebGL Çok Yönlü Gölge Eşleme Sorunu


9

Her şeyden önce, derinlik haritaları ve küp haritaları kullanarak gölge haritalama hakkında birçok yazı okuduğumu ve nasıl çalıştıklarını anladığımı ve ayrıca OpenGL kullanarak onlarla çalışma deneyimim olduğunu söylemek istiyorum, ancak uygulamada bir sorunum var 3D grafik motorumda "EZ3" adlı tek noktalı bir ışık kaynağı kullanan çok yönlü Gölge Eşleme tekniği. Motorum WebGL'yi 3D grafik API ve JavaScript'i programlama dili olarak kullanıyor, bu benim lisansımın Bilgisayar Bilimi tezi için.

Temel olarak gölge haritalama algoritmamı bu şekilde uyguladım, ancak sadece nokta ışıkları durumuna odaklanacağım çünkü onlarla çok yönlü gölge haritalamayı arşivleyebilirim.

İlk olarak, ön yüz culling böyle aktif:

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

İkincisi, her cubemap yüzü için derinlik değerleri kaydetmek için bir derinlik programı oluşturuyorum, bu GLSL 1.0'daki derinlik program kodum:

Köşe Gölgelendirici:

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

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

Fragman Gölgelendiricisi:

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

Üçüncüsü, bu benim çok fonksiyonlu gölge eşlemesini "arşivleyen" JavaScript işlevimin gövdesi

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

Dördüncüsü, ana Vertex Shader ve Fragment Shader'ımdaki derinlik cubemap'ımı kullanarak Çok Yönlü Gölge Eşlemesini şu şekilde hesaplıyorum:

Köşe Gölgelendirici:

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

Fragman Gölgelendiricisi:

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

Sonunda, elde ettiğim sonuç bu, sahnemde bir uçak, bir küp ve bir küre var. Ayrıca, kırmızı parlak küre nokta ışık kaynağıdır:

Çok Yönlü Gölge Eşleme Sorunu

Gördüğünüz gibi, nokta ışık derinliği çerçevesinin kübap haritasının yüzleri arasında iyi bir enterpolasyon yapmaması gibi görünüyor.

Şimdiye kadar, bunu nasıl çözeceğime dair hiçbir fikrim yok.


Bu iyi bir soru gibi görünüyordu - çözümü bulduğunuz için sildiniz mi? Öyleyse silme işlemini geri alabilir ve çözümünüzle bir yanıt gönderebilirsiniz. Kendi sorunuzu cevaplamak teşvik edilir ve hem soru hem de cevap için itibar kazanırsınız. Artı gelecekte benzer bir sorunu olan başka birine yardımcı olabilir ...
trichoplax

1
Merhaba @trichoplax aslında çözümü buldum, cevabı kendi sorumu yanıtlayan herkesle paylaşacağım. Dürüst olmak gerekirse sorumu sildim, çünkü kimsenin bu konuyla ilgilenmediğini düşündüm.
czapata91

1
BTW, soruyu başlığında "ÇÖZÜLMÜŞ" ile düzenlemek yerine, yalnızca kendi cevabınızı kabul etmeniz tercih edilir. (Site bunu yapmak için gönderdikten bir gün sonra sizi bekletebilir; hatırlamıyorum.)
Nathan Reed

Hey! @NathanReed Başlığı değiştireceğim, bunun için teşekkürler :)
czapata91

Yanıtlar:


7

ÇÖZÜM

Birkaç gün sonra , projeksiyon matrisimi derece olarak FOV açısı kullanarak hesapladığımı fark ettim ve radyan cinsinden olmalı . Dönüşümü yaptım ve şimdi her şey harika çalışıyor. Derinlik framebuffer'ımın kübapımın yüzleri arasındaki enterpolasyon artık mükemmel. Bu nedenle, her bir trigonometrik fonksiyonun açısını radyan cinsinden ele almak önemlidir.

Ayrıca, soru matrisinizi soruda söylediğim gibi ve bu şekilde hesaplayabileceğinizi fark ettim:

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

Bu yaklaşım, bakış açınızın nokta ışığının merkezine yerleştirildiği ve sadece kübinizin haritasının her yönünde oluşturduğunuz anlamına gelir, ancak bu yönler hangileridir? kuyu, bu yönlendirmeler pointlight konumu ile ben (her cubemap yüzüne göre) anahtar bloğunda getirdiğiniz her hedefi eklemek hesaplanır .

Dahası, O projeksiyon matrisi Y-Koordinat çevirmek için gerekli değildir , bu durumda, Bu sizin GLSL gölgelendiriciye Tamam sevk pointlight bakış açısı projeksiyon matrisi tarafından It ölçeklendirilmeden (1, -1, 1) çünkü ben çalışıyorum bir çevrilmiş Y-Koordinat yok dokular , ben size pointlight en projeksiyon matrisi Y Koordinat çevirmek gerektiğini düşünüyorum çalıştığınız yalnızca bir çevrilmiş doku Y-Koordinat , bu doğru yönlü gölge haritalama etkisi için.

Son olarak, CPU / GPU tarafında Çok Yönlü Gölge Eşleme algoritmamın son sürümünü burada bırakacağım. CPU tarafında, her bir küp haritasının yüzü için doğru bir gölge haritası hesaplamak için yapmanız gereken her adımı açıklayacağım. Öte yandan GPU tarafında, bu programı öğrenebilecek veya bu algoritma hakkında gelecekteki şüpheleri çözebilecek birine yardımcı olmak için derinlik programımın tepe / parça gölgelendiricisini ve ana parça gölgelendiricimdeki çok yönlü gölge haritalama işlevini açıklayacağım. :

İşlemci

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

RenderMeshDepth fonksiyonunda:

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

Derinlik Programı Köşe Gölgelendirici:

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

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

Derinlik Programı Parça Gölgelendiricisi:

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

Ana parça gölgelendiricimdeki Çok Yönlü Gölge Eşleme işlevi:

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

Burada algoritmanın son bir render'ı var

resim açıklamasını buraya girin

Güzel grafikler, iyi şanslar kodlama eğlenin :)

CZ

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.