ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Progressive Refinement Radiosity
    Graphics/Graphics Study 자료 2021. 1. 12. 07:15

    Progressive Refinement Radiosity

    최초 작성 : 2021-01-12

    마지막 수정 : 2021-01-12

    최재호

     

    목차

    1. 목표

    2. 내용

      2.1. 기존의 Radiosity 알고리즘의 소개

      2.2. Progressive Refinement Radiosity 소개

        2.2.1. Shooting

          2.2.1.1. Progressive Refinement Methods에서의 2가지 에너지

          2.2.1.2. Shooting 알고리즘은 진행 순서

        2.2.2. Hemicube Form-Factors

        2.2.3. Form-Factors의 계산과 가시성 확인

        2.3.4. Next Shooter 선택

        2.3.5. Ambient Term

        2.3.6. Adaptive Subdivision

    3. 실제 구현

      3.1. 기반 코드를 개선하여 추가할 기능 3가지

      3.2. 코드 실행 전 전처리

      3.3. 코드 실행 순서

      3.4. 추가 코드 리뷰

        3.4.1. Color Interpolation
        3.4.2. Add Ambient
        3.4.3. Adaptive Subdivision

    4. 현재 구현의 한계

      4.1. Hemicube Form-Factor

      4.2. Adaptive Subdivision

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

     

    1. 목표

    전통적인 방식의 Radiosity 알고리즘을 알아봅니다. 개선 버젼인 Progressive Refinement Radiosity에 대해 이해하고 구현합니다.

    Radiosity는 View-Independent 한 특성을 가지고 있습니다. 그래서 연산 중이나 후에 카메라를 이동시켜도 Radiosity 연산에 영향을 주지 않습니다.

     

    2. 내용

    2.1. 기존의 Radiosity 알고리즘의 소개

    전통적인 라디오시티 알고리즘은 장면을 작은 패치들로 나누고 해당 패치의 Radiosity를 계산합니다.

     

    그림1. 모든 장면이 Patch로 나누어서 관리됨 (출처 : 직접구현)

     

    각각의 패치의 Radiosity는 아래의 식을 사용하여 구합니다.

     

    식1. Patch i에 대한 Radiosity 계산을 위한 식 (출처 : 레퍼런스1)

     

    Form-Factor는 Reciprocity Relationship 을 성립합니다.

     

    식2. Form-Factor 의 eciprocity Relationship (출처 : 레퍼런스1)

     

    식1에 FjiAj를 FijAi로 교체하고 면적 Ai로 모든 항을 나눠주면, 식3 처럼 Patch의 면적과 상관없는 Radiosity식을 얻을 수 있습니다.

     

    식3. Patchi 에 대한 Radiosity 식 (출처 : 레퍼런스1)

     

    여기서 구한 식을 사용하여 총 3가지 스텝으로 장면의 Radiosity를 구합니다.

    1). Form-Factors 계산

       - Patch i로부터 나머지 모든 Patch j로의 Form-Factors를 구하고, 가시성 여부 또한 반영합니다.

    2). 식3의 Radiosity 알고리즘을 구함

    3). (중간/최종) 결과를 출력합니다.

       - 처리할 Patch가 남아 있다면, 해당 패치를 기준으로 다시 1번 과정 실행

     

    전통적인 Radiosity은 비용이 아주 비쌉니다. 모든 패치에 대한 Radiosity 계산을 위해서O(n^2)의 시간이 필요합니다. Patch 1개의 Radiosity를 계산하기 위해서는 나머지 모든 Patch의 Radiosity를 계산하여 합산하여야 하기 때문입니다.

     

    2.2. Progressive Refinement Radiosity 소개

    Progressive Refinement Methods는 최종 결과에 최대한 빠르게 근사하기 위해서 수정된 Radiosity 알고리즘입니다. 전통적인 Radiosity 알고리즘은 그림2과 같이 Gathering 알고리즘을 사용합니다.

    (여기서부터는 현재 Patch를 Patch i, 그 외의 모든 나머지 Patch들을 Patch j로 부릅니다.)

    Patch j로부터 Patch i로 들어오는 모든 Radiosity의 합을 계산하여 Patch i의 Radiosity 계산을 마칩니다.

     

    그림2. 전통적인 Radiosity 알고리즘인 Gathering 방식과 Progressive Refinement Radiosity의 Shooting 방식 (출처 : 레퍼런스1)

     

    2.2.1. Shooting

    Shooting 알고리즘에서는 Patch i가 다른 모든 Patch j 들을 업데이트 합니다. 만약 Patch i가 라이트이고, 나머지 Patch j들이 빛을 방출하지 않는 물체라고 합시다. Gathering의 경우는 모든 Patch j가 Gathering 연산이 다 끝나야지 전체 장면의 결과를 확인할 수 있습니다. 하지만 Shooting의 경우는 Patch i가 광원이었다고 한다면, 모든 Patch j에 바로 라이트의 Radiosity를 적용할 수 있기 때문에 더 빠른 결과를 근사 할 수 있을 것입니다.

     

    2.2.1.1. Progressive Refinement Methods에서의 2가지 에너지

    • Radiosity
    • UnShoot Radiosity

     

    UnShoot Radiosity는 자신이 가진 Radiosity 중 아직 다른 패치로 방출하지 않은 에너지입니다. 다른 Patch로부터 에너지를 받아뒀다가 자신이 Shooter가 되는 때에 이 에너지를 모든 Patch에게 방출하는 형태로 사용합니다. 만약 첫 번째 Shooting Iteration이라면, UnShoot Radiosity는 라이트 오브젝트만 가지고 있을 것이며, 이 UnShoot 에너지는 Emission 값으로 초기화 되었었을 것입니다.

     

    그림3. Shooting 알고리즘의 예제 (그림에서는 여러패치에서 여러방향으로 동시에 Shoot 하지만 실제로는 패치당 1번씩 Shoot함.) (출처 : 레퍼런스2)

     

    2.2.1.2. Shooting 알고리즘은 진행 순서

    1). Patch i 가 UnShoot 에너지를 모든 Patch j 로 Shoot 합니다.

    2). Patch j는 Patch i로부터 전달받은 Radiosity와 UnShoot Radiosity에 누적시킵니다.

    3). 자신이 가지고 있는 UnShoot Radiosity는 다음번 자신이 Shooter가 된 경우 사용하기 위해 저장합니다.

    4). Patch i가 모든 Patch j에 대해서 Radiosity를 전송하였다면, 자신의 UnShoot Radiosity를 0으로 설정합니다.

    그림4. Shooting 알고리즘 수도코드

     

    2.2.2. Hemicube Form-Factors

    이제 우리가 식에서 모르는 항은 바로 Form-Factors 뿐입니다. 지금부터 알아봅시다. 여기서는 Hemicube의 Form-Factors에 대해서 알아볼 것인데 일단 기본 Form-Factor 식을 유도해봅시다.

     

     

    그림4. 두쌍의 Patch를 연결한 Form-Factors (출처 : 레퍼런스3)
    그림5. Form-Factor 유도 (레퍼런스6을 참고하여 정리)
    식4. Form-Factor 식 (출처 : 레퍼런스3)

     

    Hemicube의 경우 그림6와 같은 형태로 Patch가 구성될 것입니다. Hemicube는 정육면체의 중심을 잘라낸 형태를 가집니다. 높이만 1이고 가로와 세로는 2인 정육면체라 보면 됩니다.

    그림6. Hemicube와 두쌍의 Patch을 연결하는 Form-Factor (출처 : 레퍼런스3)
    그림7. 원점있는 Patch i 와 Patch j 사이의 Hemicube Form-Factor의 유도 (출처 : 레퍼런스3)
    식5. Form-Factor 식 (식4와 동일) (출처 : 레퍼런스3)

    이제 Hemicube에서 윗면과 옆면 2가지로 나눠줍니다. 옆면의 경우는 각 패치들이 동일한 각도를 이루고 있기 때문에 동일한 면으로 취급할 수 있습니다. 윗면의 Form-factors는 아래의 순서로 유도할 수 있습니다. (그림7 같이 보면 좋습니다.)

     

    1). Hemicube의 중심을 원점으로 두고 여기에 Patch i를 둡니다

    2). Hemicube의 중심에 있는 Patch i의 Normal은 (0, 0, -1) 이고 Normal의 길이는 1입니다.

    3). Patch j의 위치를 (x, y, 1) 로 둡니다.

    4). 그림7의 r을  r=sqrt(x^2 + y^2 + 1) 로 둘 수 있습니다.

    5). cos(φ) = 1/r 그리고 cos(θ) = 1/r 로 둘 수 있습니다.

    6). 식6을 이제까지 유도한 식들을 사용하여 Top Face Form-Factors인 식6로 유도할 수 있습니다.

    식6. Top Face Form-Factor 식 (출처 : 레퍼런스3)

    비슷한 방식으로 옆면 Form-Factors는 아래와 같이 구할 수 있습니다.

    식7. Side Face Form-Factor 식 (출처 : 레퍼런스3)

    이제 우리는 Patch i와 윗면 모든 Patch j의 Form-Factors를 Top Face Form-Factors로부터 얻을 수 있습니다. 같은 방식으로 Side Form-Factors도 얻을 수 있습니다.

     

    2.2.3. Form-Factors의 계산과 가시성 확인

    Shooter로부터 보이는 Patch들을 얻기 위해서, 실제로 Hemicube의 5면에 대해서 렌더링을 하는 방식을 사용합니다. 렌더링 과정에서 발생하는 Depth-Test는 가려진 Patch들을 모두 제외시키도록 해줍니다. Hemicube의 윗면은 윗면에 해당하는 Form-factors, 옆면은 옆면에 해당하는 Form-factors를 통하여 Radiosity를 계산합니다. 더 자세한 것은 구현 파트의 코드를 보며 알아보도록 하겠습니다.

     

    2.3.4. Next Shooter 선택

    지금까지는 Patch i가 모든 Patch j를 업데이트하기 때문에 장면이 전체적으로 변화하면서 더 빠른 결과를 확인할 수 있었습니다. 그렇다면 가장 많은 UnShoot Radiosity를 가진 Patch를 Shooter로 선택할수록 전체 장면에 더 빠르게 근사 될 수 있을 것입니다. 그래서 매번 가장 UnShoot 에너지가 많은 Patch를 Shooter로 선택합니다.

     

    2.3.5. Ambient Term

    Patch의 UnShoot 에너지는 잠재적으로 장면에 Radiosity를 반영할 에너지입니다. 이 에너지를 Ambient로 만들어서 장면에 반영합니다. 이 항은 중간 출력 시 결과값에 최대한 근사하기 위해서 사용합니다. 그래서 최종 결과에 반영되지 않습니다. Ambient는 아래와 같은 순서로 얻습니다.

    1). 장면의 모든 오브젝트의 평균 반사율을 구합니다.

    2). 평균 반사율을 기준으로 장면 내에서 무한히 Interreflection이 발생한다고 가정합니다. 그리고 그 결과를 R로 둡니다.

    3). 식8의 Ambient를 구합니다. Ambient = 반사율 R * (모든 패치 j의 UnShoot Radiosity * (Form-Factors) 합)

    식8. Ambient Term (출처 : 레퍼런스1)

    중간 출력에 사용될 Radiosity는 식9와 같습니다.

    식9. 중간 출력에 사용되는 Radiosity (Bi’ 가 출력할 것 , Bi 는 실제  Radiosity, pi Patch i 의 반사율) (출처 : 레퍼런스1)

    이 Ambient 항은 식8을 보면 UnShoot Radiosity가 줄어들면 자동으로 0으로 수렴하게 됩니다. 즉, 장면이 최종 결과에 점점 수렵할수록 Ambient 항이 작아지면서 더 정확한 결과로 대치된다고 볼 수 있습니다.

     

    2.3.6. Adaptive Subdivision

    Radiosity 계산량을 줄이기 위해서, 더 세밀하게 표현해야 될 부분만 세분화하는 것이 성능과 디테일 모두에 도움이 될 수 있습니다. 예를들어 그리자가 드리우는 부분의 경우 경계선에서 더 세밀한 메시로 세분화되면 좋을 것입니다.

     

    그림8. 쉐도우 경계를 기반으로 한 Adaptive Subdivision (출처 : 레퍼런스2)

     

    레퍼런스1에서 설명한 방식 중 하나는 Patch, Element로 분리하여 세분화를 관리하는 것입니다. Patch는 Shooter로 선택되어 Radiosity를 다른 패치들에 전달할 수 있는 영역입니다. 그리고 Element는 Radiosity를 받을 수만 있는 영역입니다.

    즉, Radiosity Shooting은 Patch to Element는 가능하고, Element to Element는 불가능합니다.

     

    이글의 구현에서는 세분화 방식으로 QuadTree를 사용하였습니다.

     

    3. 실제 구현

    실제 구현은 레퍼런스4에 있는 코드를 활용하여 구성하였습니다. 참고한 코드도 Graphics Gems2의 코드에 기반한 내용인데, 수업을 위해 조금 수정된 것 같습니다.

     

    이 기반 코드가 렌더링 가능하도록 일부 수정 후 돌려보면 아래와 같은 결과가 나오게 됩니다.

     

    그림9. Radiosity 기반 코드 (출처 : 레퍼런스4)

     

    3.1. 기반 코드를 개선하여 추가할 기능 3가지

    1). Color Interpolation

    2). Add Ambient

    3). Adaptive Subdivision

     

    3.2. 코드 실행 전 전처리

    1). 입력 메시의 Patch와 Element 생성

    2). Emission 정보가 있는 Patch의 경우, UnShoot Energy를 Emisison 값으로 초기화

    3). Top Face Form-Factor와 Side Face Form-Factor를 미리 계산

       - 이때 Form-Factors 가 대상 Face에 대해서 대칭인 특성을 활용하여 공간을 절약합니다. 그림10 참고

     

    그림10. Form-Factor는 4-way symmetry 라 1/4 영역의 Form-Factor만으로도 충분 (출처 : 직접그림)

     

    // 윗면과 옆면 폼팩터 구하기
    // 1/4 formfactors needed since we will use this like 4-way symmetry.
    int32 Index = 0;
    const float halfRes = Res / 2.0f;
    for (int32 i = 0; i < halfRes; ++i)
    {
      const float y = (halfRes - (i - 0.5f)) / halfRes;	// ((halfRes - 0.5) / HalfRes), ((halfRes - 1.5) / HalfRes), ... (1.5 / HalfRes), (0.5 / HalfRes)
      const float ySq = y * y;
      for (int32 k = 0; k < halfRes; ++k)
      {
        const float x = (halfRes - (k + 0.5f)) / halfRes;
        const float xSq = x * x;
        float xy1Sq = xSq + ySq + 1.0f;
        xy1Sq *= xy1Sq;
        
        // Top Form-Factor
        InParams->TopFactors[Index] = 1.0f / (xy1Sq * PI * halfRes * halfRes);
        
        // Side Form-Factor
        InParams->SideFactors[Index] = y / (xy1Sq * PI * halfRes * halfRes);
        Index++;
      }
    }

     

    3.3. 코드 실행 순서

    1). UnShoot 에너지가 가장 큰 Patch를 Shooter로 설정

    2). Hemicube 5면에 대해서 렌더링 한 후, 현재 Patch에서 보이는 Element의 Form-Factors 값을 준비

       - Hemicube의 5면에 대해서 렌더링 할 때, ElementID를 렌더링 하여,텍스쳐를 다시 읽어 들였을 때 어떤 Element가 보이는지 판별

       - 만약 Adaptive Subdivision 기능을 사용한다면? 여기서 Element를 더 작게 쪼갤 수 있는지 확인하고 쪼갭니다. 쪼갠 후 ReShoot 하고, 더 쪼개질 게 없을 때까지 ReShoot 진행

    3). 2번 과정에서 Radiosity를 받은 Element들에 대해서 Radiosity와 UnShoot Radiosity를 갱신

       - 현재 Shooter의 UnShoot Radiosity는 0으로 리셋

    4). 중간/최종 결과를 렌더링

       - 중간 결과 출력 전용 Ambient 항을 만들어서 반영

     

    3.4. 추가 코드 리뷰

    3.4.1. Color Interpolation

       - Patch 내의 모든 Element들은 Vertices를 공유하고 있습니다. 그래서 Element의 4개의 Vertex에 Element의 Radiosity를 누적시켜 평균값을 사용합니다.

    // 1. Prepare all colros of vertices.
    InParams->ElementIterateOnlyLeaf([&](Element* InElement) 
    {
      Vector Color;
      if (InParams->AddAmbient)
        Color = (InElement->Radiosity + (Ambient * InElement->ParentPatch->Reflectance)) * InParams->IntensityScale;
    
      // Subdivision이 적용된 경우 현재 Element 하위에 있는 모든 Element에도 현재 색상을 반영해둠.
      // 이웃 Element의 경우 나보다 더 Subdivision이 많이 일어나있을 수 있기 때문에 하위에 있는 Element의 VertexColor 갱신이 필요함
      InElement->Iterate([&](Element* InAllChildElement) 
      {
        for (int32 k = 0; k < 4; ++k)
        {
          int32 Index = InAllChildElement->Indices[k];
          InParams->AllColors[Index] += Color;
          ++SummedCount[Index];
        }
      });
    });

       - Quad를 구성할 때 삼각형 2개로 구성하게 되면 Artifacts 가 발생할 수 있습니다. 그래서 Quad를 그리기 위한 코드를 추가합니다.각 Vertices가 가진 Color 정보를 올바르게 보간하기 위해서 아래와 코드를 사용합니다.

    // 1. C++ 에서 Quad 1개 Draw 명령 실행
    Vector Vertices[4];
    Vector4 Colors[4];
    
    Vector Normal = InElement->Normal;
    for (int32 k = 0; k < 4; ++k)
    {
      int32 Index = InElement->Indices[k];
      Vertices[k] = (InParams->AllVertices[Index]);
      Colors[k] = Vector4(InParams->AllColors[Index] / (float)SummedCount[Index], 1.0);
    }
    
    SET_UNIFORM_BUFFER_STATIC(Vector, "Pos[0]", Vertices[0], shader);
    SET_UNIFORM_BUFFER_STATIC(Vector, "Pos[1]", Vertices[1], shader);
    SET_UNIFORM_BUFFER_STATIC(Vector, "Pos[2]", Vertices[2], shader);
    SET_UNIFORM_BUFFER_STATIC(Vector, "Pos[3]", Vertices[3], shader);
    
    SET_UNIFORM_BUFFER_STATIC(Vector4, "Color[0]", Colors[0], shader);
    SET_UNIFORM_BUFFER_STATIC(Vector4, "Color[1]", Colors[1], shader);
    SET_UNIFORM_BUFFER_STATIC(Vector4, "Color[2]", Colors[2], shader);
    SET_UNIFORM_BUFFER_STATIC(Vector4, "Color[3]", Colors[3], shader);
    
    g_rhi->DrawArrays(EPrimitiveType::POINTS, 0, 1);
    
    // 2. Vertex Shader is empty.
    
    // 3. Geometry Shader
    // 정점 6개를 생성하여 삼각형 2개를 만듬
    // 삼각형의 각 정점들의 컬러가 제대로 보간될 수 있게 하기 위해서 UV를 픽셀쉐이더로 전달
    ...
    
    void main()
    {
      // Triangle 0
      gl_Position = MVP * vec4(Pos[0], 1.0);
      UV_ = vec2(0.0, 0.0);
      EmitVertex();
    
      gl_Position = MVP * vec4(Pos[1], 1.0);
      UV_ = vec2(1.0, 0.0);
      EmitVertex();
    
      gl_Position = MVP * vec4(Pos[2], 1.0);
      UV_ = vec2(0.0, 1.0);
      EmitVertex();
      
      EndPrimitive();
    
      // Triangle 1
      gl_Position = MVP * vec4(Pos[2], 1.0);
      UV_ = vec2(0.0, 1.0);
      EmitVertex();
    
      gl_Position = MVP * vec4(Pos[1], 1.0);
      UV_ = vec2(1.0, 0.0);
      EmitVertex();
    
      gl_Position = MVP * vec4(Pos[3], 1.0);
      UV_ = vec2(1.0, 1.0);
      EmitVertex();
    
      EndPrimitive();
    }
    
    // 4. Pixel Shader
    전달받은 컬러 적절이 보간하여 Quad 대각선에 발생하는 Artifacts를 제거
    ...
    uniform vec4 Color[4];
    
    in vec2 UV_;
    out vec4 color;
    
    void main()
    {
        vec4 c1 = mix(Color[0], Color[1], UV_.x);
        vec4 c2 = mix(Color[2], Color[3], UV_.x);
    
        color = mix(c1, c2, UV_.y);
    }

    3.4.2. Add Ambient

       - 위의 2.3.5. 항목에서 봤던 Ambient 공식을 그대로 활용합니다. 적용한 공식은 아래와 같이 적용

    Vector GetAmbient(InputParams* InParams)
    {
      Vector Ambient = black;
      Vector Sum = black;
      static Vector OverallInterreflectance = black;
      static int first = 1;
      static float AreaSum = 0.0f;
      if (first) 
      {
        Vector AverageReflectance = black;
        Vector rSum;
        rSum = black;
        /* sum area and (area*reflectivity) */
        for (int32 i = 0; i < InParams->Patches.size(); ++i)
        {
          AreaSum += InParams->Patches[i].Area;
          rSum += InParams->Patches[i].Reflectance * InParams->Patches[i].Area;
        }
        AverageReflectance = rSum / AreaSum;
        OverallInterreflectance = 1.0f / (1.0f - AverageReflectance);  // infinite geometric series for interreflection
        first = 0;
      }
    
      for (int32 i = 0; i < InParams->Patches.size(); ++i)
        Sum += InParams->Patches[i].UnShootRadiosity * static_cast<float>((InParams->Patches[i].Area / AreaSum));
    
      Ambient = OverallInterreflectance * Sum;
    
      return Ambient;
    }

    3.4.3. Adaptive Subdivision

       - Adaptive Subdivision을 위해서 코드를 Patch와 Element 구성으로 나누고, Element가 세분화될 수 있도록 QuadTree 자료구조를 구성

       - 편의를 위해서 QuadTree에 필요한 모든 Element는 초기화 과정에 모두 준비해두고, Subdivision이 일어났을 때 교체하여 사용

    // QuadTree 형태로 Subdivision 가능하도록 구성한 자료구조
    struct Element
    {
      ...
      Patch* ParentPatch = nullptr;
      Element* Parentlement = nullptr;
      Element* ChildElement[4] = { nullptr, nullptr, nullptr, nullptr };
      bool IsUsingChild[4] = { false, false, false, false };
      ..
    };
    
    // QuadTree는 Initialize 단계에서 모든 단계가 구성되고 IsUsingChild 플래그 여부에 따라 활성화 됨
    // QuadTree Element 생성에는 GeoHash algorithm를 사용함. https://en.wikipedia.org/wiki/Geohash

       - 쉐도우 경계를 기준으로 Subdivision을 만들어주도록 코드를 구성합니다. 그러기 위해서 Hemicube의 5면을 렌더링 할 때, Depth 정보를 함께 기록하며, 기록된 Depth를 PCF(Percentage Closer Filtering)처럼 근처 픽셀들의 Depth를 비교하여 Subdivide 해야 할지 말지 여부를 판별합니다.

    // Get Average Depth like PCF.
    ...
    // 현재 픽셀의 인접픽셀의 Depth의 합을 AverageZ에 더함
    ..
    AverageZ /= CntTemp;		// 평균값으로 만듬
    
    // 현재 픽셀의 Depth 값과 비교하여 Subdivision을 수행할지 여부를 저장
    float z = InParams->CurrentBuffer[i * ResX + k].y;
    if (z > AverageZ + 0.5f)
      SubDivideElementSet.insert(CurrentID);

     

    4. 현재 구현의 한계

    아쉽게도 구현의 한계로 해결하지 못한 이슈들도 있었습니다. 이 이슈들은 추후 더 좋은 해결법을 찾아서 수정할 수 있었으면 좋겠네요.

     

    4.1. Hemicube Form-Factor

    현재 구현의 Hemicube Form-Factors를 사용하는 경우 5방향에 대해, 현재 Patch에서 보이고 있는 Patch들을 렌더링 합니다. 이때 Artifacts가 발생할 수 있으며, Element의 Subdivision 수를 늘릴수록 Artifacts는 더 심해집니다.

    원인을 파악하기 위해 윗면과 옆면이 있는 장면을 구성했습니다. 그리고 윗면의 Patch에서 옆면 방향으로 Radiosity Shooting 하는 테스트 케이스를 만들었습니다.

     

    1). Patch 당 Element가 1개인 케이스

     

    그림11. Patch당 Element 1개인 경우 (출처 : 직접구현)

     

    2). Patch당 Element가 4개인 케이스

     

    그림12. Patch당 Element 4개인 경우 (출처 : 직접구현)

    위 케이스의 큰 차이점은 윗면의 Patch에서 Shooting 된 Radiosity를 받는 옆면의 Element의 개수가 다르다는 점입니다. Artifacts가 발생하지 않는 1) 케이스의 경우 Radiosity를 받는 Element의 개수가 1개이기 때문에 여러 Patch들 사이의 Form-Factors 값 차이가 발생하지 않습니다. 하지만 2)의 경우 가장자리로 갈수록 Form-Factors의 값이 어두워 지는 것을 볼 수 있습니다.

     

    사실 2) 케이스의 경우 Form-Factors가 Patch j들에 대해서 렌더링 되는 화면 중심에서 벗어날수록 작아지는 것은 합당합니다. 하지만 이런 형태로 Patch의 크기에 따라서 체크무늬가 나타나는 상황이라면 올바른 결과라 할 수 없을 것입니다.

     

    4.2. Adaptive Subdivision

    Adaptive Subdivision이 Element을 더 작은 단위로 나누는 조건은 그림자의 경계에 Patch가 걸쳐져 있는가? 입니다. 그래서 그림자 경계와 그림자 내부에 있는 Quad는 QuadTree Level의 차이가 납니다. Subdivision 되지 않은 Quad에서는 컬러 변화도가 급격하게 어두워지며 각진 모양의 Artifacts를 만들게 됩니다. 이렇게 되는 이유는 Element의 Quad가 클수록 그림자 내부로 Vertices가 들어갈 가능성이 크기 때문입니다. 그림자 내부의 Vertices는 Radiosity를 전혀 받지 못했을 것입니다. 그래서 전체적인 Quad의 컬러가 어두워지는 것입니다.

     

    그림13. Adaptive Subdivision 시, Shadow 내부에서 발생하는 Artifacts (출처 : 직접구현)

     

    5. 구현 결과

    1). Radiosity 구현 결과

    그림13. Radiosity 연산 결과 (출처 : 직접구현)

     

    2). View-Independent Radiosity 결과

    그림14. View-Independent 결과를 보여주는 Radiosity (출처 : 직접구현)

     

    2). Ambient 적용 전/후 비교

    그림15. 좌측 : Ambient 적용전, 우측 : Ambient 적용후. Ambient 적용시 결과 화면에 거의 즉시 근사하는 것 처럼 보임 (출처 : 직접구현)

     

    3). Adaptive Subdivision 적용 전/후 비교

    그림16. Adaptive Subdivision 전의 경우 그림자가 흐릿함 (출처 : 직접구현)
    그림17. Adaptive Subdivision 결과, 그림자 영역이 좀 더 선명해진 것을 볼 수 있음 (출처 : 직접구현)

     

     

    6. 구현 코드

    github.com/scahp/Shadows/tree/RadiosityCornellBox

     

    7. 레퍼런스

    1. A Progressive Refinement Approach to Fast Radiosity Image Generation, Michael F. Cohen, Shenchang Eric Chen, John R. Wallace, Donald P. Greenberg

    2. Chapter 39. Global Illumination Using Progressive Refinement Radiosity, Mark HarrisNVIDIA Corporation

    3. Lecture 13: Radiosity - Principles

    4. Coursework 2: Progressive Refinement Radiosity (Individual)

    5. Graphics Gems2, JAMES ARVO

    6. Chapter13 RADIATION HEAT TRANSFER, Universiti Malaysia Perlis, Azizul Mohamad - Heat and Mass Transfer: Fundamentals & Applications, 5th Edition, Yunus A. Çengel, Afshin J. Ghajar McGraw-Hill, 2015

     

     

     

     

    댓글

Designed by Tistory & scahp.