8364786c |
/// Includes
#include <array>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <limits>
#include <vector>
#define GLM_FORCE_SWIZZLE
#define GLM_FORCE_INTRINSICS // For `GLM_SWIZZLE_OPERATOR`.
#include <glm/glm.hpp>
// #include <glm/gtx/io.hpp> // For `operator<<(std::ostream &)`.
/// Namespaces
using namespace glm;
/// Constants
auto const infinity = std::numeric_limits<float>::infinity();
|
86e4eca2 |
auto const delta = 0.001F;
|
8364786c |
/// Types
//// Ray
struct Ray
{
|
5590d623 |
vec3 origin;
vec3 direction;
float range;
|
86e4eca2 |
vec3 point(float distance) const
{
return origin + direction * distance;
}
Ray resumed(float distance) const
{
distance += delta;
return {point(distance), direction, range - distance};
}
|
8364786c |
};
|
d52cb1d2 |
//// Material
struct Material
{
float alpha;
};
|
8364786c |
//// Trace
struct Trace
{
|
d52cb1d2 |
float distance;
Material material;
|
8364786c |
explicit operator bool() const
{
return distance < infinity;
}
};
//// Sphere
struct Sphere
{
|
d52cb1d2 |
vec3 center;
float radius;
Material material;
|
8364786c |
Trace trace(Ray const & ray) const
{
auto const offs = center - ray.origin;
auto const proj = dot(offs, ray.direction);
if (proj < 0.0F)
|
d52cb1d2 |
return {infinity, {}}; // Past.
|
8364786c |
auto const perp2 = dot(offs, offs) - proj * proj;
auto const over2 = radius * radius - perp2;
if (over2 < 0.0F)
|
d52cb1d2 |
return {infinity, {}}; // Miss.
|
8364786c |
auto const dist = proj - sqrt(over2);
if (dist < 0.0F)
|
d52cb1d2 |
return {infinity, {}}; // Inside.
return {dist, material}; // Hit.
|
8364786c |
}
};
//// Shapes
using Shapes = std::vector<Sphere>;
//// Scene
struct Scene
{
Shapes shapes;
Trace trace(Ray const & ray) const
{
|
d52cb1d2 |
auto nearest = Trace{infinity, {}};
|
8364786c |
for (auto const & shape : shapes)
{
auto const trace = shape.trace(ray);
|
5590d623 |
if (trace.distance < min(nearest.distance, ray.range))
|
8364786c |
nearest = trace;
}
return nearest;
}
};
//// Camera
struct Camera
{
|
07a62564 |
mat4 camera_inverse;
explicit Camera(
mat4 const & view_,
mat4 const & projection_,
mat4 const & window_
)
:
camera_inverse{inverse(window_ * projection_ * view_)}
{}
vec3 unproject(vec3 const & frag_coord) const
|
8364786c |
{
|
07a62564 |
auto const world_coord = camera_inverse * vec4(frag_coord, 1.0F);
return vec3(world_coord) / world_coord.w;
}
Ray ray(vec4 const & frag_coord, vec2 const & depth_range) const
{
auto const origin = unproject(vec3(frag_coord.xy, depth_range[0]));
auto const stop = unproject(vec3(frag_coord.xy, depth_range[1]));
|
5590d623 |
auto const range = length(stop - origin);
auto const direction = (stop - origin) / range;
return Ray{origin, direction, range};
|
8364786c |
}
};
//// Framebuffer
struct Framebuffer
{
uvec2 size;
std::vector<u8vec4> color;
explicit Framebuffer(uvec2 const & size_)
:
size{size_},
color((std::size_t)(size_.x * size_.y))
{}
template<typename Shader>
|
07a62564 |
void render(uvec4 viewport, vec2 depth_range, Shader const & shader)
|
8364786c |
{
|
07a62564 |
auto const begin = max(uvec2(0), uvec2(viewport.xy));
auto const end = min(uvec2(size), uvec2(viewport.xy + viewport.zw));
auto const z = 0.5F * (depth_range[0] + depth_range[1]);
|
8364786c |
for (auto y = begin.y; y < end.y; ++y)
for (auto x = begin.x; x < end.x; ++x)
{
auto const index = size.x * y + x;
|
07a62564 |
auto const frag_coord = vec4(vec2(x, y) + 0.5F, z, 1.0F);
|
8364786c |
auto const frag_color = vec4(shader(frag_coord).bgra);
color[index] = clamp(frag_color, 0.0F, 1.0F) * 255.0F + 0.5F;
}
}
void write_tga(char const * path) const
{
auto header = std::array<uint8_t, 18>{
0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0,
(uint8_t)(size.x >> 0U),
(uint8_t)(size.x >> 8U),
(uint8_t)(size.y >> 0U),
(uint8_t)(size.y >> 8U),
32, 0,
};
auto ostream = std::ofstream(path, std::ios::binary);
write(ostream, header);
write(ostream, color);
}
template<typename Collection>
void write(std::ostream & ostream, Collection const & collection) const
{
ostream.write(
(char const *) collection.data(),
(std::streamsize)(sizeof(*collection.data()) * collection.size())
);
}
};
|
07a62564 |
/// Transformations
//// window
mat4 window(uvec4 viewport, vec2 depth_range)
{
auto const size = vec3(uvec2(viewport.zw), depth_range[1]-depth_range[0]);
auto const offs = vec3(uvec2(viewport.xy), depth_range[0]);
auto const s = 0.5F * size;
auto const t = 0.5F * size + offs;
auto const S = matrixCompMult(mat3(1.0F), outerProduct(vec3(1.0F), s));
return mat4(mat4x3(S[0], S[1], S[2], t));
}
//// ortho
mat4 ortho(float left, float right, float bottom, float top, float near, float far)
{
auto const size = vec3(right-left, top-bottom, far-near);
auto const offs = vec3( -left, -bottom, +near);
auto const s = vec3(2.0F, 2.0F, -2.0F) / size;
auto const t = s * offs - 1.0F;
auto const S = matrixCompMult(mat3(1.0F), outerProduct(vec3(1.0F), s));
return mat4(mat4x3(S[0], S[1], S[2], t));
}
//// frustum
mat4 frustum(float left, float right, float bottom, float top, float near, float far)
{
auto const z = vec4(0.0F, 0.0F, near+far, -1.0F);
auto const w = vec4(0.0F, 0.0F, near*far, 0.0F);
auto const S = mat4(near);
return ortho(left, right, bottom, top, near, far) * mat4(S[0], S[1], z, w);
}
//// perspective
mat4 perspective(float fovy, float aspect, float near, float far)
{
auto const y = near * tan(0.5F * radians(fovy));
auto const x = y * aspect;
return frustum(-x, +x, -y, +y, near, far);
}
//// lookat
mat4 lookat(vec3 position, vec3 target, vec3 up)
{
auto const z = normalize(position - target);
auto const x = normalize(cross(up, z));
auto const y = cross(z, x);
auto const R_inv = transpose(mat3(x, y, z));
auto const t_inv = -position;
return mat4(mat4x3(R_inv[0], R_inv[1], R_inv[2], R_inv * t_inv));
}
|
8364786c |
/// Main
int main(int argc, char const * argv[])
{
//// Arguments
if (argc != 2)
{
std::cerr << "Usage: raytracer <path>" << std::endl;
return EXIT_FAILURE;
}
auto const * path = argv[1]; // NOLINT
//// Configure
auto size = uvec2(640, 480);
|
07a62564 |
auto aspect = (float)size.x / (float)size.y;
auto viewport = uvec4(uvec2(0), size);
auto depth_range = vec2(0.0F, 1.0F);
|
8364786c |
auto framebuffer = Framebuffer(size);
|
07a62564 |
auto camera = Camera(
lookat( // view
{0.0F, 0.0F, 0.0F}, // position
{0.0F, 0.0F, -1.0F}, // target
{0.0F, 1.0F, 0.0F} // up
),
perspective( // projection
90.0F, // fovy
aspect, // aspect
1.0F, // near
100.0F // far
),
window(viewport, depth_range)
);
|
5590d623 |
auto scene = Scene{
|
8364786c |
{ // shapes
|
d52cb1d2 |
Sphere{{ 6.0F, -4.0F, -24.0F}, 12.0F, 1.0F},
Sphere{{ 2.0F, 6.0F, -16.0F}, 8.0F, 1.0F},
Sphere{{ -4.0F, 0.0F, -10.0F}, 4.0F, 1.0F},
Sphere{{ 0.0F, -4.0F, -6.0F}, 1.0F, 0.5F},
Sphere{{ -1.1F, 0.8F, -1.1F}, 0.2F, 1.0F},
Sphere{{-110.0F, -80.0F, -110.0F}, 20.0F, 1.0F},
|
8364786c |
},
};
//// Render
|
07a62564 |
framebuffer.render(viewport, depth_range, [&](vec4 const & frag_coord)
|
8364786c |
{
|
86e4eca2 |
auto ray = camera.ray(frag_coord, depth_range);
auto trace = Trace{};
auto traces = 0;
|
d52cb1d2 |
auto trans = 1.0F;
while (trans != 0.0F && (trace = scene.trace(ray)))
|
86e4eca2 |
{
++traces;
|
d52cb1d2 |
trans *= 1.0F - trace.material.alpha;
|
86e4eca2 |
ray = ray.resumed(trace.distance);
}
auto const rgb = vec3(1.0F / (1.0F + (float)traces));
|
d52cb1d2 |
auto const alpha = 1.0F - trans;
|
8364786c |
return vec4(rgb, alpha);
});
//// Finalize
framebuffer.write_tga(path);
return EXIT_SUCCESS;
}
|