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이다.