OptiX - optix7course Example 10 분석

선비Sunbei·2023년 9월 3일
0

OptiX

목록 보기
24/25
post-thumbnail
https://github.com/ingowald/optix7course

이전과 마찬가지로 변경된 점만 작성한다.

// main.cpp
  /*! main entry point to this example - initially optix, print hello
    world, then exit */
  extern "C" int main(int ac, char **av)
  {
    try {
      Model *model = loadOBJ(
#ifdef _WIN32
      // on windows, visual studio creates _two_ levels of build dir
      // (x86/Release)
      "../../models/sponza.obj"
#else
      // on linux, common practice is to have ONE level of build dir
      // (say, <project>/build/)...
      "../models/sponza.obj"
#endif
                             );
      Camera camera = { /*from*/vec3f(-1293.07f, 154.681f, -0.7304f),
                        /* at */model->bounds.center()-vec3f(0,400,0),
                        /* up */vec3f(0.f,1.f,0.f) };

      // some simple, hard-coded light ... obviously, only works for sponza
      const float light_size = 200.f;
      QuadLight light = { /* origin */ vec3f(-1000-light_size,800,-light_size),
                          /* edge 1 */ vec3f(2.f*light_size,0,0),
                          /* edge 2 */ vec3f(0,0,2.f*light_size),
                          /* power */  vec3f(3000000.f) };
                      
      // something approximating the scale of the world, so the
      // camera knows how much to move for any given user interaction:
      const float worldScale = length(model->bounds.span());

      SampleWindow *window = new SampleWindow("Optix 7 Course Example",
                                              model,camera,light,worldScale);
      window->run();
      
    } catch (std::runtime_error& e) {
      std::cout << GDT_TERMINAL_RED << "FATAL ERROR: " << e.what()
                << GDT_TERMINAL_DEFAULT << std::endl;
	  std::cout << "Did you forget to copy sponza.obj and sponza.mtl into your optix7course/models directory?" << std::endl;
	  exit(1);
    }
    return 0;
  }

light에 대한 설정을 SampleWindow에 넘겨주고 결과적으로 SampleRenderer에 넘겨진다.

// Model.h
#pragma once

#include "gdt/math/AffineSpace.h"
#include <vector>

/*! \namespace osc - Optix Siggraph Course */
namespace osc {
  using namespace gdt;
  
  /*! a simple indexed triangle mesh that our sample renderer will
      render */
  struct TriangleMesh {
    std::vector<vec3f> vertex;
    std::vector<vec3f> normal;
    std::vector<vec2f> texcoord;
    std::vector<vec3i> index;

    // material data:
    vec3f              diffuse;
    int                diffuseTextureID { -1 };
  };

  struct QuadLight {
    vec3f origin, du, dv, power;
  };
  
  struct Texture {
    ~Texture()
    { if (pixel) delete[] pixel; }
    
    uint32_t *pixel      { nullptr };
    vec2i     resolution { -1 };
  };
  
  struct Model {
    ~Model()
    {
      for (auto mesh : meshes) delete mesh;
      for (auto texture : textures) delete texture;
    }
    
    std::vector<TriangleMesh *> meshes;
    std::vector<Texture *>      textures;
    //! bounding box of all vertices in the model
    box3f bounds;
  };

  Model *loadOBJ(const std::string &objFile);
}

QuadLight는 Model.h에서 정의한다.

// SampleRenderer.cpp
  SampleRenderer::SampleRenderer(const Model *model, const QuadLight &light)
    : model(model)
  {
    initOptix();

    launchParams.light.origin = light.origin;
    launchParams.light.du     = light.du;
    launchParams.light.dv     = light.dv;
    launchParams.light.power  = light.power;
...

launchParam에도 QuadLight에 대한 값을 넣는다.

// LaunchParams.h
#pragma once

#include "gdt/math/vec.h"
#include "optix7.h"

namespace osc {
  using namespace gdt;

  // for this simple example, we have a single ray type
  enum { RADIANCE_RAY_TYPE=0, SHADOW_RAY_TYPE, RAY_TYPE_COUNT };

  struct TriangleMeshSBTData {
    vec3f  color;
    vec3f *vertex;
    vec3f *normal;
    vec2f *texcoord;
    vec3i *index;
    bool                hasTexture;
    cudaTextureObject_t texture;
  };
  
  struct LaunchParams
  {
    struct {
      uint32_t *colorBuffer;
      vec2i     size;
      int       accumID { 0 };
    } frame;
    
    struct {
      vec3f position;
      vec3f direction;
      vec3f horizontal;
      vec3f vertical;
    } camera;

    struct {
      vec3f origin, du, dv, power;
    } light;
    
    OptixTraversableHandle traversable;
  };

} // ::osc

light에 대한 구조체 및 변수는 LaunchParms에 정의된다.
그리고 frame 구조체에 accumID가 생겨났다.

// deviceParams.cu
extern "C" __global__ void __raygen__renderFrame()
  {
    // compute a test pattern based on pixel ID
    const int ix = optixGetLaunchIndex().x;
    const int iy = optixGetLaunchIndex().y;
    const int accumID  = optixLaunchParams.frame.accumID;
    const auto &camera = optixLaunchParams.camera;
    
    PRD prd;
    prd.random.init(ix+accumID*optixLaunchParams.frame.size.x,
                 iy+accumID*optixLaunchParams.frame.size.y);
    prd.pixelColor = vec3f(0.f);

    // the values we store the PRD pointer in:
    uint32_t u0, u1;
    packPointer( &prd, u0, u1 );

    int numPixelSamples = NUM_PIXEL_SAMPLES;

    vec3f pixelColor = 0.f;
    for (int sampleID=0;sampleID<numPixelSamples;sampleID++) {
      // normalized screen plane position, in [0,1]^2
      const vec2f screen(vec2f(ix+prd.random(),iy+prd.random())
                         / vec2f(optixLaunchParams.frame.size));
    
      // generate ray direction
      vec3f rayDir = normalize(camera.direction
                               + (screen.x - 0.5f) * camera.horizontal
                               + (screen.y - 0.5f) * camera.vertical);

      optixTrace(optixLaunchParams.traversable,
                 camera.position,
                 rayDir,
                 0.f,    // tmin
                 1e20f,  // tmax
                 0.0f,   // rayTime
                 OptixVisibilityMask( 255 ),
                 OPTIX_RAY_FLAG_DISABLE_ANYHIT,//OPTIX_RAY_FLAG_NONE,
                 RADIANCE_RAY_TYPE,            // SBT offset
                 RAY_TYPE_COUNT,               // SBT stride
                 RADIANCE_RAY_TYPE,            // missSBTIndex 
                 u0, u1 );
      pixelColor += prd.pixelColor;
    }
    
    const int r = int(255.99f*min(pixelColor.x / numPixelSamples,1.f));
    const int g = int(255.99f*min(pixelColor.y / numPixelSamples,1.f));
    const int b = int(255.99f*min(pixelColor.z / numPixelSamples,1.f));

    // convert to 32-bit rgba value (we explicitly set alpha to 0xff
    // to make stb_image_write happy ...
    const uint32_t rgba = 0xff000000
      | (r<<0) | (g<<8) | (b<<16);

    // and write to frame buffer ...
    const uint32_t fbIndex = ix+iy*optixLaunchParams.frame.size.x;
    optixLaunchParams.frame.colorBuffer[fbIndex] = rgba;
  }
// devicePrograms.cu
#define NUM_LIGHT_SAMPLES 1
#define NUM_PIXEL_SAMPLES 16

  typedef gdt::LCG<16> Random;

  struct PRD {
    Random random;
    vec3f  pixelColor;
  };

random은 gdt library에서 linear congruential Generator의 약어로 LCG를 쓴다. 16bit 난수 생성 알고리즘이다.
accumID는 random의 seed 값을 초기화하는데 사용된다.

// SampleRenderer.cpp
/*! render one frame */
  void SampleRenderer::render()
  {
    // sanity check: make sure we launch only after first resize is
    // already done:
    if (launchParams.frame.size.x == 0) return;
      
    launchParamsBuffer.upload(&launchParams,1);
    launchParams.frame.accumID++;
    
    OPTIX_CHECK(optixLaunch(/*! pipeline we're launching launch: */
                            pipeline,stream,
                            /*! parameters and SBT */
                            launchParamsBuffer.d_pointer(),
                            launchParamsBuffer.sizeInBytes,
                            &sbt,
                            /*! dimensions of the launch: */
                            launchParams.frame.size.x,
                            launchParams.frame.size.y,
                            1
                            ));
    // sync - make sure the frame is rendered before we download and
    // display (obviously, for a high-performance application you
    // want to use streams and double-buffering, but for this simple
    // example, this will have to do)
    CUDA_SYNC_CHECK();
  }

위와 같이 accumID는 frame마다 증가한다.

NUM_PIXEL_SAMPLES만큼 한 픽셀로의 ray에 대해서 약간의 랜덤으로 오차를 주어 샘플링하여 pixelColor 값에 더한다.
즉, 한 픽셀에 대해 여러 빛을 받는 것을 가정하는 것이다.

// devicePrograms.cu
extern "C" __global__ void __closesthit__radiance()
  {
    const TriangleMeshSBTData &sbtData
      = *(const TriangleMeshSBTData*)optixGetSbtDataPointer();
    PRD &prd = *getPRD<PRD>();

    // ------------------------------------------------------------------
    // gather some basic hit information
    // ------------------------------------------------------------------
    const int   primID = optixGetPrimitiveIndex();
    const vec3i index  = sbtData.index[primID];
    const float u = optixGetTriangleBarycentrics().x;
    const float v = optixGetTriangleBarycentrics().y;

    // ------------------------------------------------------------------
    // compute normal, using either shading normal (if avail), or
    // geometry normal (fallback)
    // ------------------------------------------------------------------
    const vec3f &A     = sbtData.vertex[index.x];
    const vec3f &B     = sbtData.vertex[index.y];
    const vec3f &C     = sbtData.vertex[index.z];
    vec3f Ng = cross(B-A,C-A);
    vec3f Ns = (sbtData.normal)
      ? ((1.f-u-v) * sbtData.normal[index.x]
         +       u * sbtData.normal[index.y]
         +       v * sbtData.normal[index.z])
      : Ng;
    
    // ------------------------------------------------------------------
    // face-forward and normalize normals
    // ------------------------------------------------------------------
    const vec3f rayDir = optixGetWorldRayDirection();
    
    if (dot(rayDir,Ng) > 0.f) Ng = -Ng;
    Ng = normalize(Ng);
    
    if (dot(Ng,Ns) < 0.f)
      Ns -= 2.f*dot(Ng,Ns)*Ng;
    Ns = normalize(Ns);

    // ------------------------------------------------------------------
    // compute diffuse material color, including diffuse texture, if
    // available
    // ------------------------------------------------------------------
    vec3f diffuseColor = sbtData.color;
    if (sbtData.hasTexture && sbtData.texcoord) {
      const vec2f tc
        = (1.f-u-v) * sbtData.texcoord[index.x]
        +         u * sbtData.texcoord[index.y]
        +         v * sbtData.texcoord[index.z];
      
      vec4f fromTexture = tex2D<float4>(sbtData.texture,tc.x,tc.y);
      diffuseColor *= (vec3f)fromTexture;
    }

    // start with some ambient term
    vec3f pixelColor = (0.1f + 0.2f*fabsf(dot(Ns,rayDir)))*diffuseColor;
    
    // ------------------------------------------------------------------
    // compute shadow
    // ------------------------------------------------------------------
    const vec3f surfPos
      = (1.f-u-v) * sbtData.vertex[index.x]
      +         u * sbtData.vertex[index.y]
      +         v * sbtData.vertex[index.z];

    const int numLightSamples = NUM_LIGHT_SAMPLES;
    for (int lightSampleID=0;lightSampleID<numLightSamples;lightSampleID++) {
      // produce random light sample
      const vec3f lightPos
        = optixLaunchParams.light.origin
        + prd.random() * optixLaunchParams.light.du
        + prd.random() * optixLaunchParams.light.dv;
      vec3f lightDir = lightPos - surfPos;
      float lightDist = gdt::length(lightDir);
      lightDir = normalize(lightDir);
    
      // trace shadow ray:
      const float NdotL = dot(lightDir,Ns);
      if (NdotL >= 0.f) {
        vec3f lightVisibility = 0.f;
        // the values we store the PRD pointer in:
        uint32_t u0, u1;
        packPointer( &lightVisibility, u0, u1 );
        optixTrace(optixLaunchParams.traversable,
                   surfPos + 1e-3f * Ng,
                   lightDir,
                   1e-3f,      // tmin
                   lightDist * (1.f-1e-3f),  // tmax
                   0.0f,       // rayTime
                   OptixVisibilityMask( 255 ),
                   // For shadow rays: skip any/closest hit shaders and terminate on first
                   // intersection with anything. The miss shader is used to mark if the
                   // light was visible.
                   OPTIX_RAY_FLAG_DISABLE_ANYHIT
                   | OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT
                   | OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT,
                   SHADOW_RAY_TYPE,            // SBT offset
                   RAY_TYPE_COUNT,               // SBT stride
                   SHADOW_RAY_TYPE,            // missSBTIndex 
                   u0, u1 );
        pixelColor
          += lightVisibility
          *  optixLaunchParams.light.power
          *  diffuseColor
          *  (NdotL / (lightDist*lightDist*numLightSamples));
      }
    }
    
    prd.pixelColor = pixelColor;
  }
// __closesthit__radiance 일부
 const int numLightSamples = NUM_LIGHT_SAMPLES;
    for (int lightSampleID=0;lightSampleID<numLightSamples;lightSampleID++) {
      // produce random light sample
      const vec3f lightPos
        = optixLaunchParams.light.origin
        + prd.random() * optixLaunchParams.light.du
        + prd.random() * optixLaunchParams.light.dv;
      vec3f lightDir = lightPos - surfPos;
      float lightDist = gdt::length(lightDir);
      lightDir = normalize(lightDir);
    
      // trace shadow ray:
      const float NdotL = dot(lightDir,Ns);
      if (NdotL >= 0.f) {
        vec3f lightVisibility = 0.f;
        // the values we store the PRD pointer in:
        uint32_t u0, u1;
        packPointer( &lightVisibility, u0, u1 );
        optixTrace(optixLaunchParams.traversable,
                   surfPos + 1e-3f * Ng,
                   lightDir,
                   1e-3f,      // tmin
                   lightDist * (1.f-1e-3f),  // tmax
                   0.0f,       // rayTime
                   OptixVisibilityMask( 255 ),
                   // For shadow rays: skip any/closest hit shaders and terminate on first
                   // intersection with anything. The miss shader is used to mark if the
                   // light was visible.
                   OPTIX_RAY_FLAG_DISABLE_ANYHIT
                   | OPTIX_RAY_FLAG_TERMINATE_ON_FIRST_HIT
                   | OPTIX_RAY_FLAG_DISABLE_CLOSESTHIT,
                   SHADOW_RAY_TYPE,            // SBT offset
                   RAY_TYPE_COUNT,               // SBT stride
                   SHADOW_RAY_TYPE,            // missSBTIndex 
                   u0, u1 );
        pixelColor
          += lightVisibility
          *  optixLaunchParams.light.power
          *  diffuseColor
          *  (NdotL / (lightDist*lightDist*numLightSamples));
      }

달라진 부분은 위와 같다.
NUM_LIGHT_SAMPLES를 통해서 구형 light가 아닌 사각형의 light 중 특정 지점에서의 light만 sampling 하려고 한 것으로 보인다.
그래서 lightPos에 random을 사용한 것으로 보인다.

외에는 비슷하며 위에 대한 반증으로 pixelColor를 다음과 같이 계산한다.

        pixelColor
          += lightVisibility
          *  optixLaunchParams.light.power
          *  diffuseColor
          *  (NdotL / (lightDist*lightDist*numLightSamples));

실제로 맨 위에서 보이는 rendering은 속도는 빠르지만 자세히 보면 noise가 심하다.
하지만 아래와 같이 30,30으로 설정 시 속도는 느리지만 noise가 발생하지 않는 것을 볼 수 있다.

#define NUM_LIGHT_SAMPLES 30
#define NUM_PIXEL_SAMPLES 30

이러한 절충안으로 사용되는 것이 ai denoise이다.

0개의 댓글