1
Fork 0

Add initial files

This commit is contained in:
redstrate 2020-02-17 10:33:56 -05:00
commit bf68e43af8
16 changed files with 3567 additions and 0 deletions

72
.gitignore vendored Normal file
View file

@ -0,0 +1,72 @@
build/
*build/
.DS_Store
CMakeLists.txt.user
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
_deps
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Gcc Patch
/*.gcno
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app

23
CMakeLists.txt Normal file
View file

@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 2.6)
project(raytracer)
find_package(GLM REQUIRED)
add_subdirectory(extern)
add_executable(raytracer
include/camera.h
include/intersections.h
include/lighting.h
include/ray.h
include/image.h
include/tiny_obj_loader.h
include/scene.h
src/main.cpp)
target_include_directories(raytracer PUBLIC include)
target_link_libraries(raytracer PRIVATE stb glm)
set_target_properties(raytracer PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO
)

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Joshua Goins
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Raytracer
A multi-threaded raytracer using glm, tinyobjloader, stb and C++17.
![example result](https://raw.githubusercontent.com/redstrate/raytracer/master/misc/output.png)

1
extern/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1 @@
add_subdirectory(stb)

2
extern/stb/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,2 @@
add_library(stb INTERFACE)
target_include_directories(stb INTERFACE include)

1092
extern/stb/include/stb_image_write.h vendored Normal file

File diff suppressed because it is too large Load diff

28
include/camera.h Normal file
View file

@ -0,0 +1,28 @@
#pragma once
#include "ray.h"
class Camera {
public:
Camera() : position(glm::vec3(0)), direction(glm::vec3(0)) {}
Camera(glm::vec3 position, glm::vec3 direction) : position(position), direction(direction) {}
void look_at(glm::vec3 eye, glm::vec3 target) {
position = eye;
direction = glm::normalize(target - eye);
}
Ray get_ray(const int32_t x, const int32_t y, const int32_t width, const int32_t height) const {
const glm::vec3 up = glm::vec3(0, 1, 0);
const glm::vec3 right = glm::normalize(glm::cross(direction, up));
const float h2 = height / 2.0f;
const float w2 = width / 2.0f;
glm::vec3 ray_dir = position + (h2 / tan(glm::radians(fov) / 2)) * direction + (y - h2) * up + (float)(x - w2) * right;
return Ray(position, ray_dir);
}
glm::vec3 position, direction;
float fov = 45.0f;
};

17
include/image.h Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <glm/glm.hpp>
template<class T, int Width, int Height>
class Image {
public:
T& get(const int32_t x, const int32_t y) {
return array[y * Width + x];
}
T get(const int32_t x, const int32_t y) const {
return array[y * Width + x];
}
T array[Width * Height] = {};
};

64
include/intersections.h Normal file
View file

@ -0,0 +1,64 @@
#pragma once
#include <glm/glm.hpp>
#include "ray.h"
constexpr float epsilon = std::numeric_limits<float>().epsilon();
namespace intersections {
bool ray_sphere(const Ray ray, const glm::vec4 sphere) {
const glm::vec3 diff = ray.origin - glm::vec3(sphere);
const float b = glm::dot(ray.direction, diff);
const float c = glm::dot(diff, diff) - sphere.w * sphere.w;
float t = b * b - c;
if (t > 0.0) {
t = -b - glm::sqrt(t);
if (t > 0.0)
return true;
}
return false;
}
float ray_triangle(const Ray ray,
const glm::vec3 v0,
const glm::vec3 v1,
const glm::vec3 v2,
float& t,
float& u,
float& v) {
glm::vec3 e1 = v1 - v0;
glm::vec3 e2 = v2 - v0;
glm::vec3 pvec = glm::cross(ray.direction, e2);
float det = glm::dot(e1, pvec);
// if determinant is zero then ray is
// parallel with the triangle plane
if (det > -epsilon && det < epsilon) return false;
float invdet = 1.0/det;
// calculate distance from m[0] to origin
glm::vec3 tvec = ray.origin - v0;
// u and v are the barycentric coordinates
// in triangle if u >= 0, v >= 0 and u + v <= 1
u = glm::dot(tvec, pvec) * invdet;
// check against one edge and opposite point
if (u < 0.0 || u > 1.0) return false;
glm::vec3 qvec = glm::cross(tvec, e1);
v = glm::dot(ray.direction, qvec) * invdet;
// check against other edges
if (v < 0.0 || u + v > 1.0) return false;
//distance along the ray, i.e. intersect at o + t * d
t = glm::dot(e2, qvec) * invdet;
return true;
}
};

12
include/lighting.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <glm/glm.hpp>
namespace lighting {
float point_light(const glm::vec3 pos, const glm::vec3 light, const glm::vec3 normal) {
const glm::vec3 dir = light - pos;
const float n_dot_l = glm::max(glm::dot(normal, glm::normalize(dir)), 0.0f);
return n_dot_l;
}
};

9
include/ray.h Normal file
View file

@ -0,0 +1,9 @@
#pragma once
#include <glm/glm.hpp>
struct Ray {
Ray(const glm::vec3 origin, const glm::vec3 direction) : origin(origin), direction(direction) {}
glm::vec3 origin, direction;
};

93
include/scene.h Normal file
View file

@ -0,0 +1,93 @@
#pragma once
#include <optional>
#include <tiny_obj_loader.h>
struct Scene {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
bool load_from_file(const std::string_view path) {
return tinyobj::LoadObj(&attrib, &shapes, &materials, nullptr, path.data());
}
};
inline glm::vec3 fetch_position(const Scene& scene, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) {
tinyobj::index_t idx = mesh.indices[(index * 3) +vertex];
tinyobj::real_t vx = scene.attrib.vertices[3*idx.vertex_index+0];
tinyobj::real_t vy = scene.attrib.vertices[3*idx.vertex_index+1];
tinyobj::real_t vz = scene.attrib.vertices[3*idx.vertex_index+2];
return glm::vec3(vx, vy, vz);
}
inline glm::vec3 fetch_normal(const Scene& scene, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) {
tinyobj::index_t idx = mesh.indices[(index * 3) + vertex];
tinyobj::real_t nx = scene.attrib.normals[3*idx.normal_index+0];
tinyobj::real_t ny = scene.attrib.normals[3*idx.normal_index+1];
tinyobj::real_t nz = scene.attrib.normals[3*idx.normal_index+2];
return glm::vec3(nx, ny, nz);
}
struct HitResult {
glm::vec3 position, normal;
tinyobj::mesh_t* mesh = nullptr;
};
std::optional<HitResult> test_mesh(const Ray ray, const Scene& scene, const tinyobj::mesh_t& mesh, float& tClosest) {
bool intersection = false;
HitResult result = {};
for (size_t i = 0; i < mesh.num_face_vertices.size(); i++) {
const glm::vec3 v0 = fetch_position(scene, mesh, i, 0);
const glm::vec3 v1 = fetch_position(scene, mesh, i, 1);
const glm::vec3 v2 = fetch_position(scene, mesh, i, 2);
float t = std::numeric_limits<float>::infinity(), u, v;
if (intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) {
if (t < tClosest && t > epsilon) {
const glm::vec3 n0 = fetch_normal(scene, mesh, i, 0);
const glm::vec3 n1 = fetch_normal(scene, mesh, i, 1);
const glm::vec3 n2 = fetch_normal(scene, mesh, i, 2);
result.normal = (1 - u - v) * n0 + u * n1 + v * n2;
result.position = ray.origin + ray.direction * t;
tClosest = t;
intersection = true;
}
}
}
if(intersection)
return result;
else
return {};
}
std::optional<HitResult> test_scene(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits<float>::infinity()) {
bool intersection = false;
HitResult result = {};
for (uint32_t i = 0; i < scene.shapes.size(); i++) {
auto mesh = scene.shapes[i].mesh;
if(auto hit = test_mesh(ray, scene, mesh, tClosest)) {
intersection = true;
result = hit.value();
result.mesh = &mesh;
}
}
if(intersection)
return result;
else
return {};
}

2029
include/tiny_obj_loader.h Normal file

File diff suppressed because it is too large Load diff

BIN
misc/output.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

99
src/main.cpp Normal file
View file

@ -0,0 +1,99 @@
#include <iostream>
#include <limits>
#include <future>
#include <vector>
#include <glm/glm.hpp>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>
#include "intersections.h"
#include "camera.h"
#include "image.h"
#include "lighting.h"
#include "scene.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include <tiny_obj_loader.h>
// scene information
constexpr int32_t width = 512, height = 512;
constexpr glm::vec3 light_position = glm::vec3(5);
const Camera camera = [] {
Camera camera;
camera.look_at(glm::vec3(0, 0, 4), glm::vec3(0));
return camera;
}();
constexpr std::string_view model_path = "suzanne.obj";
// internal variables
constexpr float light_bias = 0.01f;
constexpr int32_t tile_size = 32;
constexpr int32_t num_tiles_x = width / tile_size;
constexpr int32_t num_tiles_y = height / tile_size;
// globals
Scene scene = {};
Image<glm::ivec4, width, height> colors = {};
bool calculate_tile(const int32_t from_x, const int32_t to_width, const int32_t from_y, const int32_t to_height) {
for (int32_t y = from_y; y < (from_y + to_height); y++) {
for (int32_t x = from_x; x < (from_x + to_width); x++) {
Ray ray_camera = camera.get_ray(x, y, width, height);
if (auto hit = test_scene(ray_camera, scene)) {
const float diffuse = lighting::point_light(hit->position, light_position, hit->normal);
//shadow calculation
bool blocked = false;
if(glm::dot(light_position - hit->position, hit->normal) > 0) {
const glm::vec3 light_dir = glm::normalize(light_position - hit->position);
const Ray shadow_ray(hit->position + (hit->normal * light_bias), light_dir);
if(test_scene(shadow_ray, scene))
blocked = true;
} else {
blocked = true;
}
const int32_t finalColor = diffuse * 255 * !blocked;
colors.get(x, y) = glm::ivec4(finalColor, finalColor, finalColor, 1);
}
}
}
return true;
}
int main(int, char*[]) {
if(!scene.load_from_file(model_path))
return -1;
std::vector<std::future<bool>> futures;
for(int32_t y = 0; y < num_tiles_y; y++)
for(int32_t x = 0; x < num_tiles_x; x++)
futures.push_back(std::async(std::launch::async, calculate_tile, x * tile_size, tile_size, y * tile_size, tile_size));
for(auto& future : futures)
future.wait();
std::cout << "rendering done!!" << std::endl;
uint8_t pixels[width * height * 3] = {};
int i = 0;
for (int32_t y = height - 1; y >= 0; y--) {
for (int32_t x = 0; x < width; x++) {
const glm::ivec4 c = colors.get(x, y);
pixels[i++] = c.r;
pixels[i++] = c.g;
pixels[i++] = c.b;
}
}
if(stbi_write_png("output.png", width, height, 3, pixels, width * 3) != 1)
return -1;
return 0;
}