Add initial files
This commit is contained in:
commit
bf68e43af8
16 changed files with 3567 additions and 0 deletions
72
.gitignore
vendored
Normal file
72
.gitignore
vendored
Normal 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
23
CMakeLists.txt
Normal 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
21
LICENSE
Normal 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
5
README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# Raytracer
|
||||||
|
|
||||||
|
A multi-threaded raytracer using glm, tinyobjloader, stb and C++17.
|
||||||
|
|
||||||
|

|
1
extern/CMakeLists.txt
vendored
Normal file
1
extern/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
add_subdirectory(stb)
|
2
extern/stb/CMakeLists.txt
vendored
Normal file
2
extern/stb/CMakeLists.txt
vendored
Normal 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
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
28
include/camera.h
Normal 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
17
include/image.h
Normal 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
64
include/intersections.h
Normal 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
12
include/lighting.h
Normal 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
9
include/ray.h
Normal 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
93
include/scene.h
Normal 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
2029
include/tiny_obj_loader.h
Normal file
File diff suppressed because it is too large
Load diff
BIN
misc/output.png
Normal file
BIN
misc/output.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
99
src/main.cpp
Normal file
99
src/main.cpp
Normal 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;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue