In this post, I try to implement a simple Ray-Tracing algorithm which support to render some basic shape (sphere, box, plane, disk). And the corresponding codes can be downloaded in hudongyue1/myGL (github.com) The reference is Scratchapixel .
1. The Whole Pipeline
The picture above shows the whole procedure in my simple ray-tracer.
Degree to radians 1 2 3 4 inline float deg2rad (const float °) { return deg * M_PI / 180 ; }
Clamp 1 2 3 4 inline float clamp (const float &lo, const float &hi, const float &v) { return std::max (lo, std::min (hi, v)); }
MixColor 1 2 3 4 inline Vec3f mix (const Vec3f &a, const Vec3f& b, const float &mixValue) { return a * (1 - mixValue) + b * mixValue; }
The parent class for all shape Different kind of shape should implement these virtual function separately, and the detailed method could be found in the previous post: Scratchapixel: Intersection .
1 2 3 4 5 6 7 8 9 10 11 12 class Object {public : Object () : color (dis (gen), dis (gen), dis (gen)) {} virtual ~Object () {} virtual bool intersect (const Vec3f &, const Vec3f &, float &) const = 0 ; virtual void getSurfaceData (const Vec3f &, Vec3f &, Vec2f &) const = 0 ; Vec3f color; };
3. Important Functions render The render could be splited as two steps:
Iterating over each pixel of the image, and calling the function to cast a ray for each pixel.
Using the result of as the color for each pixel, then storing the result to build the image.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 void render (const Options &options, const std::vector<std::unique_ptr<Object>> &objects) { Vec3f *framebuffer = new Vec3f[options.width * options.height]; Vec3f *pix = framebuffer; float scale = tan (deg2rad (options.fov * 0.5 )); float imageAspectRatio = options.width / (float ) options.height; Vec3f orig; options.cameraToWorld.multVecMatrix (Vec3f (0 ), orig); for (uint32_t j=0 ; j<options.height; ++j) { for (uint32_t i=0 ; i<options.width; ++i) { float x = (2 * (i + 0.5 ) / (float ) options.width - 1 ) * scale * imageAspectRatio; float y = (1 - 2 * (j + 0.5 ) / (float ) options.height) * scale; Vec3f dir; options.cameraToWorld.multDirMatrix (Vec3f (x, y, -1 ), dir); dir.normalize (); *(pix++) = castRay (orig, dir, objects); } } std::ofstream ofs ("./out/out.ppm" , std::ios::out | std::ios::binary) ; ofs << "P6\n" << options.width << " " << options.height << "\n225\n" ; for (uint32_t i=0 ; i<options.height * options.width; ++i) { char r = (char )(255 *clamp (0 , 1 , framebuffer[i].x)); char g = (char )(255 *clamp (0 , 1 , framebuffer[i].y)); char b = (char )(255 *clamp (0 , 1 , framebuffer[i].z)); ofs << r << g << b; } ofs.close (); delete [] framebuffer; }
castRay Using the result of to compute the color for the specific ray.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Vec3f castRay (const Vec3f &orig, const Vec3f &dir, const std::vector<std::unique_ptr<Object>> &objects) { Vec3f hitColor = 0 ; const Object *hitObject = nullptr ; float t; if (trace (orig, dir, objects, t, hitObject)) { Vec3f Phit = orig + dir * t; Vec3f Nhit; Vec2f tex; hitObject->getSurfaceData (Phit, Nhit, tex); float scale = 4 ; float pattern = (fmodf (tex.x * scale, 1 ) > 0.5 ) ^ (fmodf (tex.y * scale, 1 ) > 0.5 ); hitColor = std::max (0.f , Nhit.dotProduct (-dir)) * mix (hitObject->color, hitObject->color * 0.8 , pattern); } return hitColor; }
trace Iterating all the objects in world space, finding the first intersection with these objects for the specific ray (if do not parallel).
1 2 3 4 5 6 7 8 9 10 11 12 13 bool trace (const Vec3f &orig, const Vec3f &dir, const std::vector<std::unique_ptr<Object>> &objects, float &tNear, const Object *&hitObject) { tNear = kInfinity; std::vector<std::unique_ptr<Object>>::const_iterator iter = objects.begin (); for (; iter != objects.end (); ++iter) { float t = kInfinity; if ((*iter)->intersect (orig, dir, t) && t < tNear) { hitObject = iter->get (); tNear = t; } } return (hitObject != nullptr ); }