ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Weighted Blended OIT
    Graphics/Graphics Study 자료 2022. 5. 23. 22:12

    Weighted Blended OIT

     

    최초 작성 : 2022-05-23

    마지막 수정 : 2022-05-23

    최재호

     

    목차

    1. 목표
    2. 내용

      2.1. Blended OIT
        2.1.1. Meshkin's Method
        2.1.2. Bavoil and Myers' Method
        2.1.3. New Blended OIT Method
      2.2. Weighted Blended OIT Method

    3. 실제 구현
    4. 구현 결과
    5. 실제 구현 코드
    6. 레퍼런스

     

    1. 목표

    Weighted Blended OIT(Order Independent Transparency) 를 이해하고 실제로 구현해봅시다.

    레퍼런스1의 논문을 기반으로 구현의 핵심 내용을 확인해봅시다.

     

    2. 내용

    반투명의 경우 Partial coverage 를 대상으로 합니다. Partial coverage는 굴절(refract) 없이 빛이 투과(transmit)되거나 반사(reflect) 그리고 방출(emit) 될 수 있는 표면입니다.

    논문에서는 Partial coverage 표면을 a와 C 로 나타냅니다. a는 알파값, C는 미리 알파값이 곱해진 컬러입니다. 여기서 C는 미리 알파값이 곱해진 컬러 값을 사용하는데, 그렇게 하지 않으면 다른 그래픽스 관련 처리 시 컬러 블리딩 현상을 겪을 수도 있습니다. (예를 들어 알파값이 0이라 실제 색이 표현되지 않아야 하는 컬러가 있는 경우, 블러와 같이 인접 픽셀의 색상을 가져가서 연산을 하는 경우) 그래서 이 글의 모든 식들은 미리 알파값이 곱해진 C와 알파값 a를 사용하여 수행합니다.

     

    먼저 기존에 사용하던 블랜딩 방식을 봅시다. 그림1의 C1은 포그라운, C0은 백그라운드 컬러입니다. a1는 포그라운드 컬러의 알파값 입니다. 일반적으로 사용하는 알파 블랜딩 식입니다.

    그림1. Porter and Duff's OVER 연산 (출처 : 레퍼런스1)

    그림2는 그림1의 연산을 반복적으로 적용한 방식을 볼 수 있습니다.

    그림2. N 개의 컬러를 그림1의 방식으로 반복해서 적용하는 경우 (출처 : 레퍼런스1)

    OVER 방식의 단점은 교환 법칙이 성립하지 않는 것입니다. 그래서 알파 블랜딩을 처리할 때 항상 소팅을 해서 렌더링 해야 합니다.

     

    이런 점을 개선하기 위해서 Order-independent transparency 를 고안합니다. 다양한 방법으로 OIT를 구현할 수 있습니다만 여기서는 연산을 교환 가능한 형태로 적용하는 방법에 대해서 알아볼 것입니다.

     

    2.1. Blended OIT

    2.1.1. Meshkin's Method

    Meshkin's Method(2004) 는 간단하게 컬러와 알파를 weighted sum으로 표현합니다. 여기서 C0은 백그라운드 색상입니다. 이 방식의 단점은 알파값이 더 커질수록 OVER 연산에 비해서 백그라운드의 색상이 많이 달라지는 점입니다.  

    그림3. Meshkin's Method(2004) (출처 : 레퍼런스1)

    2.1.2. Bavoil and Myers' Method

    Bavoil and Myers' Method(2008) 는 Meshkin's 연산보다 더 나은 근사를 보여줍니다. 이 연산은 weighted average 연산을 사용합니다. 식의 내부에 보면 이제까지의 알파의 평균을 사용하는 것을 볼 수 있습니다. 이 방식의 단점은 C=0, a=0인 완전히 투명한 표면도 최종 결과에 영향을 미칠 수 있는 점입니다. 이렇게 되는 이유는 알파값이 0인 값도 평균값에 영향을 미칠 수 있기 때문입니다.

    그림4. Bavoil and Myers' Method.(2008) (출처 : 레퍼런스1)

     

    2.1.3. New Blended OIT Method

    지금 부터 논문에서 소개하는 Weighted Blended OIT 가 시작됩니다. 먼저 여기서는 Blended OIT를 새로 정의합니다.

     

    Bavoil and Myer's Method 의 단점을 개선하기 위해서 아래 그림5의 연산을 만듭니다. 이 경우는 알파값이 0 인 경우는 최종적으로 반영되지 못하도록 해줍니다.((1-ai)가 1이 되도록 해 주고, 1은 곱해져도 1이기 때문에 무시될 수 있음)

    그림5. New Blended OIT Method (출처 : 레퍼런스1)

    2.2. Weighted Blended OIT Method

    여기서 추가로 카메라에 더 근접한 표면이 뒤에 있는 표면을 차폐하는 효과를 주기 위해서 Weight 함수를 추가로 적용해줍니다. 이 Weight 함수는 Depth 값을 기반으로 정의하며, 프로젝트의 환경에 따라 자유롭게 정의할 수 있습니다.

    그림6. Weighted Blended OIT Method (출처 : 레퍼런스1)

    논문에서는 z 값이 0.1 ~ 500 사이의 값일 때 사용하기 적절한 Weight 함수를 아래와 같이 제안해줍니다.

    그림7. 저자가 제안한 Weight 함수들 (출처 : 레퍼런스1)

    아래 그림8 처럼 Weight 함수는 z값이 0.1~500 인경우 0.01~3000 의 값을 돌려줍니다. 각각의 함수들의 커브를 보고 적절한 것을 선택할 수 있을 것입니다. 

    그림8. Weight 함수의 범위 (출처 : 레퍼런스1)

    Weighted Blended OIT 의 실제 구현은 그림9와 같습니다. 2개의 패스가 필요하며 렌더타겟은 RGBA16F 1장 R16F 1장이 필요합니다.

    첫 번째 렌더 패스에서는 Cn*W 와 an*W의 Weighted Sum을 수행합니다. 그리고 두 번째 패스에서는 이전 패스에서 준비한 값을 사용하여 최종 결과를 생성합니다. 아래 코드의 일부 연산은 알파 블랜드의 연산을 활용하고 있습니다. 그래서 알파 블랜드의 함수 설정을 확인하고 코드를 같이 보면 그림6의 Weighted Blended OIT Method 식인 것을 확인할 수 있습니다.

    그림9. Weighted Blended OIT 실제 구현 코드 (출처 : 레퍼런스1)

     

    그림10. 지금까지 본 방식들의 차이 비교, f의 Sorted OVER과 가장 유사한 결과를 만들어내는 것이 OIT 의 목표이며 여기서 소개된 방식인 e는 Sorted OVER 방식과 상당히 유사함을 확인할 수 있음. (출처 : 레퍼런스1)

     

    논문 저자는 더욱더 발전시킨 Weight 함수들 또한 발표했습니다. 이런 부분들은 필요한 경우 참고하면 좋을 것입니다.

     

    이 방식은 Weight 함수가 Depth 기반으로 작성되어있기 때문에 카메라의 Depth 이 변화에 따라서 최종 블랜딩 결과가 영향을 받습니다.

    - 예를들어 카메라의 Near과 Far 의 범위에 따라 블랜딩 결과가 달라질 수 있음.

    - 표면의 위치가 카메라에서 가깝거나 먼 경우에 따라 블랜딩 결과가 달라질 수 있음.

    - 한 개의 표면이 카메라에 비스듬하게 배치된 경우 동일 표면 내에서도 블랜딩 결과가 달라질 수 있음.

     

    하지만 근거리에서의 반투명 결과는 거의 Sorted OVER 방식과 동일합니다.

     

    3. 실제 구현

    C++ 에서 두 개의 렌더링 패스를 실행합니다.

    // 첫번째 렌더링 패스, Weighted Sum 수행
    if (TranslucentRTPtr->Begin(0, true))
    {
      g_rhi->EnableDepthTest(true);
      g_rhi->SetViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
    
      // 블랜딩 함수 설정
      g_rhi->EnableBlend(true);
      g_rhi->SetBlendFuncRT(EBlendSrc::ONE, EBlendDest::ONE, 0);
      g_rhi->SetBlendFuncRT(EBlendSrc::ZERO, EBlendDest::ONE_MINUS_SRC_ALPHA, 1);
      g_rhi->SetBlendEquation(EBlendEquation::ADD);
    
      g_rhi->EnableCullFace(false);
    
      float ClearColorRT0[] = { 0.0f, 0.0f, 0.0f, 1.0f };
      float ClearColorRT1[] = { 1.0f, 1.0f, 1.0f, 1.0f };
      float ClearColorRT2[] = { 0.0f, 0.0f, 0.0f, 0.0f };
      g_rhi->SetClearBuffer(ERenderBufferType::COLOR, &ClearColorRT0[0], 0);
      g_rhi->SetClearBuffer(ERenderBufferType::COLOR, &ClearColorRT1[0], 1);
      g_rhi->SetClearBuffer(ERenderBufferType::COLOR, &ClearColorRT2[0], 2);
    
      // 프리미티브들 렌더링 시작
      jShader* pShader = jShader::GetShader("WeightedOIT");
      if (pFloor)
      {
        pFloor->Update(deltaTime);
        pFloor->Draw(MainCamera, pShader, { DirectionalLight });
      }
    
      ...
      // 프리미티브들 렌더링 끝
    
      TranslucentRTPtr->End();
    }
    
    // 두번째 렌더링 패스, 반투명 처리 마무리
    if (FullScreenQuad)
    {
      g_rhi->EnableDepthTest(false);
    
      if (appSetting.BackgroundColorOnOff)
        g_rhi->SetClearColor(appSetting.BackgroundColor.x, appSetting.BackgroundColor.y, appSetting.BackgroundColor.z, 1.0f);
      else
        g_rhi->SetClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    
      g_rhi->SetClear(ERenderBufferType::COLOR);
    
      // 첫번째 패스에서 준비한 텍스쳐들 설정
      FullScreenQuad->SetTexture(TranslucentRTPtr->GetTexture(0), jSamplerStatePool::GetSamplerState("Point").get());
      FullScreenQuad->SetTexture2(TranslucentRTPtr->GetTexture(1), jSamplerStatePool::GetSamplerState("Point").get());
    
      g_rhi->EnableDepthTest(false);
      g_rhi->SetViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
    
      // 블랜딩 함수 설정
      g_rhi->EnableBlend(true);
      g_rhi->SetBlendFunc(EBlendSrc::ONE_MINUS_SRC_ALPHA, EBlendDest::SRC_ALPHA);
      g_rhi->SetBlendEquation(EBlendEquation::ADD);
    
      jShader* pShader = jShader::GetShader("WeightedOIT_Finalize");
      FullScreenQuad->Draw(MainCamera, pShader, {});
    }

     

    첫 번째 렌더링 패스를 위한 픽셀 쉐이더입니다.

    // weightedOIT_fs.glsl
    #version 330 core
    
    precision mediump float;
    
    in vec4 Color_;
    in float LinearZ;
    
    layout (location = 0) out vec4 color;
    layout (location = 1) out vec4 alpha;
    layout(location = 2) out vec4 debug;
    
    uniform vec4 ColorUniform;
    
    #define ORIGINAL_WEIGHT_FUNC 0
    
    void main()
    {
      vec3 ColorN = ColorUniform.xyz;
      float AlphaN = ColorUniform.w;
    
    #if ORIGINAL_WEIGHT_FUNC
      // Tuned to work well with FP16 accumulation buffers and 0.001 < linearDepth < 2.5
      // See Equation (9) from http://jcgt.org/published/0002/02/09/
      float WeightN = clamp(0.03 / (1e-5 + pow(LinearZ, 4.0)), 0.01, 3e3);
      
      // To avoid the transparent object to be black with far distance.
      WeightN = max(WeightN, 1.0);
    #else
      // _WEIGHTED0 from https://github.com/candycat1992/OIT_Lab/blob/master/Assets/OIT/WeightedBlended/Shaders/WB_Accumulate.shader
      float WeightN = pow(LinearZ, -2.5);
    #endif
    
      debug.x = gl_FragCoord.z;
      debug.y = abs(LinearZ);
      debug.z = WeightN;
    
      color = vec4(ColorN * AlphaN, AlphaN) * WeightN;
      alpha = vec4(AlphaN);
    }

     

    두 번째 패스를 위한 렌더링 패스입니다. 이 패스는 PostProcess와 같이 전체 화면을 렌더링 하여 각 픽셀 별 최종 컬러를 확정합니다.

    // WeightedOIT_Finalize_fs.glsl
    #version 330 core
    precision mediump float;
    
    uniform sampler2D tex_object;
    uniform sampler2D tex_object2;
    
    in vec2 TexCoord_;
    out vec4 FragColor;
    
    void main()
    {
    	vec4 accum = texture(tex_object, TexCoord_);
    	float r = texture(tex_object2, TexCoord_).r;
    
    	FragColor = vec4(accum.rgb / clamp(accum.a, 1e-4, 5e4), r);
    }

     

     

    4. 구현 결과

    그림11. 근거리에서 Weighted Blended OIT 와 Sorted Over 차이비교, 거의 동일 한 것을 볼 수 있음. (출처 : 직접구현)

     

    그림12. 원거리에서 Weighted OIT 와 Sorted Over 차이비교, Sorted OVER 는 Weighted Blended OIT 에 비해서 파란색 Quad가 나머지 초록과 빨강 Quad를 잘 차폐시키고 있는 것 처럼 보임. (출처 : 직접구현)

     

    그림13. 여러 물체들을 사용한 Weighted Blended OIT 예제, 그림에서 파란색 벽의 하단과 우측의 경우 인접한 벽의 색이 더 많이 적용된 것이 보이며, 이렇게 되는 이유는 Depth를 기반으로한 Weight 함수에서 근거리일 수록 가중치를 더 많이 주도록 설정하기 때문. (출처 : 직접구현)

     

    5. 실제 구현 코드

    https://github.com/scahp/Shadows/tree/WeightOIT

     

    6. 레퍼런스

    1. https://jcgt.org/published/0002/02/09/

     

     

    'Graphics > Graphics Study 자료' 카테고리의 다른 글

    [UE4 PBR] Split sum appoximation 리뷰  (2) 2022.07.07
    Dual-depth relief interior mapping  (0) 2022.06.11
    Horizon Mapping  (0) 2022.03.15
    Parallax Mapping  (0) 2022.02.27
    Color Science  (2) 2021.12.25

    댓글

Designed by Tistory & scahp.