WebGL 전 방향 그림자 매핑 문제


9

우선, 깊이 맵과 큐브 맵을 사용하여 그림자 매핑에 대한 많은 게시물을 읽었으며 작동 방식을 이해하고 OpenGL을 사용하여 작업 경험을 가지고 있지만 구현에 문제가 있다고 말하고 싶습니다. "EZ3"라는 내 3D 그래픽 엔진에서 단일 포인트 광원을 사용하는 전 방향 그림자 매핑 기술. 내 엔진은 WebGL을 3D 그래픽 API로 사용하고 JavaScript는 프로그래밍 언어로 사용합니다. 이것은 컴퓨터 과학 학사 학위 논문입니다.

기본적으로 이것이 그림자 매핑 알고리즘을 구현 한 방법이지만 전 방향 그림자 매핑을 보관할 수 있기 때문에 포인트 라이트 케이스에만 초점을 맞출 것입니다.

먼저 다음과 같이 전면 컬링을 활성화했습니다.

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

둘째, 각 큐브 맵면의 깊이 값을 기록하기 위해 깊이 프로그램을 만듭니다. 이것은 GLSL 1.0의 깊이 프로그램 코드입니다.

정점 셰이더 :

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

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

조각 쉐이더 :

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

셋째, 이것은 전 방향 그림자 매핑을 "보관"하는 JavaScript 함수의 본문입니다.

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

넷째, 메인 버텍스 쉐이더 및 프래그먼트 쉐이더에서 깊이 큐브 맵을 사용하여 전 방향 그림자 매핑을 계산하는 방법입니다.

정점 셰이더 :

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

조각 쉐이더 :

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

마지막으로 이것은 내가 얻는 결과이며, 내 장면에는 평면, 큐브 및 구가 있습니다. 게다가, 붉은 밝은 구체는 점 광원입니다 :

전 방향 그림자 매핑 문제

보시다시피, 포인트 라이트 깊이 프레임 버퍼의 큐브 맵처럼 보이지만 얼굴 사이의 보간이 잘 이루어지지 않습니다.

지금까지 나는 이것을 해결하는 방법을 모른다.


이것은 좋은 질문처럼 보였습니다. 해결책을 찾았 기 때문에 삭제 했습니까? 그렇다면 삭제를 취소하고 솔루션에 답변을 게시 할 수 있습니다. 자신의 질문에 대답하는 것이 권장되며 질문과 답변 모두에 대한 명성을 얻습니다. 또한 나중에 비슷한 문제가있는 다른 사람을 도울 수도 있습니다.
trichoplax

1
안녕하세요 @trichoplax 실제로 해결책을 찾았습니다. 내 질문에 대답하는 모든 사람들과 답변을 공유 할 것입니다. 솔직히 나는이 문제에 관심이 없다고 생각하여 내 질문을 삭제했습니다.
czapata91

1
BTW, 제목에 "SOLVED"로 질문을 편집하는 대신 자신의 답변 만 수락하는 것이 좋습니다. (이 사이트는 게시 후 하루를 기다리게 할 수도 있습니다. 기억이 나지 않습니다.)
Nathan Reed

야! 그 :)에 대한, 내가 제목을 변경합니다 @NathanReed 감사
czapata91

답변:


7

해결책

며칠 후 나는 FOV 각도를 사용하여 투영 행렬을 계산하고 있으며 라디안이어야한다는 것을 깨달았습니다 . 나는 전환을했고 이제는 모든 것이 잘 작동합니다. 깊이 프레임 버퍼의 큐브 맵의 면들 사이의 보간이 이제 완벽 해졌습니다. 이러한 이유로 모든 단일 삼각 함수의 각도를 라디안 단위로 처리하는 것이 중요합니다.

또한, 내가 질문에서 말했듯 이이 방법으로 뷰 매트릭스를 계산할 수 있음을 깨달았습니다.

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

이 접근 방식은 시점이 포인트 라이트의 중심에 위치하고 큐브 맵의 각 방향으로 렌더링하지만이 방향은 무엇입니까? 글쎄, 이 방향은 스위치 블록에있는 각 대상 (각 큐브 맵의 얼굴에 따라)을 포인트 라이트의 위치에 추가하여 계산됩니다 .

또한, 투영 행렬의 Y 좌표를 뒤집을 필요는 없습니다 .이 경우, 내가 작업하고 있기 때문에 (1, -1, 1)로 스케일링하지 않고 포인트 라이트의 원근 투영 행렬을 GLSL 쉐이더에 전달하는 것이 좋습니다. 뒤집힌 Y 좌표 가없는 텍스처 , 올바른 전 방향 그림자 매핑 효과를 얻으려면 뒤집힌 질감의 Y 좌표로 작업하는 경우에만 포인트 라이트의 투영 행렬의 Y 좌표를 뒤집어 야한다고 생각합니다 .

마지막으로 CPU / GPU쪽에 Omnidirectional Shadow Mapping 알고리즘의 최종 버전을 남겨 두겠습니다. CPU 쪽에서는 각 큐브 맵면에 올바른 그림자 맵을 계산하기 위해 수행해야하는 모든 단계를 설명하겠습니다. 반면 GPU 측면에서는 메인 프래그먼트 셰이더에서 깊이 프로그램의 버텍스 / 프래그먼트 셰이더 및 전 방향 그림자 매핑 기능을 설명합니다.이 기법을 배우 거나이 알고리즘에 대한 미래의 의심을 해결할 수있는 누군가를 돕기 위해 :

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

renderMeshDepth 함수에서 나는 :

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

깊이 프로그램 정점 셰이더 :

precision highp float;

attribute vec3 position;

uniform mat4 uModelView;
uniform mat4 uProjection;

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

깊이 프로그램 조각 쉐이더 :

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

메인 프래그먼트 셰이더의 전 방향 그림자 매핑 기능 :

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

여기에 알고리즘의 최종 렌더링이 있습니다

여기에 이미지 설명을 입력하십시오

아름다운 그래픽을 코딩하는 재미, 행운을 빕니다 :)

CZ

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.