
Ray Tracing은 reflections, refractions, shadows, indirect lighting을 포함하여 scene의 빛과 물체를 실제와 같이 표현하기 위해 사용하는 렌더링 기술입니다. scene을 관통하면서 빛은 아래의 특징들을 갖게 될 수 있습니다.

ray가 scene 내의 물체를 마주했을 때, 해당 물체 표면의 point가 갖는 color와 lighting 정보를 얻습니다. ray가 bounce off되거나 표면을 뚫고 travel하면 해당 물체들의 color, lighting정보들이 final color를 구성하게 됩니다. (rendering)
Rasterization은 3d 물체를 2d screen에 나타내는 기술입니다. rasterization을 통해 screen의 물체는 가상의 traingles 혹은 polygons의 mesh로 표현됩니다. triangle의 corners(=vertices)는 position, color, texture, normal(물체의 표면이 향하는 방향 벡터) 등의 정보들을 담게 됩니다.
각 픽셀은 초기화된 color를 갖고 있지만, pixel이 처리되거나 shading이 이뤄지면 final color를 업데이트합니다.
Rasterization은 Ray Tracing에 비해서는 연산량이 많지 않아, real time에 사용됩니다. 하지만 Ray Tracing이 물리적으로 scene을 더 정확하게 표현할 수 있습니다. 두 가지 렌더링을 동시에 사용하는 hybrid 방법도 존재합니다.
카메라로 image plane에 여러 ray를 쏩니다. 이때 ray가 어떤 scene 내 primitives의 intersect하면, ray origin과 primitive까지의 거리가 구해집니다. 이를 통해 primitive가 가지는 pixel color 정보를 획득합니다.
single object를 구로 설정하여 hit에 대해 알아보겠습니다.
center가 인 구의 방정식은 위와 같습니다. 이때 를 ray로 표현하고, 이에 대해 식을 정리하면 아래와 같습니다.
위의 식을 에 대한 이차방정식으로 바라보게 되면, 우리는 t에 대한 해를 구할 수 있습니다.

double hit_sphere(const point3& center, double radius, const ray& r) {
vec3 oc = center - r.origin();
auto a = r.direction().length_squared();
auto h = dot(r.direction(), oc);
auto c = oc.length_squared() - radius*radius;
auto discriminant = h*h - a*c;
if (discriminant < 0) {
return -1.0;
} else {
return (h - std::sqrt(discriminant)) / a;
}
}
color ray_color(const ray& r) {
auto t = hit_sphere(point3(0,0,-1), 0.5, r);
if (t > 0.0) {
vec3 N = unit_vector(r.at(t) - vec3(0,0,-1));
return 0.5*color(N.x()+1, N.y()+1, N.z()+1);
}
vec3 unit_direction = unit_vector(r.direction());
auto a = 0.5*(unit_direction.y() + 1.0);
return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}
quadratic을 적용하여 closest point를 해로 반환합니다. 방정식 discriminant가 0보다 작은 경우는 해가 존재하지 않습니다. (=hit X) 해가 존재하지 않는 경우에는 배경색을 반환합니다. 해가 존재하는 경우 hitting point t normal 계산 후, rendering color에 반영합니다.
구가 하나가 아닐 경우나, 구가 아닌 다른 형태의 물체가 ray에 닿을 수도 있습니다. 이때 object oriented가 아닌 경우에는, surface를 사용하여 hit을 표현합니다. volume을 갖는 물체들을 위주로 다루며, 해당 물체들을 hittable objects로 표현하겠습니다. (구름 등의 물체 제외
auto sqrtd = std::sqrt(discriminant);
// Find the nearest root that lies in the acceptable range.
auto root = (h - sqrtd) / a;
if (root <= ray_tmin || ray_tmax <= root) {
root = (h + sqrtd) / a;
if (root <= ray_tmin || ray_tmax <= root)
return false;
}
rec.t = root;
rec.p = r.at(rec.t);
rec.normal = (rec.p - center) / radius;
return true;
hittable sphere 코드의 일부입니다. hitting point t가 인 경우에만 해당 포인트를 사용합니다. closest point가 해당 범위에 없을 경우, far point를 t로 사용합니다. 이를 통해 normal까지 구할 수 있습니다.

피사체를 표현하기 위해서는 outside normal과 ray가 반대되는 방향에 위치해야 합니다. 이러한 상황을 front face로 정의하고, 이에 따른 hittable object의 normal을 정의할 수 있습니다.
void set_face_normal(const ray& r, const vec3& outward_normal) {
// Sets the hit record normal vector.
// NOTE: the parameter `outward_normal` is assumed to have unit length.
front_face = dot(r.direction(), outward_normal) < 0;
normal = front_face ? outward_normal : -outward_normal;
}

ray tracing을 통해 표현할 물체를 diffuse해야 합니다. 이번에도 구를 예시로 두겠습니다. 빛이 물체에 닿았을 때, 물체는 자신의 내재적인 color를 통해 빛을 일부 랜덤한 방향으로 방출합니다. (100% 방출 -> 흰색 / 0% 방출 -> 검정색)
이때 Lambertian Reflection을 고려하여, normal과 ray 사이 각도인 안에서만 random unit vector가 생성되도록 합니다.
inline vec3 random_on_hemisphere(const vec3& normal) {
vec3 on_unit_sphere = random_unit_vector();
if (dot(on_unit_sphere, normal) > 0.0) // In the same hemisphere as the normal
return on_unit_sphere;
else
return -on_unit_sphere;
}
class camera {
...
color ray_color(const ray& r, int depth, const hittable& world) const {
// If we've exceeded the ray bounce limit, no more light is gathered.
if (depth <= 0)
return color(0,0,0);
hit_record rec;
if (world.hit(r, interval(0.001, infinity), rec)) {
vec3 direction = rec.normal + random_unit_vector();
return 0.5 * ray_color(ray(rec.p, direction), depth-1, world); # for gray color -> 0.5 *
}
vec3 unit_direction = unit_vector(r.direction());
auto a = 0.5*(unit_direction.y() + 1.0);
return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}
};

BVH는 tree 구조를 바탕으로 구현된 ray tracing 가속 기술입니다. 여러 개의 계층적으로 정렬된 bounding boxes로 scene geometry 혹은 primitives를 표현합니다. depth-first tree로 구성된 BVH를 활용해 각 ray가 렌더링 됩니다. scene이 업데이트되면, BVH를 다시 빌드하거나 refitting합니다.
Ray-object intersection은 ray tracing의 주요한 병목 현상 원인입니다. 같은 scene에서 여러 개의 objects를 linear run time으로 처리하기 때문입니다. 따라서 이를 binary search와 같은 logarithmic search로 구현하였습니다.
방법은 보통 아래의 두 가지로 사용됩니다.
1) space를 subdivide합니다.
2) objects를 subdivide합니다.
axis-aligned bounding boxes를 통하여 빠르고 compact한 bounding volume을 계산합니다. n차원의 AABB가 n개의 axis-aligned 인터벌과 교차하는지 파악하는 slab방법을 이용하여 AABB를 구현합니다.

bool overlaps(t_interval1, t_interval2)
t_min ← max(t_interval1.min, t_interval2.min)
t_max ← min(t_interval1.max, t_interval2.max)
return t_min < t_max
위의 코드와 그림을 같이 보시면 더 이해하기 쉬울 것입니다. interveal min끼리의 max와 interval max끼리의 min을 비교하였을 때, t_min < t_max인 경우가 overlap하는 것을 확인할 수 있습니다.(=보라색 체크박스)
class bvh_node : public hittable {
public:
bvh_node(hittable_list list) : bvh_node(list.objects, 0, list.objects.size()) {
// There's a C++ subtlety here. This constructor (without span indices) creates an
// implicit copy of the hittable list, which we will modify. The lifetime of the copied
// list only extends until this constructor exits. That's OK, because we only need to
// persist the resulting bounding volume hierarchy.
}
bvh_node(std::vector<shared_ptr<hittable>>& objects, size_t start, size_t end) {
int axis = random_int(0,2);
auto comparator = (axis == 0) ? box_x_compare
: (axis == 1) ? box_y_compare
: box_z_compare;
size_t object_span = end - start;
if (object_span == 1) {
left = right = objects[start];
} else if (object_span == 2) {
left = objects[start];
right = objects[start+1];
} else {
std::sort(std::begin(objects) + start, std::begin(objects) + end, comparator);
auto mid = start + object_span/2;
left = make_shared<bvh_node>(objects, start, mid);
right = make_shared<bvh_node>(objects, mid, end);
}
bbox = aabb(left->bounding_box(), right->bounding_box());
}
bool hit(const ray& r, interval ray_t, hit_record& rec) const override {
if (!bbox.hit(r, ray_t))
return false;
bool hit_left = left->hit(r, ray_t, rec);
bool hit_right = right->hit(r, interval(ray_t.min, hit_left ? rec.t : ray_t.max), rec);
return hit_left || hit_right;
}
aabb bounding_box() const override { return bbox; }
private:
shared_ptr<hittable> left;
shared_ptr<hittable> right;
aabb bbox;
};
#endif
hit은 위에서 설명한 overlap 여부로 판단합니다. bvh를 빌드할 때, objects를 정렬한 후에 binary search 기반으로 bbox를 저장합니다.

ray tracing의 pipeline입니다. 각 기능은 아래에서 간략하게 살펴보겠습니다. 파이프라인이 작동하는 동안, 각 GPU thread가 하나의 ray를 순차적으로 다룹니다. 즉, 다른 thread가 처리되는 동안 communication은 불가능합니다.
다른 shader들과 소통하는 방법은 ray payload입니다. payload는 임의로 정의한 구조체이며, TraceRay()의 inout 파라미터로 적용됩니다.
Ray Generation은 TraceRay() 함수로 single ray를 casting하여, scene 내에서 intersection을 찾습니다.
// This is a struct used to communicate launch parameters which are constant
// for all threads in a given optixLaunch call.
struct Params
{
uchar4* image;
unsigned int image_width;
unsigned int image_height;
float3 cam_eye;
float3 cam_u, cam_v, cam_w;
OptixTraversableHandle handle;
};
extern "C"
{
__constant__ Params params;
}
// Note the __raygen__ prefix which marks this as a ray-generation
// program function
extern "C" __global__ void __raygen__rg()
{
// Lookup our location within the launch grid
const uint3 idx = optixGetLaunchIndex();
const uint3 dim = optixGetLaunchDimensions();
// Map our launch idx to a screen location and create a ray from // the camera location through the screen
float3 ray_origin, ray_direction;
computeRay( idx, dim, ray_origin, ray_direction );
// Trace the ray against our scene hierarchy
unsigned int p0, p1, p2;
optixTrace(
params.handle,
ray_origin,
ray_direction,
0.0f, // Min intersection distance
1e16f, // Max intersection distance
0.0f, // ray-time -- used for motion blur
OptixVisibilityMask( 255 ), // Specify always visible
OPTIX_RAY_FLAG_NONE,
0, // SBT offset -- See SBT discussion
0, // SBT stride -- See SBT discussion 0, // missSBTIndex -- See SBT discussion
p0, p1, p2 ); // These 32b values are the ray payload
// Our results were packed into opaque 32b registers
float3 result;
result.x = int_as_float( p0 );
result.y = int_as_float( p1 );
result.z = int_as_float( p2 );
// Record results in our output raster
params.image[idx.y * params.image_width + idx.x] = make_color( result );
}
OptiX의 ray generation 코드입니다. computeRay로 ray origin과 ray direction 정보를 얻은 후, optixTrace 함수로 ray payload 정보를 얻습니다. buffer를 활용하는 것도 가능합니다.
TraceRay()가 potential intersection을 찾으면, intersection의 기하학적 primitive를 계산합니다. (sphere, surface, etc)
TracoRay()가 ray-scene intersection을 찾은 경우, 해당 프로그램이 실행됩니다. closest hit은 material evlauation이나 texture lookup 등을 수행합니다.
extern "C" __global__ void __closesthit__ch()
{
// When built-in triangle intersection is used, a number of fundamental // attributes are provided by the OptiX API, including barycentric // coordinates.
const float2 barycentrics = optixGetTriangleBarycentrics();
// Convert to color and assign to our payload outputs.
const float3 c = make_float3( barycentrics, 1.0f );
optixSetPayload_0( float_as_int( c.x ) );
optixSetPayload_1( float_as_int( c.y ) );
optixSetPayload_2( float_as_int( c.z ) );
}
triangle intersection이 사용될 때, barycentric 요소를 구성한 후, payload에 color 값을 저장합니다.
intersection이 발견되지 않았을 때, miss program이 실행됩니다. miss program은 background나 environment shader를 제공합니다.
extern "C" __global__ void __miss__ms()
{
MissData* miss_data =
reinterpret_cast<MissData*>( optixGetSbtDataPointer() );
setPayload( miss_data->bg_color );
}

Ray Tracing에서 렌더링 속도를 높이기 위해, two-level hierarchy 구조를 사용합니다. bottom-level에서는 기하학적 primitives(i.e. triangles)가 빌드됩니다. geometry descriptor는 vertex, index buffer의 정보를 포함합니다. top-level에서는 bottom-level 구조를 참조하는 instance descriptors로 빌드됩니다. 각 instance descriptor는 transformation matrix 정보와 shader table의 offset 정보를 포함합니다.
[1] Ray Tracing , NVIDIA, https://developer.nvidia.com/discover/ray-tracing
[2] RTX DirectX Ray Tracing, NVIDIA, https://developer.nvidia.com/blog/introduction-nvidia-rtx-directx-ray-tracing/
[3] Ray Tracing In One Weekend, https://raytracing.github.io/books/RayTracingInOneWeekend.html#rays,asimplecamera,andbackground/therayclass
[4] How to get started with OptiX 7, NVIDIA, https://developer.nvidia.com/blog/how-to-get-started-with-optix-7/