├── .gitattributes ├── output ├── bi.png ├── balls2.png ├── boxgi.png ├── cornel.png ├── glitch.png ├── norm.png ├── norm1.png ├── shiny.png ├── fresnel1.png ├── texture.png ├── myPlanes2.png ├── myPlanes3.png └── valentine.png ├── .gitmodules ├── .gitignore ├── src ├── Light.hpp ├── Camera.hpp ├── Box.hpp ├── Light.cpp ├── Plane.hpp ├── Ray.hpp ├── Sphere.hpp ├── BoundingBox.hpp ├── Triangle.hpp ├── BoxRenderable.hpp ├── TextureBatch.hpp ├── Ray.cpp ├── Texture.hpp ├── Camera.cpp ├── TextureBatch.cpp ├── Intersection.hpp ├── Plane.cpp ├── Renderer.hpp ├── BRDF.hpp ├── Box.cpp ├── BoxRenderable.cpp ├── Sphere.cpp ├── Scene.hpp ├── Loader.hpp ├── Texture.cpp ├── Intersection.cpp ├── Triangle.cpp ├── BoundingBox.cpp ├── Renderer.cpp ├── GeoObject.hpp ├── Scene.cpp ├── main.cpp ├── BRDF.cpp ├── Loader.cpp └── stb_image_write.h ├── run.sh ├── myplanes.pov ├── CMakeLists.txt └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | *.h linguist-language=C++ 3 | *.hpp linguist-language=C++ 4 | -------------------------------------------------------------------------------- /output/bi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/bi.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/glm"] 2 | path = ext/glm 3 | url = https://github.com/g-truc/glm 4 | -------------------------------------------------------------------------------- /output/balls2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/balls2.png -------------------------------------------------------------------------------- /output/boxgi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/boxgi.png -------------------------------------------------------------------------------- /output/cornel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/cornel.png -------------------------------------------------------------------------------- /output/glitch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/glitch.png -------------------------------------------------------------------------------- /output/norm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/norm.png -------------------------------------------------------------------------------- /output/norm1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/norm1.png -------------------------------------------------------------------------------- /output/shiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/shiny.png -------------------------------------------------------------------------------- /output/fresnel1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/fresnel1.png -------------------------------------------------------------------------------- /output/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/texture.png -------------------------------------------------------------------------------- /output/myPlanes2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/myPlanes2.png -------------------------------------------------------------------------------- /output/myPlanes3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/myPlanes3.png -------------------------------------------------------------------------------- /output/valentine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaafersheriff/CPU-Ray-Tracer/HEAD/output/valentine.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | 3 | build/ 4 | tests/ 5 | res/ 6 | .vscode/ 7 | 8 | outt 9 | 10 | .scannerwork/ 11 | sonar-project.properties 12 | 13 | raytrace.* 14 | 15 | output.png 16 | 17 | *.py 18 | compare.sh 19 | run.sh 20 | -------------------------------------------------------------------------------- /src/Light.hpp: -------------------------------------------------------------------------------- 1 | // Light class 2 | // Solely holds light data 3 | 4 | #pragma once 5 | #ifndef _LIGHT_H_ 6 | #define _LIGHT_H_ 7 | 8 | #include // vec3 9 | #include // print 10 | 11 | class Light { 12 | public: 13 | Light(); 14 | void print(); 15 | 16 | glm::vec3 position; 17 | glm::vec3 color; 18 | }; 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | # Pre 4 | clear 5 | rm out* 6 | 7 | #Build 8 | echo "Building..." 9 | cd build 10 | cmake .. 11 | make 12 | 13 | # If successful compile 14 | if [ $? -eq 0 ] 15 | then 16 | cd .. 17 | time ./build/raytrace $@ 18 | 19 | # If render flag is set 20 | if [ "$1" == "render" ] 21 | then 22 | cygstart.exe output.png 23 | fi 24 | else 25 | cd .. 26 | echo "FAIL" 27 | fi 28 | -------------------------------------------------------------------------------- /src/Camera.hpp: -------------------------------------------------------------------------------- 1 | // Camera class 2 | // Only functionality is storing data and print 3 | #pragma once 4 | #ifndef _CAMERA_H_ 5 | #define _CAMERA_H_ 6 | 7 | #include // vec3 8 | #include // std::cout 9 | 10 | class Camera { 11 | public: 12 | Camera(); 13 | void print(); 14 | 15 | glm::vec3 location; 16 | glm::vec3 up; 17 | glm::vec3 right; 18 | glm::vec3 lookAt; 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/Box.hpp: -------------------------------------------------------------------------------- 1 | #ifndef _BOX_H_ 2 | #define _BOX_H_ 3 | 4 | #include 5 | #include "Ray.hpp" 6 | 7 | class Box { 8 | public: 9 | Box(); 10 | Box(glm::vec3, glm::vec3); 11 | 12 | void updateBox(glm::vec3, glm::vec3); 13 | 14 | float intersect(const Ray &); 15 | void updateT(const Ray &, float*, float*, int); 16 | bool hasBeenInit(); 17 | 18 | glm::vec3 minCorner, maxCorner; 19 | glm::vec3 center; 20 | 21 | void print(); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/Light.cpp: -------------------------------------------------------------------------------- 1 | #include "Light.hpp" 2 | 3 | Light::Light() { 4 | this->position = glm::vec3(0, 0, 0); 5 | this->color = glm::vec3(0, 0, 0); 6 | } 7 | 8 | void Light::print() { 9 | std::cout << "- Location: {"; 10 | std::cout << position.x << " " << position.y << " " << position.z; 11 | std::cout << "}" << std::endl; 12 | std::cout << "- Color: {"; 13 | std::cout << color.x << " " << color.y << " " << color.z; 14 | std::cout << "}" << std::endl; 15 | } 16 | -------------------------------------------------------------------------------- /src/Plane.hpp: -------------------------------------------------------------------------------- 1 | // Plane class extends GeoObject 2 | 3 | #pragma once 4 | #ifndef _PLANE_H_ 5 | #define _PLANE_H_ 6 | 7 | #include "GeoObject.hpp" 8 | 9 | class Plane: public GeoObject { 10 | public: 11 | Plane(); 12 | 13 | glm::vec3 normal; 14 | float distance; 15 | 16 | float intersect(const Ray &); 17 | glm::vec2 getUVCoords(glm::vec3); 18 | glm::vec3 findNormal(glm::vec3); 19 | glm::vec3 findCenter(); 20 | BoundingBox* createBox(); 21 | void print(); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/Ray.hpp: -------------------------------------------------------------------------------- 1 | // Ray class 2 | 3 | #pragma once 4 | #ifndef _RAY_H_ 5 | #define _RAH_H_ 6 | 7 | #include // cout 8 | #include "glm/glm.hpp" 9 | 10 | class Ray { 11 | public: 12 | Ray(); 13 | Ray(glm::vec3, glm::vec3); 14 | void set(const glm::vec3, const glm::vec3); 15 | 16 | glm::vec3 position; 17 | glm::vec3 direction; 18 | 19 | // Calculates the point of intersection given a value t 20 | glm::vec3 calculatePoint(float); 21 | 22 | void print(); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/Sphere.hpp: -------------------------------------------------------------------------------- 1 | // Sphere class extends GeoObject 2 | 3 | #pragma once 4 | #ifndef _SPHERE_H_ 5 | #define _SPHERE_H_ 6 | 7 | #include "GeoObject.hpp" 8 | 9 | class Sphere: public GeoObject { 10 | public: 11 | Sphere(); 12 | 13 | glm::vec3 center; 14 | float radius; 15 | 16 | float intersect(const Ray &); 17 | glm::vec2 getUVCoords(glm::vec3); 18 | glm::vec3 findNormal(glm::vec3); 19 | glm::vec3 findCenter(); 20 | BoundingBox* createBox(); 21 | void print(); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /src/BoundingBox.hpp: -------------------------------------------------------------------------------- 1 | // Bounding box class 2 | // Implements Box class 3 | // Includes a list of objects witihn this bounding box 4 | 5 | #pragma once 6 | #ifndef _BOUNDING_BOX_H_ 7 | #define _BOUNDING_BOX_H_ 8 | 9 | #include 10 | 11 | #include "Box.hpp" 12 | 13 | class BoundingBox : public Box { 14 | public: 15 | BoundingBox(); 16 | BoundingBox(glm::vec3, glm::vec3); 17 | 18 | void addPoint(glm::vec3); 19 | void addBox(BoundingBox *); 20 | void transform(glm::mat4 &); 21 | }; 22 | 23 | #endif -------------------------------------------------------------------------------- /src/Triangle.hpp: -------------------------------------------------------------------------------- 1 | // Triangle class extends GeoObject 2 | 3 | #pragma once 4 | #ifndef _TRIANGLE_H_ 5 | #define _TRIANGLE_H_ 6 | 7 | #include "GeoObject.hpp" 8 | 9 | class Triangle: public GeoObject { 10 | public: 11 | Triangle(); 12 | 13 | glm::vec3 v1; 14 | glm::vec3 v2; 15 | glm::vec3 v3; 16 | 17 | float intersect(const Ray &); 18 | glm::vec2 getUVCoords(glm::vec3); 19 | glm::vec3 findNormal(glm::vec3); 20 | glm::vec3 findCenter(); 21 | BoundingBox* createBox(); 22 | void print(); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/BoxRenderable.hpp: -------------------------------------------------------------------------------- 1 | // Renderable box 2 | // Implements Box and GeoObject 3 | 4 | #pragma once 5 | #ifndef _BOX_RENDERABLE_H_ 6 | #define _BOX_RENDERABLE_H_ 7 | 8 | #include "GeoObject.hpp" 9 | #include "Box.hpp" 10 | 11 | class BoxRenderable : public GeoObject, Box { 12 | public: 13 | BoxRenderable(); 14 | 15 | void updateBox(glm::vec3, glm::vec3); 16 | 17 | float intersect(const Ray &); 18 | glm::vec2 getUVCoords(glm::vec3); 19 | glm::vec3 findNormal(glm::vec3); 20 | glm::vec3 findCenter(); 21 | BoundingBox *createBox(); 22 | void print(); 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/TextureBatch.hpp: -------------------------------------------------------------------------------- 1 | // Texture batch class 2 | // Holds on to all loaded textures 3 | // Allows multiple objects to use the same texture without having 4 | // to load a new texture per objects 5 | 6 | #pragma once 7 | #ifndef _TEXTURE_BATCH_H_ 8 | #define _TEXTURE_BATCH_H_ 9 | 10 | #include 11 | 12 | #include "Texture.hpp" 13 | 14 | class TextureBatch { 15 | public: 16 | // List of Textures 17 | std::vector textures; 18 | 19 | // Get a texture from the list 20 | Texture* getTexture(std::string, Texture::Type); 21 | 22 | void print(); 23 | private: 24 | // Add a new texture to the list 25 | Texture* addTexture(std::string, Texture::Type); 26 | }; 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/Ray.cpp: -------------------------------------------------------------------------------- 1 | #include "Ray.hpp" 2 | 3 | Ray::Ray() { 4 | this->position = glm::vec3(0, 0, 0); 5 | this->direction = glm::vec3(0, 0, 0); 6 | } 7 | 8 | Ray::Ray(glm::vec3 pos, glm::vec3 dir) { 9 | this->position = pos; 10 | this->direction = dir; 11 | } 12 | 13 | void Ray::set(const glm::vec3 p, const glm::vec3 d) { 14 | this->position = glm::vec3(p); 15 | this->direction = glm::vec3(d); 16 | } 17 | 18 | glm::vec3 Ray::calculatePoint(float t) { 19 | return position + t * direction; 20 | } 21 | 22 | void Ray::print() { 23 | std::cout << "{" << position.x << " " << position.y << " " << position.z; 24 | std::cout << "} -> {" << direction.x << " " << direction.y << " " << direction.z; 25 | std::cout << "}" << std::endl; 26 | } 27 | -------------------------------------------------------------------------------- /src/Texture.hpp: -------------------------------------------------------------------------------- 1 | // Texture class 2 | // Holds all data regarding textures 3 | 4 | #pragma once 5 | #ifndef _TEXTURE_H_ 6 | #define _TEXTURE_H_ 7 | 8 | #include 9 | #include 10 | 11 | class Texture { 12 | public: 13 | enum Type { 14 | ColorMap, 15 | NormalMap, 16 | BumpMap 17 | }; 18 | 19 | 20 | // Take in a file name and create a new texture 21 | Texture(); 22 | Texture(std::string, Type); 23 | 24 | std::string name; 25 | Type type; 26 | int width; 27 | int height; 28 | int components; 29 | unsigned char *data; 30 | 31 | void init(); 32 | glm::vec3 getColor(glm::vec2); 33 | void print(); 34 | 35 | private: 36 | glm::vec3 getPixelColor(glm::vec2); 37 | }; 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/Camera.cpp: -------------------------------------------------------------------------------- 1 | #include "Camera.hpp" 2 | 3 | Camera::Camera() { 4 | this->location = glm::vec3(0, 0, 0); 5 | this->up = glm::vec3(0, 0, 0); 6 | this->right = glm::vec3(0, 0, 0); 7 | this->lookAt = glm::vec3(0, 0, 0); 8 | } 9 | 10 | void Camera::print() { 11 | std::cout << "Camera:" << std::endl; 12 | std::cout << "- Location: {"; 13 | std::cout << location.x << " " << location.y << " " << location.z; 14 | std::cout << "}" << std::endl; 15 | std::cout << "- Up: {"; 16 | std::cout << up.x << " " << up.y << " " << up.z; 17 | std::cout << "}" << std::endl; 18 | std::cout << "- Right: {"; 19 | std::cout << right.x << " " << right.y << " " << right.z; 20 | std::cout << "}" << std::endl; 21 | std::cout << "- Look at: {"; 22 | std::cout << lookAt.x << " " << lookAt.y << " " << lookAt.z; 23 | std::cout << "}" << std::endl; 24 | } 25 | -------------------------------------------------------------------------------- /src/TextureBatch.cpp: -------------------------------------------------------------------------------- 1 | #include "TextureBatch.hpp" 2 | #include // cout 3 | 4 | Texture* TextureBatch::addTexture(std::string name, Texture::Type type) { 5 | Texture *texture = new Texture(name, type); 6 | if (!texture->data) { 7 | std::cout << "Failed to create: " << name << std::endl; 8 | return nullptr; 9 | } 10 | 11 | textures.push_back(texture); 12 | 13 | std::cout << "Created texture: "; 14 | texture->print(); 15 | 16 | return texture; 17 | } 18 | 19 | Texture* TextureBatch::getTexture(std::string name, Texture::Type type) { 20 | for (Texture *t : textures) { 21 | if (!name.compare(t->name) && t->type == type) { 22 | return t; 23 | } 24 | } 25 | 26 | return addTexture(name, type); 27 | } 28 | 29 | void TextureBatch::print() { 30 | std::cout << "Textures: " << textures.size() << std::endl; 31 | for (Texture *t : textures) { 32 | std::cout << t->name << std::endl; 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /myplanes.pov: -------------------------------------------------------------------------------- 1 | camera { 2 | location <0, 0, 20> 3 | up <0, 1, 0> 4 | right <1.33333, 0, 0> 5 | look_at <0, 0, 0> 6 | } 7 | 8 | 9 | light_source {<-1, 100, 1> color rgb <3.5, 3.5, 3.5>} 10 | 11 | sphere { <0, 0, 3>, 3 12 | pigment { color rgbf <1.0, 1.0, 1.0, 0.95> } 13 | finish { ambient 0.02 diffuse 0.4 specular 10.0 roughness 0.03 refraction 1.0 ior 1.02 } 14 | scale <1.0, 1.5, 1.0> 15 | } 16 | 17 | sphere { <1, 0, -5>, 3 18 | pigment { color rgbf <1.0, 1.0, 1.0, 0.95>} 19 | finish { ambient 0.02 diffuse 0.4 specular 10.0 roughness 0.03 refraction 1.0 ior 1.15 } 20 | scale <2.5, 1, 1> 21 | } 22 | 23 | sphere { <2, 5, -10>, 3 24 | pigment { color rgb <1.0, 0.0, 0.0>} 25 | finish { ambient 0.02 diffuse 0.4 specular 10.0 reflection 0.1} 26 | } 27 | 28 | 29 | plane {<0, 1, 0>, -8 30 | pigment { color rgb <0.4, 0.4, 0.4> } 31 | finish { ambient 0.4 diffuse 0.8 reflection 0.8} 32 | } 33 | 34 | plane {<1, 0, 0>, -15 35 | pigment { color rgb <0.0, 1.0, 0.0> } 36 | finish {ambient 0.4 diffuse 0.8 reflection 0.6} 37 | } 38 | 39 | plane {<-1, 0, 0>, -15 40 | pigment { color rgb <0, 0, 1.0> } 41 | finish {ambient 0.4 diffuse 0.8 reflection 0.6} 42 | } 43 | 44 | plane {<0, 0, 1>, -15 45 | pigment { color rgb <1.0 0.0 1.0> } 46 | finish {ambient 0.4 diffuse 0.8 reflection 0.6} 47 | } 48 | -------------------------------------------------------------------------------- /src/Intersection.hpp: -------------------------------------------------------------------------------- 1 | // Intersection class 2 | // Includes a ray, an intersected object, 3 | // the T value for the ray, and the intersection point 4 | // in world coords 5 | #pragma once 6 | #ifndef _INTERSECTION_H_ 7 | #define _INTERSECTION_H_ 8 | 9 | #include // vector 10 | 11 | #include "GeoObject.hpp" 12 | #include "Scene.hpp" 13 | 14 | class Intersection { 15 | public: 16 | Intersection() {}; 17 | Intersection(Scene &, Ray &, int); 18 | 19 | // Create intersection w object intersection 20 | void createIntersection(GeoObject *, Ray &); 21 | 22 | void print(); 23 | 24 | // True if object is intersected, false otherwise 25 | bool hit; 26 | 27 | // Object intersected and normal at point of intersection 28 | // Set to nullptr if no object is intersected 29 | GeoObject *object = nullptr; 30 | glm::vec3 normal; 31 | 32 | // Ray of intersection in object and world space 33 | Ray ray; 34 | Ray objectRay; 35 | 36 | // t value of intersection and intersection point in object and world space 37 | float t; 38 | glm::vec3 point; 39 | glm::vec3 objectPoint; 40 | 41 | // Color of object at a given intersection point 42 | // Pulled from object pigment or from texture 43 | glm::vec3 pigment; 44 | 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_LEGACY_CYGWIN_WIN32 0) # Remove when CMake >= 2.8.4 is required 2 | 3 | cmake_minimum_required(VERSION 2.8) 4 | 5 | # Name of the project 6 | project(raytrace) 7 | 8 | # Use glob to get the list of all source files. 9 | file(GLOB_RECURSE SOURCES "src/*.cpp") 10 | 11 | # We don't really need to include header and resource files to build, but it's nice to have them show up in IDEs. 12 | file(GLOB_RECURSE HEADERS "src/*.h") 13 | file(GLOB_RECURSE HEADERS "src/*.hpp") 14 | 15 | # Set the executable. 16 | add_executable(${CMAKE_PROJECT_NAME} ${SOURCES} ${HEADERS}) 17 | 18 | set(GLM_INCLUDE_DIR "ext/glm") 19 | 20 | # OS specific options and libraries 21 | if(WIN32) 22 | # c++0x is enabled by default. 23 | # -Wall produces way too many warnings. 24 | # -pedantic is not supported. 25 | # Disable warning 4996. 26 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4996") 27 | else() 28 | # Enable all pedantic warnings. 29 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -std=c++0x -lpthread -Wall -pedantic") 30 | endif() 31 | 32 | if(NOT GLM_INCLUDE_DIR) 33 | MESSAGE(FATAL_ERROR "Please point the environment variable GLM_INCLUDE_DIR to the include directory of your GLM installation.") 34 | else() 35 | MESSAGE("GLM dir: " ${GLM_INCLUDE_DIR}) 36 | endif() 37 | include_directories(${GLM_INCLUDE_DIR}) 38 | 39 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 40 | -------------------------------------------------------------------------------- /src/Plane.cpp: -------------------------------------------------------------------------------- 1 | #include "Plane.hpp" 2 | 3 | Plane::Plane() : GeoObject() { 4 | this->type = "Plane"; 5 | 6 | this->normal = glm::vec3(0, 0, 0); 7 | this->distance = 0; 8 | } 9 | 10 | glm::vec3 Plane::findNormal(glm::vec3 point) { 11 | return normal; 12 | } 13 | 14 | glm::vec3 Plane::findCenter() { 15 | // No such center for plane 16 | return glm::vec3(0, 0, 0); 17 | } 18 | 19 | float Plane::intersect(const Ray &ray) { 20 | float den = dot(ray.direction, normal); 21 | if (!den) { 22 | return -1; 23 | } 24 | float num = distance-dot(ray.position, normal); 25 | return num/den; 26 | } 27 | 28 | BoundingBox* Plane::createBox() { 29 | // Planes will never need a bounding box 30 | return new BoundingBox; 31 | } 32 | 33 | glm::vec2 Plane::getUVCoords(glm::vec3 point) { 34 | glm::vec3 p = glm::vec3(point.x - std::floor(point.x), point.y - std::floor(point.y), point.z - std::floor(point.z)); 35 | p /= 5.f; 36 | glm::vec2 ret; 37 | 38 | /* TODO: There must be a better way to do this */ 39 | if (normal.x) { 40 | ret = glm::vec2(p.y, p.z); 41 | } 42 | else if (normal.y) { 43 | ret = glm::vec2(p.x, p.z); 44 | } 45 | else { 46 | ret = glm::vec2(p.x, p.y); 47 | } 48 | 49 | return ret; 50 | } 51 | 52 | void Plane::print() { 53 | std::cout << "- Type: Plane" << std::endl; 54 | std::cout << "- Normal: {"; 55 | std::cout << normal.x << " " << normal.y << " " << normal.z; 56 | std::cout << "}" << std::endl; 57 | std::cout << "- Distance: "; 58 | std::cout << distance << std::endl; 59 | GeoPrint(); 60 | } 61 | -------------------------------------------------------------------------------- /src/Renderer.hpp: -------------------------------------------------------------------------------- 1 | // Renderer class 2 | // Takes in a Scene and renders it 3 | 4 | #pragma once 5 | #ifndef _RENDERER_H_ 6 | #define _RENDERER_H_ 7 | 8 | #include // cout 9 | #include 10 | 11 | #include "Scene.hpp" 12 | #include "BRDF.hpp" 13 | 14 | #define OUTPUT_NAME "output.png" 15 | 16 | #define RECURSE_COUNT 6 17 | 18 | class Renderer { 19 | public: 20 | // TODO : Move thread stuff elsewhere 21 | struct thread_data{ 22 | Scene *scene; 23 | glm::ivec2 size; 24 | int numChannels; 25 | unsigned char *data; 26 | int index; 27 | float *count; 28 | int startX; 29 | int endX; 30 | }; 31 | 32 | // Output file name 33 | std::string fileName = OUTPUT_NAME; 34 | 35 | // BRDF object for recursive raytracing 36 | BRDF brdf; 37 | 38 | // 0 - don't print 39 | // 1 - print 40 | int percent_flag = 0; 41 | 42 | // Super-sample count 43 | int SSCount = 1; 44 | void setSSCount(const int in) { this->SSCount = in; } 45 | 46 | // Global Illumination values 47 | void setGIFlag(int flag) { brdf.gi_flag = flag; } 48 | void setGISamples(int n) { brdf.gi_samples = n; } 49 | void setGIBounces(int n) { brdf.gi_bounces = n; } 50 | void setGIRatio(int n) { brdf.gi_ratio = n; } 51 | 52 | void render(Scene &, const int, const int, const int); 53 | void threadRender(thread_data *); 54 | glm::vec3 calculateColor(Scene &, const glm::ivec2, const int, const int); 55 | 56 | void print(); 57 | void pixeltrace(int); 58 | void printrays(int); 59 | 60 | void setVerbose(int flag) { brdf.verbose_flag = flag; } 61 | void setFresnelFlag(int flag) { brdf.fresnel_flag = flag; } 62 | void setSpatialFlag(int flag) { brdf.spatial_flag = flag; } 63 | void setPercentFlag(int flag) { percent_flag = flag; } 64 | void setOutputName(char *name) { fileName = std::string(name); } 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/BRDF.hpp: -------------------------------------------------------------------------------- 1 | // Recursive BRDF class 2 | // Recursive reflection/refraction ray calculation and color calculations 3 | #pragma once 4 | #ifndef _BRDF_H_ 5 | #define _BRDF_H_ 6 | 7 | #include 8 | 9 | #include "Intersection.hpp" 10 | #include "Scene.hpp" 11 | 12 | #define PI 3.14159265359f 13 | 14 | #define DEFAULT_SAMPLES 128 15 | #define DEFAULT_BOUNCES 2 16 | #define DEFAULT_RATIO 8 17 | 18 | class BRDF { 19 | public: 20 | // 0 - No ray printing 21 | // 1 - Ray printing 22 | int verbose_flag; 23 | 24 | // 0 - no fresnel calculations 25 | // 1 - fresnel calculations 26 | int fresnel_flag; 27 | 28 | // 0 - iterate through each object to find intersection 29 | // 1 - use spatial data structure tree 30 | int spatial_flag; 31 | 32 | // 0 - default ambient calculation 33 | // 1 - global illumination ambient calculation 34 | int gi_flag; 35 | int gi_samples; 36 | int gi_bounces; 37 | int gi_ratio; 38 | 39 | BRDF() { 40 | verbose_flag = 0; 41 | fresnel_flag = 0; 42 | spatial_flag = 0; 43 | gi_flag = 0; 44 | gi_samples = DEFAULT_SAMPLES; 45 | gi_bounces = DEFAULT_BOUNCES; 46 | gi_ratio = DEFAULT_RATIO; 47 | }; 48 | 49 | // Master recursive function for tracing rays 50 | glm::vec3 raytrace(Scene &, Ray &, int); 51 | glm::vec3 calculateColor(Scene &, Intersection &, int); 52 | 53 | // Color calculation functions 54 | float calculateFresnelReflectance(float, Intersection &); 55 | float fresnel(float, glm::vec3, glm::vec3); 56 | glm::vec3 calculateLocalColor(Scene &, Intersection &, int); 57 | glm::vec3 calculateGlobalIllumination(Scene &, Intersection &, int); 58 | glm::vec3 calculateReflectionColor(Scene &, Intersection &, int); 59 | glm::vec3 calculateRefractionColor(Scene &, Intersection &, int); 60 | 61 | // Shading calculations 62 | glm::vec3 BlinnPhong(Light *, Intersection &); 63 | }; 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/Box.cpp: -------------------------------------------------------------------------------- 1 | #include "Box.hpp" 2 | #include 3 | 4 | Box::Box(){ 5 | float inf = std::numeric_limits::max(); 6 | updateBox(glm::vec3(inf, inf, inf), glm::vec3(inf, inf, inf)); 7 | } 8 | 9 | Box::Box(glm::vec3 min, glm::vec3 max) { 10 | updateBox(min, max); 11 | } 12 | 13 | void Box::updateBox(glm::vec3 min, glm::vec3 max) { 14 | this->minCorner = min; 15 | this->maxCorner = max; 16 | this->center = (min + max) / 2.f; 17 | } 18 | 19 | float Box::intersect(const Ray &ray) { 20 | // Check for uninitialized box 21 | if (!this->hasBeenInit()) { 22 | return -1; 23 | } 24 | 25 | float tgmax = std::numeric_limits::max(); 26 | float tgmin = -tgmax; 27 | 28 | for (int axis = 0; axis <= 2; axis++) { 29 | // If ray is parallel 30 | if (!ray.direction[axis] && (ray.position[axis] >= this->minCorner[axis] || ray.position[axis] < this->maxCorner[axis])) { 31 | return -1; 32 | } 33 | // Otherwise update tgmin and tgmax 34 | updateT(ray, &tgmin, &tgmax, axis); 35 | } 36 | 37 | if (tgmin > tgmax || tgmax < 0) { 38 | return -1; 39 | } 40 | 41 | if (tgmin > 0) { 42 | return tgmin; 43 | } 44 | 45 | return tgmax; 46 | } 47 | 48 | void Box::updateT(const Ray &ray, float *tgmin, float *tgmax, int axis) { 49 | float t1 = (this->minCorner[axis] - ray.position[axis]) / ray.direction[axis]; 50 | float t2 = (this->maxCorner[axis] - ray.position[axis]) / ray.direction[axis]; 51 | if (t1 > t2) { 52 | std::swap(t1, t2); 53 | } 54 | if (t1 > *tgmin) { 55 | *tgmin = t1; 56 | } 57 | if (t2 < *tgmax) { 58 | *tgmax = t2; 59 | } 60 | } 61 | 62 | bool Box::hasBeenInit() { 63 | return this->minCorner[0] < std::numeric_limits::max(); 64 | } 65 | 66 | void Box::print() { 67 | std::cout << "Min Corner: {" << minCorner.x << " " << minCorner.y << " " << minCorner.z << "}" << std::endl; 68 | std::cout << "Max Corner: {" << maxCorner.x << " " << maxCorner.y << " " << maxCorner.z << "}" << std::endl; 69 | } 70 | -------------------------------------------------------------------------------- /src/BoxRenderable.cpp: -------------------------------------------------------------------------------- 1 | #include "BoxRenderable.hpp" 2 | #include "glm/gtc/epsilon.hpp" // Epsilon equals 3 | 4 | BoxRenderable::BoxRenderable() : GeoObject(), Box() { 5 | this->type = "Box"; 6 | } 7 | 8 | void BoxRenderable::updateBox(glm::vec3 min, glm::vec3 max) { 9 | Box::updateBox(min, max); 10 | } 11 | 12 | BoundingBox* BoxRenderable::createBox() { 13 | BoundingBox *box = new BoundingBox(this->minCorner, this->maxCorner); 14 | box->transform(this->M); 15 | return box; 16 | } 17 | 18 | float BoxRenderable::intersect(const Ray &ray) { 19 | return Box::intersect(ray); 20 | } 21 | 22 | glm::vec3 BoxRenderable::findCenter() { 23 | return this->center; 24 | } 25 | 26 | static inline bool Equals(const float a, const float b, const float epsilon = 0.0001f) { 27 | return std::abs(a - b) < epsilon; 28 | } 29 | 30 | glm::vec3 BoxRenderable::findNormal(glm::vec3 point) { 31 | // Check for uninitialized box 32 | if (!this->hasBeenInit()) { 33 | return glm::vec3(0, 0, 0); 34 | } 35 | 36 | glm::vec3 normal = glm::vec3(0, 0, 0); 37 | // X 38 | if (Equals(point.x, this->minCorner.x)) { 39 | normal = glm::vec3(-1, 0, 0); 40 | } 41 | else if (Equals(point.x, this->maxCorner.x)) { 42 | normal = glm::vec3(1, 0, 0); 43 | } 44 | // Y 45 | else if (Equals(point.y, this->minCorner.y)) { 46 | normal = glm::vec3(0, -1, 0); 47 | } 48 | else if (Equals(point.y, this->maxCorner.y)) { 49 | normal = glm::vec3(0, 1, 0); 50 | } 51 | // Z 52 | else if (Equals(point.z, this->minCorner.z)) { 53 | normal = glm::vec3(0, 0, -1); 54 | } 55 | else if (Equals(point.z, this->maxCorner.z)) { 56 | normal = glm::vec3(0, 0, 1); 57 | } 58 | 59 | return normal; 60 | } 61 | 62 | glm::vec2 BoxRenderable::getUVCoords(glm::vec3 point) { 63 | /* TODO */ 64 | return glm::vec2(0, 0); 65 | } 66 | 67 | void BoxRenderable::print() { 68 | Box::print(); 69 | } 70 | -------------------------------------------------------------------------------- /src/Sphere.cpp: -------------------------------------------------------------------------------- 1 | #include "Sphere.hpp" 2 | #include 3 | #include 4 | 5 | #define PI 3.14159265359 6 | 7 | Sphere::Sphere() : GeoObject() { 8 | this->type = "Sphere"; 9 | 10 | this->center = glm::vec3(0, 0, 0); 11 | this->radius = 0; 12 | } 13 | 14 | glm::vec3 Sphere::findNormal(glm::vec3 point) { 15 | return glm::normalize(point - center); 16 | } 17 | 18 | glm::vec3 Sphere::findCenter() { 19 | return center; 20 | } 21 | 22 | float Sphere::intersect(const Ray &ray) { 23 | glm::vec3 pc = ray.position - center; 24 | 25 | const float A = dot(ray.direction, ray.direction); 26 | const float B = dot(ray.direction + ray.direction, pc); 27 | const float C = dot(pc, pc) - radius*radius; 28 | float det = B*B-4*A*C; 29 | 30 | if (det < 0 || !A) { 31 | return -1; 32 | } 33 | 34 | det = sqrt(det); 35 | float t1 = (-B+det)/(2*A); 36 | float t2 = (-B-det)/(2*A); 37 | 38 | if (t1 > 0 && t2 > 0) { 39 | return std::min(t1, t2); 40 | } 41 | if (t1 > 0) { 42 | return t1; 43 | } 44 | if (t2 > 0) { 45 | return t2; 46 | } 47 | return -1; 48 | } 49 | 50 | BoundingBox* Sphere::createBox() { 51 | glm::vec3 min = this->center; 52 | glm::vec3 max = this->center; 53 | for (int axis = 0; axis <= 2; axis++) { 54 | min[axis] -= radius; 55 | max[axis] += radius; 56 | } 57 | BoundingBox* box = new BoundingBox(min, max); 58 | box->transform(this->M); 59 | return box; 60 | } 61 | 62 | glm::vec2 Sphere::getUVCoords(glm::vec3 point) { 63 | glm::vec3 N = findNormal(point); 64 | 65 | float u = 0.5f + std::atan2(N.z, N.x) / (2.f * PI); 66 | float v = 0.5f + glm::asin(N.y) / PI; 67 | 68 | return glm::vec2(glm::clamp(u, 0.f, 1.f), glm::clamp(v, 0.f, 1.f)); 69 | } 70 | 71 | void Sphere::print() { 72 | std::cout << "- Type: Sphere" << std::endl; 73 | std::cout << "- Center: {"; 74 | std::cout << center.x << " " << center.y << " " << center.z; 75 | std::cout << "}" << std::endl; 76 | std::cout << "- Radius: "; 77 | std::cout << radius << std::endl; 78 | GeoPrint(); 79 | } 80 | -------------------------------------------------------------------------------- /src/Scene.hpp: -------------------------------------------------------------------------------- 1 | // Scene class 2 | // Contains Camera, all Lights, and all Objects in the scene 3 | 4 | #pragma once 5 | #ifndef _SCENE_H_ 6 | #define _SCENE_H_ 7 | 8 | #include // cout 9 | #include // vector 10 | 11 | #include "BoundingBox.hpp" 12 | 13 | #include "Camera.hpp" 14 | #include "Light.hpp" 15 | 16 | #include "GeoObject.hpp" 17 | #include "Sphere.hpp" 18 | #include "Plane.hpp" 19 | #include "Triangle.hpp" 20 | #include "BoxRenderable.hpp" 21 | 22 | class Intersection; // Forward declaration 23 | 24 | class Scene { 25 | public: 26 | // Spatial data structure node 27 | struct BoxNode { 28 | BoundingBox boundingBox; 29 | std::vector objects; 30 | 31 | BoxNode* leftChild = nullptr; 32 | BoxNode* rightChild = nullptr; 33 | }; 34 | 35 | // Constructor 36 | Scene(); 37 | 38 | // Scene objects 39 | Camera *camera; 40 | std::vector lights; 41 | // If spatial flag is not set, scene.objects contains all objects in the scene 42 | // If spatial flag is set, then scene.objects contains all planes and box tree contains all other objects 43 | std::vector objects; 44 | 45 | // Spatial Data Structures 46 | BoxNode* rootBox; 47 | void createNodeBoundingBox(BoxNode *); 48 | void createSpatialStructures(std::vector, BoxNode*, int); // Recursively build spatial structure tree 49 | void sortObjects(std::vector, int); 50 | GeoObject* boxTraversal(BoxNode *, const Ray &); 51 | 52 | // Ray creation 53 | Ray createCameraRay(const int width, const int height, const int x, const int y, const int m, const int n, const int s); 54 | Ray createReflectionRay(const Intersection &); 55 | Ray createRefractionRay(const Intersection &); 56 | 57 | // Used for creating global illumination sample rays 58 | glm::vec3 createSamplePoint(Intersection &, glm::mat4 &, float, float); 59 | 60 | // Calculates the color in the world at an Intersection 61 | glm::vec3 findColor(const glm::ivec2, const int, const int, const int, int); 62 | 63 | void print(); 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/Loader.hpp: -------------------------------------------------------------------------------- 1 | // Loader class 2 | // Parses .pov files and creates the various Camera, Light, and GeoObjects objects 3 | #pragma once 4 | #ifndef _LOADER_H_ 5 | #define _LOADER_H_ 6 | 7 | #include // read file input 8 | #include // sstream 9 | #include // strtof 10 | #include // vector 11 | #include // find 12 | 13 | #include "Scene.hpp" 14 | #include "TextureBatch.hpp" 15 | 16 | class Loader { 17 | public: 18 | Loader(){}; 19 | TextureBatch batch; 20 | // Walk through an input file creating a Scene object 21 | int parse(const char *file_name, Scene &scene); 22 | 23 | private: 24 | // Parse through remaining object adding object properties 25 | void addProperties(GeoObject *, std::vector, std::ifstream&); 26 | 27 | // Find all floats inside a line 28 | std::vector findFloatsInLine(std::vector); 29 | 30 | // Find all floats inside a word 31 | std::vector findFloatsInWord(std::string word); 32 | 33 | // Adds color param to a finish object 34 | void createColor(GeoObject::Finish*, std::vector); 35 | 36 | // Initializes Finish struct containing all GeoObject finish properties 37 | void createFinish(GeoObject::Finish*, std::vector); 38 | 39 | // Initializes Textures struct in Object as well as individual textures themselves 40 | void createTextures(GeoObject::Textures*, std::vector); 41 | 42 | // Break up the current line in a file into a vector 43 | // Separating by white space 44 | std::vector getLine(std::ifstream *file); 45 | std::vector cleanseLine(std::vector); 46 | 47 | // Create a Camera object given a file pointing to a camera line 48 | Camera* createCamera(std::vector, std::ifstream &file); 49 | 50 | // Create a Light object given a file pointing to a light line 51 | Light* createLight(std::vector, std::ifstream &file); 52 | 53 | // Create geometric objects given a file pointing to a object line 54 | Sphere* createSphere(int, std::vector, std::ifstream &file); 55 | Plane* createPlane(int, std::vector, std::ifstream &file); 56 | Triangle* createTriangle(int, std::vector, std::ifstream &file); 57 | BoxRenderable* createBox(int, std::vector, std::ifstream &file); 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/Texture.cpp: -------------------------------------------------------------------------------- 1 | #include "Texture.hpp" 2 | 3 | #define STB_IMAGE_IMPLEMENTATION 4 | #include "stb_image.h" 5 | 6 | #include 7 | 8 | Texture::Texture() { 9 | name = ""; 10 | width = height = 0; 11 | } 12 | 13 | Texture::Texture(std::string name, Type type) { 14 | this->name = name; 15 | this->type = type; 16 | init(); 17 | } 18 | 19 | void Texture::init() { 20 | if (strcmp(name.c_str() + name.size() - 3, "bmp") ) { 21 | std::cerr << name << " is not a .bmp file" << std::endl; 22 | return; 23 | } 24 | 25 | stbi_set_flip_vertically_on_load(true); 26 | data = stbi_load(name.c_str(), &width, &height, &components, 0); 27 | 28 | 29 | // Ensure valid data 30 | int errorFlag = 0; 31 | if (!data) { 32 | std::cerr << name << " not found" << std::endl; 33 | errorFlag = 1; 34 | } 35 | else if (components != 3) { 36 | std::cerr << name << " is not RGB" << std::endl; 37 | errorFlag = 1; 38 | } 39 | else if ((width & (width-1)) != 0 || (height & (height-1)) != 0) { 40 | std::cerr << name << " must be a power of 2" << std::endl; 41 | errorFlag = 1; 42 | } 43 | 44 | if (errorFlag) { 45 | width = height = components = 0; 46 | data = nullptr; 47 | } 48 | } 49 | 50 | glm::vec3 Texture::getColor(glm::vec2 uv_point) { 51 | // UV->ST 52 | glm::vec2 st_point = glm::vec2(std::floor(uv_point.x * width), std::floor(uv_point.y * height)); 53 | 54 | // Bilinear interpolation 55 | glm::vec2 uvp = glm::vec2(uv_point.x * width - st_point.x, uv_point.y * height - st_point.y); 56 | return (1-uvp.x)*(1-uvp.y)*getPixelColor(st_point) + uvp.x*(1-uvp.y)* 57 | getPixelColor(glm::vec2(st_point.x+1, st_point.y)) + (1-uvp.x)* 58 | uvp.y*getPixelColor(glm::vec2(st_point.x, st_point.y+1)) + 59 | uvp.x*uvp.y*getPixelColor(glm::vec2(st_point.x+1, st_point.y+1)); 60 | } 61 | 62 | glm::vec3 Texture::getPixelColor(glm::vec2 st_point) { 63 | int index = st_point.y * (width) * components + st_point.x * components; 64 | return glm::vec3(data[index]/255.f, data[index+1]/255.f, data[index+2]/255.f); 65 | } 66 | 67 | void Texture::print() { 68 | switch(type) { 69 | case ColorMap: 70 | std::cout << "Color Map: "; 71 | break; 72 | case NormalMap: 73 | std::cout << "Normal Map: "; 74 | break; 75 | case BumpMap: 76 | std::cout << "Bump Map: "; 77 | break; 78 | default: 79 | std::cout << "No type: "; 80 | break; 81 | } 82 | std::cout << name << ": " << width << "x" << height << "x" << components << std::endl; 83 | } 84 | -------------------------------------------------------------------------------- /src/Intersection.cpp: -------------------------------------------------------------------------------- 1 | #include "Intersection.hpp" 2 | 3 | const static float EPSILON = 0.0001f; 4 | 5 | Intersection::Intersection(Scene &scene, Ray& ray, int spatial_flag) { 6 | this->ray = ray; 7 | this->hit = false; 8 | this->t = std::numeric_limits::max(); 9 | 10 | for (unsigned int i = 0; i < scene.objects.size(); i++) { 11 | createIntersection(scene.objects[i], ray); 12 | } 13 | if (spatial_flag) { 14 | GeoObject* object = scene.boxTraversal(scene.rootBox, ray); 15 | if (object != nullptr) { 16 | createIntersection(object, ray); 17 | } 18 | } 19 | } 20 | 21 | void Intersection::createIntersection(GeoObject *object, Ray &ray) { 22 | // Transform ray into object's object space 23 | glm::vec3 p = glm::vec3(object->inv_M * glm::vec4(ray.position, 1.0f)); 24 | glm::vec3 d = glm::vec3(object->inv_M * glm::vec4(ray.direction, 0.0f)); 25 | Ray objectRay(p, d); 26 | 27 | float obj_t = object->intersect(objectRay); 28 | if (obj_t > EPSILON && obj_t < this->t) { 29 | this->hit = true; 30 | this->t = obj_t; 31 | this->object = object; 32 | this->objectRay = objectRay; 33 | this->point = this->ray.calculatePoint(this->t); 34 | this->objectPoint = this->objectRay.calculatePoint(this->t); 35 | 36 | // Coordinate transform normal 37 | glm::vec3 obj_normal = this->object->findNormal(objectPoint); 38 | glm::vec3 world_normal = glm::vec3(glm::transpose(this->object->inv_M) * glm::vec4(obj_normal, 0.0f)); 39 | 40 | // Pull normal from normal map or from calcualtion 41 | if (object->textures.normalMap != nullptr) { 42 | glm::vec3 t = glm::cross(world_normal, glm::vec3(0, 1, 0)); 43 | if (!t.length()) { 44 | t = glm::cross(world_normal, glm::vec3(0, 0, 1)); 45 | } 46 | t = glm::normalize(t); 47 | glm::vec3 b = glm::normalize(glm::cross(world_normal, t)); 48 | glm::vec3 m = this->object->textures.normalMap->getColor(this->object->getUVCoords(objectPoint)); 49 | m = 2.f*m - glm::vec3(1, 1, 1); 50 | glm::mat3 mat = glm::mat3(t, b, world_normal); 51 | world_normal = glm::normalize(mat * m); 52 | } 53 | this->normal = glm::normalize(world_normal); 54 | } 55 | } 56 | 57 | void Intersection::print() { 58 | if (!hit) { 59 | std::cout << "No Hit" << std::endl; 60 | return; 61 | } 62 | 63 | std::cout << "T = " << t << std::endl; 64 | std::cout << "Object Type: " << object->type << std::endl; 65 | } 66 | -------------------------------------------------------------------------------- /src/Triangle.cpp: -------------------------------------------------------------------------------- 1 | #include "Triangle.hpp" 2 | 3 | Triangle::Triangle() : GeoObject() { 4 | this->type = "Triangle"; 5 | 6 | this->v1 = glm::vec3(0, 0, 0); 7 | this->v2 = glm::vec3(0, 0, 0); 8 | this->v3 = glm::vec3(0, 0, 0); 9 | } 10 | 11 | BoundingBox* Triangle::createBox() { 12 | BoundingBox* box = new BoundingBox; 13 | box->addPoint(v1); 14 | box->addPoint(v2); 15 | box->addPoint(v3); 16 | box->transform(this->M); 17 | return box; 18 | } 19 | 20 | glm::vec3 Triangle::findCenter() { 21 | return (this->v1 + this->v2 + this->v3) / 3.f; 22 | } 23 | 24 | float Triangle::intersect(const Ray &ray) { 25 | 26 | // Alpha ? 27 | glm::mat3 A = glm::mat3(v1.x-v2.x, v1.x-v3.x, ray.direction.x, 28 | v1.y-v2.y, v1.y-v3.y, ray.direction.y, 29 | v1.z-v2.z, v1.z-v3.z, ray.direction.z) ; 30 | float detA = glm::determinant(A); 31 | 32 | // Beta 33 | glm::mat3 B = glm::mat3(v1.x-ray.position.x, v1.x-v3.x, ray.direction.x, 34 | v1.y-ray.position.y, v1.y-v3.y, ray.direction.y, 35 | v1.z-ray.position.z, v1.z-v3.z, ray.direction.z); 36 | float beta = glm::determinant(B)/detA; 37 | if (beta < 0 || beta > 1) { 38 | return -1; 39 | } 40 | 41 | // Gamma 42 | glm::mat3 G = glm::mat3(v1.x-v2.x, v1.x-ray.position.x, ray.direction.x, 43 | v1.y-v2.y, v1.y-ray.position.y, ray.direction.y, 44 | v1.z-v2.z, v1.z-ray.position.z, ray.direction.z); 45 | float gamma = glm::determinant(G)/detA; 46 | if (gamma < 0 || gamma > 1 - beta) { 47 | return -1; 48 | } 49 | 50 | // t 51 | glm::mat3 T = glm::mat3(v1.x-v2.x, v1.x-v3.x, v1.x-ray.position.x, 52 | v1.y-v2.y, v1.y-v3.y, v1.y-ray.position.y, 53 | v1.z-v2.z, v1.z-v3.z, v1.z-ray.position.z); 54 | return glm::determinant(T)/detA; 55 | } 56 | 57 | glm::vec3 Triangle::findNormal(glm::vec3 point) { 58 | glm::vec3 V = v2 - v1; 59 | glm::vec3 W = v3 - v1; 60 | 61 | return normalize(glm::vec3(V.y*W.z-V.z*W.y, V.z*W.x-V.x*W.z, V.x*W.y-V.y*W.x)); 62 | } 63 | 64 | glm::vec2 Triangle::getUVCoords(glm::vec3 point) { 65 | /* TODO */ 66 | return glm::vec2(0, 0); 67 | } 68 | 69 | void Triangle::print() { 70 | std::cout << "- Type: Triangle" << std::endl; 71 | std::cout << "- Corner 1: {"; 72 | std::cout << v1.x << " " << v1.y << " " << v1.z; 73 | std::cout << "}" << std::endl; 74 | std::cout << "- Corner 2: {"; 75 | std::cout << v2.x << " " << v2.y << " " << v2.z; 76 | std::cout << "}" << std::endl; 77 | std::cout << "- Corner 3: {"; 78 | std::cout << v3.x << " " << v3.y << " " << v3.z; 79 | std::cout << "}" << std::endl; 80 | 81 | GeoPrint(); 82 | } 83 | -------------------------------------------------------------------------------- /src/BoundingBox.cpp: -------------------------------------------------------------------------------- 1 | #include "BoundingBox.hpp" 2 | #include 3 | 4 | BoundingBox::BoundingBox() { 5 | Box(); 6 | } 7 | 8 | BoundingBox::BoundingBox(glm::vec3 min, glm::vec3 max) { 9 | updateBox(min, max); 10 | } 11 | 12 | // Bounding box up to this point has been initialized as objects object space 13 | void BoundingBox::transform(glm::mat4 &M) { 14 | // Catch for uninitialized box 15 | if (!this->hasBeenInit()) { 16 | return; 17 | } 18 | 19 | // Calculate 8 points 20 | std::vector vertices; 21 | vertices.push_back(glm::vec3(minCorner.x, minCorner.y, minCorner.z)); 22 | vertices.push_back(glm::vec3(minCorner.x, minCorner.y, maxCorner.z)); 23 | vertices.push_back(glm::vec3(minCorner.x, maxCorner.y, minCorner.z)); 24 | vertices.push_back(glm::vec3(minCorner.x, maxCorner.y, maxCorner.z)); 25 | vertices.push_back(glm::vec3(maxCorner.x, minCorner.y, minCorner.z)); 26 | vertices.push_back(glm::vec3(maxCorner.x, minCorner.y, maxCorner.z)); 27 | vertices.push_back(glm::vec3(maxCorner.x, maxCorner.y, minCorner.z)); 28 | vertices.push_back(glm::vec3(maxCorner.x, maxCorner.y, maxCorner.z)); 29 | 30 | float inf = std::numeric_limits::max(); 31 | this->minCorner = glm::vec3(inf, inf, inf); 32 | this->maxCorner = glm::vec3(-inf, -inf, -inf); 33 | 34 | // Transform 8 points 35 | // Update min and max as we go 36 | for (int i = 0; i < 8; i++) { 37 | vertices[i] = glm::vec3(M * glm::vec4(vertices[i], 1.f)); 38 | addPoint(vertices[i]); 39 | } 40 | 41 | updateBox(this->minCorner, this->maxCorner); 42 | } 43 | 44 | 45 | void BoundingBox::addPoint(glm::vec3 point) { 46 | // Catch for uninitialized box 47 | if(!this->hasBeenInit()) { 48 | updateBox(point, point); 49 | return; 50 | } 51 | 52 | // Min 53 | this->minCorner.x = std::min(point.x, std::min(this->minCorner.x, this->maxCorner.x)); 54 | this->minCorner.y = std::min(point.y, std::min(this->minCorner.y, this->maxCorner.y)); 55 | this->minCorner.z = std::min(point.z, std::min(this->minCorner.z, this->maxCorner.z)); 56 | // Max 57 | this->maxCorner.x = std::max(point.x, std::max(this->minCorner.x, this->maxCorner.x)); 58 | this->maxCorner.y = std::max(point.y, std::max(this->minCorner.y, this->maxCorner.y)); 59 | this->maxCorner.z = std::max(point.z, std::max(this->minCorner.z, this->maxCorner.z)); 60 | 61 | updateBox(this->minCorner, this->maxCorner); 62 | } 63 | 64 | void BoundingBox::addBox(BoundingBox *box) { 65 | // If box is uninitialized 66 | if (!this->hasBeenInit()) { 67 | updateBox(box->minCorner, box->maxCorner); 68 | return; 69 | } 70 | 71 | // Min 72 | this->minCorner.x = std::min(this->minCorner.x, std::min(box->maxCorner.x, box->minCorner.x)); 73 | this->minCorner.y = std::min(this->minCorner.y, std::min(box->maxCorner.y, box->minCorner.y)); 74 | this->minCorner.z = std::min(this->minCorner.z, std::min(box->maxCorner.z, box->minCorner.z)); 75 | // Max 76 | this->maxCorner.x = std::max(this->maxCorner.x, std::max(box->maxCorner.x, box->minCorner.x)); 77 | this->maxCorner.y = std::max(this->maxCorner.y, std::max(box->maxCorner.y, box->minCorner.y)); 78 | this->maxCorner.z = std::max(this->maxCorner.z, std::max(box->maxCorner.z, box->minCorner.z)); 79 | 80 | updateBox(this->minCorner, this->maxCorner); 81 | } -------------------------------------------------------------------------------- /src/Renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "Renderer.hpp" 2 | 3 | #include 4 | #include // multiple threads 5 | 6 | #define STB_IMAGE_WRITE_IMPLEMENTATION 7 | #include "stb_image_write.h" 8 | 9 | glm::vec3 Renderer::calculateColor(Scene &scene, const glm::ivec2 size, const int x, const int y) { 10 | // Calculate color 11 | glm::vec3 color = glm::vec3(0, 0, 0); 12 | for (int m = 0; m < SSCount; m++) { 13 | for (int n = 0; n < SSCount; n++) { 14 | Ray camera_ray = scene.createCameraRay(size.x, size.y, x, y, m, n, SSCount); 15 | color += brdf.raytrace(scene, camera_ray, RECURSE_COUNT); 16 | } 17 | } 18 | color /= (SSCount*SSCount); 19 | 20 | // Scale RGB from [0, 1] to [0, 255] 21 | color.r = round(glm::clamp(color.r, 0.f, 1.f) * 255.f); 22 | color.g = round(glm::clamp(color.g, 0.f, 1.f) * 255.f); 23 | color.b = round(glm::clamp(color.b, 0.f, 1.f) * 255.f); 24 | 25 | return color; 26 | } 27 | 28 | void Renderer::threadRender(thread_data *td) { 29 | for (int x = td->startX; x < td->endX; x++) { 30 | // Print percentages 31 | if (percent_flag) { 32 | std::cout << *td->count/(td->size.x)*100 << "%" << std::endl; 33 | } 34 | for (int y = 0; y < td->size.y; y++) { 35 | 36 | // Calculate color 37 | glm::vec3 color = calculateColor(*td->scene, td->size, x, y); 38 | 39 | // Set pixel color 40 | unsigned char red = (unsigned char) color.r; 41 | unsigned char green = (unsigned char) color.g; 42 | unsigned char blue = (unsigned char) color.b; 43 | int pixel = (td->size.x * td->numChannels) * (td->size.y - 1 - y) + td->numChannels * x; 44 | td->data[pixel + 0] = red; 45 | td->data[pixel + 1] = green; 46 | td->data[pixel + 2] = blue; 47 | } 48 | (*td->count)++; 49 | } 50 | } 51 | 52 | void Renderer::render(Scene &scene, const int window_width, const int window_height, const int num_threads) { 53 | const int numChannels = 3; 54 | const glm::ivec2 size = glm::ivec2(window_width, window_height); 55 | std::cout << "Writing to " << fileName.c_str() << "[" << size.x << ", " << size.y << "]" << std::endl; 56 | 57 | unsigned char *data = new unsigned char[size.x * size.y * numChannels]; 58 | 59 | // Create threads 60 | std::vector threads; 61 | std::vector td; 62 | td.resize(num_threads); 63 | 64 | float count = 0; 65 | for (int i = 0; i < num_threads; i++) { 66 | td[i].scene = &scene; 67 | td[i].size = size; 68 | td[i].numChannels = numChannels; 69 | td[i].data = data; 70 | td[i].index = i; 71 | td[i].count = &count; 72 | td[i].startX = (int) std::floor(i * size.x / num_threads); 73 | td[i].endX = (int) std::ceil((i+1) * size.x / num_threads); 74 | 75 | std::cout << "Thread " << i << ": [" << td[i].startX << ", " << td[i].endX << "}" << std::endl; 76 | threads.push_back(std::thread(&Renderer::threadRender, this, &td[i])); 77 | } 78 | for (auto& thread : threads) { 79 | thread.join(); 80 | } 81 | 82 | if (!stbi_write_png(fileName.c_str(), size.x, size.y, numChannels, data, size.x * numChannels)) { 83 | std::cout << "FAILED WRITING IMAGE" << std::endl; 84 | } 85 | delete[] data; 86 | } 87 | 88 | void Renderer::print() { 89 | std::cout << "BRDF: "; 90 | std::cout << "Blinn-Phong"; 91 | std::cout << std::endl; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPU-Ray-Tracer 2 | 3 | ### Project Description 4 | The goal of this project was to create an efficient ray tracer in C++ with many practical features. Implementation features include: 5 | * POV-Ray file parsing 6 | * Blinn-Phong BRDF 7 | * Cook Torrance BRDF 8 | * Refractions and reflections 9 | * Object transformations 10 | * Bounding Volume Hierarchy 11 | * Monte Carlo global illumination 12 | * Texture and Normal mapping 13 | * Bilinear Interpolation 14 | * Multithreading 15 | 16 | # Usage 17 | ## Building 18 | * Install [GLM](https://glm.g-truc.net/) under environment variable `GLM_INCLUDE_DIR` 19 | * Clone project 20 | * In project directory run `mkdir buid; cd build; cmake ..; make` or use CMake GUI to build 21 | 22 | ## Running 23 | Supported povray files can be found in /res/ 24 | 25 | Command line arguments: 26 | * `sceneinfo ` - Prints out povray scene info without rendering 27 | * `render ` - Renders the povray scene using defined width and height 28 | * `-out=` - Specify output image name - default is output.png 29 | * `-fresnel` - Render scene including Fresnel reflectance 30 | * `-ss=N` - Render scene with super sampling with NxN samples 31 | * `-gi` - Render scene using Monte Carlo global illumination 32 | * `-gi_samples=N` - Render scene using at most N bounces for Monte Carlo global illumination 33 | * `-threads=N` - Specify number of threads to be used for rendering 34 | * `-percent` - Print out per-thread percent completion 35 | 36 | # Output 37 | 38 | ### Reflection 39 | ![shiny](output/shiny.png) 40 | 41 | ### Refraction 42 | ![fresnel](output/fresnel1.png) 43 | 44 | ### Object Transformations 45 | ![valentine](output/valentine.png) 46 | 47 | ### Bounding Volume Hierarchy 48 | ![balls](output/balls2.png) 49 | 50 | ### Monte Carlo global illumination and Multithreading 51 | Single-threaded: 52h:34m:58s 52 | 53 | Multi-threaded: 15h:02m:18s 54 | ![gi](output/cornel.png) 55 | 56 | ### Texture mapping 57 | ![texture](output/texture.png) 58 | 59 | ### Normal mapping 60 | ![norm](output/norm.png) 61 | ![norm1](output/norm1.png) 62 | 63 | ### Bilinear Interpolation 64 | ![bi](output/bi.png) 65 | 66 | ### Final Project 67 | For my final project I chose to do texture mapping. Texture mapping is a low-cost, high-reward feature in computer graphics that is conceptually straightforward, relatively simple to implement, and makes our project much more realistic. 68 | 69 | ### Software Design 70 | Adding textures to a ray tracer isn't terribly difficult, but doing it well from a software design standpoint requires some extra functionality. 71 | 72 | The first thing I did was I implemented what I call a TextureBatch. The TextureBatch contains a list of all the unique textures that have been loaded thus far. When my POV-Ray parser finds a texture, it first communicates with the TextureBatch to make sure that texture hasn't already been loaded. If the texture already exists, we can reference the loaded texture rather than loading it again. 73 | 74 | As of now each texture is individualized by only its file name. I would like to implement unique ID's per texture for added security. 75 | 76 | The other thing I had to implement for my final project was different texture *types*. My implementation allows objects to reference color maps and normal maps. Looking ahead I may want to add bump maps or specular maps. I created a system that would allow objects to contain multiple texture types through the use of enums. This design allows me to add many different types of textures in the future. 77 | 78 | ### Research 79 | Realistic Ray Tracing by Peter Shirley and R. Keith Morley 80 | 81 | Ray Tracing Tutorial by The CoderMind Team 82 | 83 | opengameart.org for color and normal map pairs 84 | 85 | Normal Map Online for creating my own normal maps 86 | -------------------------------------------------------------------------------- /src/GeoObject.hpp: -------------------------------------------------------------------------------- 1 | // Abstract geometric object class 2 | // Includes finish struct containing all finish data 3 | // All objets have a finish, a transformation, a type, and an ID 4 | #pragma once 5 | #ifndef _GEOOBJECT_H_ 6 | #define _GEOOBJECT_H_ 7 | 8 | #include 9 | #include // std::cout 10 | #include 11 | 12 | #include "Texture.hpp" 13 | #include "BoundingBox.hpp" 14 | #include "Ray.hpp" 15 | 16 | class GeoObject { 17 | public: 18 | // Finish struct contains all material data 19 | struct Finish { 20 | glm::vec3 color = glm::vec3(0, 0, 0); 21 | 22 | float ambient = 0; 23 | float diffuse = 0; 24 | float specular = 0; 25 | float roughness = 0; 26 | float metallic = 0; 27 | float refraction = 0; 28 | float reflection = 0; 29 | float filter = 0; 30 | float ior = 0; 31 | }; 32 | 33 | // Types of textures 34 | struct Textures { 35 | Texture *colorMap = nullptr; 36 | Texture *normalMap = nullptr; 37 | Texture *bumpMap = nullptr; 38 | }; 39 | 40 | GeoObject() { 41 | inv_M = glm::mat4(1.0f); 42 | }; 43 | 44 | 45 | // Abstract types 46 | int id; 47 | std::string type; 48 | 49 | // Material properties 50 | Finish finish; 51 | Textures textures; 52 | 53 | // Model matrix and its inverse 54 | glm::mat4 M; 55 | glm::mat4 inv_M; 56 | 57 | // Abstract functions 58 | virtual float intersect(const Ray &) = 0; 59 | virtual glm::vec2 getUVCoords(glm::vec3) = 0; 60 | virtual glm::vec3 findNormal(glm::vec3) = 0; 61 | virtual BoundingBox *createBox() = 0; 62 | virtual glm::vec3 findCenter() = 0; 63 | virtual void print() = 0; 64 | 65 | // Parent print functionality 66 | // All objects need to print their finish and transformation 67 | void GeoPrint() { 68 | std::cout << "- Color: {"; 69 | std::cout << finish.color.x << " " << finish.color.y << " " << finish.color.z; 70 | std::cout << "}" << std::endl; 71 | 72 | std::cout << "- Material: " << std::endl; 73 | std::cout << " - Ambient: "; 74 | std::cout << finish.ambient << std::endl; 75 | std::cout << " - Diffuse: "; 76 | std::cout << finish.diffuse << std::endl; 77 | std::cout << " - Specular: "; 78 | std::cout << finish.specular << std::endl; 79 | std::cout << " - Roughness: "; 80 | std::cout << finish.roughness << std::endl; 81 | std::cout << " - Metallic: "; 82 | std::cout << finish.metallic << std::endl; 83 | std::cout << " - Reflection: "; 84 | std::cout << finish.reflection << std::endl; 85 | std::cout << " - Refraction: "; 86 | std::cout << finish.refraction << std::endl; 87 | std::cout << " - Filter: "; 88 | std::cout << finish.filter << std::endl; 89 | std::cout << " - IOR: "; 90 | std::cout << finish.ior << std::endl; 91 | 92 | std::cout << " - Model Transform: " << std::endl; 93 | std::cout << " " << std::setw(4) << inv_M[0][0] << " " << std::setw(4) << inv_M[1][0] << " " << std::setw(4) << inv_M[2][0] << " " << std::setw(4) << inv_M[3][0] << std::endl; 94 | std::cout << " " << std::setw(4) << inv_M[0][1] << " " << std::setw(4) << inv_M[1][1] << " " << std::setw(4) << inv_M[2][1] << " " << std::setw(4) << inv_M[3][1] << std::endl; 95 | std::cout << " " << std::setw(4) << inv_M[0][2] << " " << std::setw(4) << inv_M[1][2] << " " << std::setw(4) << inv_M[2][2] << " " << std::setw(4) << inv_M[3][2] << std::endl; 96 | std::cout << " " << std::setw(4) << inv_M[0][3] << " " << std::setw(4) << inv_M[1][3] << " " << std::setw(4) << inv_M[2][3] << " " << std::setw(4) << inv_M[3][3] << std::endl; 97 | 98 | 99 | std::cout << " - Texture: " << std::endl; 100 | if (textures.colorMap != nullptr) { 101 | textures.colorMap->print(); 102 | } 103 | if (textures.normalMap != nullptr) { 104 | textures.normalMap->print(); 105 | } 106 | if (textures.bumpMap != nullptr) { 107 | textures.bumpMap->print(); 108 | } 109 | } 110 | }; 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /src/Scene.cpp: -------------------------------------------------------------------------------- 1 | #include "Scene.hpp" 2 | #include "Intersection.hpp" 3 | #include "BRDF.hpp" 4 | 5 | const static float EPSILON = 0.0001f; 6 | 7 | Scene::Scene() { 8 | this->rootBox = new BoxNode; 9 | } 10 | 11 | void Scene::createNodeBoundingBox(BoxNode* parent) { 12 | // Leaf node 13 | if (parent->objects.size() <= 1) { 14 | parent->boundingBox.addBox(parent->objects[0]->createBox()); 15 | } 16 | // Inner node 17 | else { 18 | if (parent->leftChild != nullptr) { 19 | parent->boundingBox.addBox(&parent->leftChild->boundingBox); 20 | } 21 | if (parent->rightChild != nullptr) { 22 | parent->boundingBox.addBox(&parent->rightChild->boundingBox); 23 | } 24 | } 25 | } 26 | 27 | void Scene::createSpatialStructures(std::vector objects, BoxNode *parent, int axis) { 28 | if (objects.size() > 1) { 29 | sortObjects(parent->objects, axis); 30 | 31 | // Create left subtree 32 | std::vector leftObjects(objects.begin(), objects.begin() + objects.size() / 2); 33 | parent->leftChild = new BoxNode; 34 | createSpatialStructures(leftObjects, parent->leftChild, (axis+1)%3); 35 | 36 | // Create right subtree 37 | std::vector rightObjects(objects.begin() + objects.size() / 2, objects.end()); 38 | parent->rightChild = new BoxNode; 39 | createSpatialStructures(rightObjects, parent->rightChild, (axis+1)%3); 40 | } 41 | parent->objects = objects; 42 | createNodeBoundingBox(parent); 43 | } 44 | 45 | void Scene::sortObjects(std::vector objects, int axis) { 46 | // Selection sort 47 | for (unsigned int i = 0; i < objects.size(); i++) { 48 | glm::vec3 icen = objects[i]->findCenter(); 49 | unsigned int min = i; 50 | unsigned int j = i+1; 51 | for ( ; j < objects.size(); j++) { 52 | glm::vec3 jcen = objects[j]->findCenter(); 53 | if (jcen[axis] < icen[axis]) { 54 | min = j; 55 | } 56 | } 57 | if (min != i) { 58 | GeoObject* temp = objects[i]; 59 | objects[i] = objects[min]; 60 | objects[min] = temp; 61 | } 62 | } 63 | } 64 | 65 | GeoObject* Scene::boxTraversal(BoxNode *node, const Ray &ray) { 66 | // Base Case 67 | if (node->objects.size() == 1) { 68 | return node->objects[0]; 69 | } 70 | 71 | // Traversal 72 | if (node->boundingBox.intersect(ray) > EPSILON) { 73 | if (node->leftChild != nullptr) { 74 | return boxTraversal(node->leftChild, ray); 75 | } 76 | if (node->rightChild != nullptr) { 77 | return boxTraversal(node->rightChild, ray); 78 | } 79 | } 80 | 81 | return nullptr; 82 | } 83 | 84 | glm::vec3 Scene::createSamplePoint(Intersection &intersection, glm::mat4 &matrix, float x, float y) { 85 | 86 | // Create point on hemisphere 87 | float radial = sqrt(x); 88 | float theta = 2.f*PI*y; 89 | glm::vec3 point = glm::vec3(radial*glm::cos(theta), radial*glm::sin(theta), sqrt(1-x)); 90 | 91 | // Align hemisphere with intersection before returning 92 | return glm::vec3(matrix * glm::vec4(point, 1.f)); 93 | } 94 | 95 | Ray Scene::createCameraRay(const int width, const int height, const int x, const int y, const int m, const int n, const int s) { 96 | Ray ray; 97 | 98 | // p0 99 | ray.position = camera->location; 100 | 101 | // direction 102 | float u = (x + (m + 0.5) / s)/width - 0.5; 103 | float v = (y + (n + 0.5) / s)/height - 0.5; 104 | glm::vec3 w = glm::normalize(glm::vec3(camera->lookAt - camera->location)); 105 | ray.direction = glm::normalize(glm::vec3(u*camera->right + v*camera->up + w)); 106 | 107 | return ray; 108 | } 109 | 110 | Ray Scene::createReflectionRay(const Intersection &intersection) { 111 | glm::vec3 reflection_dir = glm::normalize(intersection.ray.direction - 2 * glm::dot(intersection.ray.direction, intersection.normal) * intersection.normal); 112 | 113 | return Ray(intersection.point + reflection_dir * EPSILON, reflection_dir); 114 | } 115 | 116 | Ray Scene::createRefractionRay(const Intersection &intersection) { 117 | float n1 = 1; 118 | float n2 = intersection.object->finish.ior; 119 | glm::vec3 norm = intersection.normal; 120 | 121 | // If we're 'exiting' an object 122 | if (dot(norm, intersection.ray.direction) > 0) { 123 | n1 = n2; 124 | n2 = 1; 125 | norm = -norm; 126 | } 127 | 128 | float dDotN = dot(intersection.ray.direction, norm); 129 | float ratio = n1/n2; 130 | float root = 1-(ratio*ratio)*(1-dDotN*dDotN); 131 | 132 | glm::vec3 refraction_dir = glm::normalize(ratio*(intersection.ray.direction-dDotN*norm)-norm*(float)sqrt(root)); 133 | 134 | return Ray(intersection.point + refraction_dir * EPSILON, refraction_dir); 135 | } 136 | 137 | void Scene::print() { 138 | // Print camera 139 | camera->print(); 140 | std::cout << std::endl << "---" << std::endl; 141 | 142 | // Lights 143 | std::cout << std::endl << lights.size() << " light(s)" << std::endl; 144 | for(unsigned int i = 0; i < lights.size(); i++) { 145 | std::cout << std::endl << "Light[" << i << "]:" << std::endl; 146 | lights[i]->print(); 147 | } 148 | std::cout << std::endl << "---" << std::endl; 149 | 150 | // Print objects 151 | std::cout << std::endl << objects.size() << " object(s)" << std::endl; 152 | for(unsigned int i = 0; i < objects.size(); i++) { 153 | std::cout << std::endl << "Object[" << i << "]:" << std::endl; 154 | objects[i]->print(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include // std::std::cout 2 | #include // strcpy 3 | #include // vectors 4 | #include // set precision 5 | #include 6 | 7 | #include "Renderer.hpp" 8 | #include "Intersection.hpp" 9 | #include "Loader.hpp" 10 | #include "Scene.hpp" 11 | 12 | Renderer renderer; 13 | Loader loader; 14 | Scene scene; 15 | 16 | /* Flags for different arguments 17 | 0 - render 18 | 1 - sceneinfo 19 | 2 - pixelray 20 | 3 - firsthit | pixelcolor 21 | */ 22 | int arg_flags[4] = {0}; 23 | 24 | int main(int args, char **argv) { 25 | 26 | // Catch empty line args 27 | if (args == 1) { 28 | return 1; 29 | } 30 | 31 | // Store command line arguments 32 | arg_flags[0] = !strcmp(argv[1], "render"); 33 | arg_flags[1] = !strcmp(argv[1], "sceneinfo"); 34 | arg_flags[2] = !strcmp(argv[1], "pixelray"); 35 | arg_flags[3] = !strcmp(argv[1], "firsthit") || !strcmp(argv[1], "pixelcolor"); 36 | int num_threads = 0; 37 | if (!strcmp(argv[1], "pixeltrace")) { 38 | renderer.setVerbose(1); 39 | } 40 | if (!strcmp(argv[1], "printrays")) { 41 | renderer.setVerbose(2); 42 | } 43 | // Optional flags 44 | for (int i = 0; i < args; i++) { 45 | if (argv[i][0] == '-') { 46 | // SuperSampling 47 | if (std::string(argv[i]).find("ss") != std::string::npos) { 48 | if (char *num = (strchr(argv[i], '=') + 1)) { 49 | renderer.setSSCount(atoi(num)); 50 | } 51 | } 52 | // Global illumination 53 | if (std::string(argv[i]).find("gi") != std::string::npos) { 54 | renderer.setGIFlag(1); 55 | if (std::string(argv[i]).find("gi_samples") != std::string::npos) { 56 | if (char *num = (strchr(argv[i], '=') + 1)) { 57 | renderer.setGISamples(atoi(num)); 58 | } 59 | } 60 | else if (std::string(argv[i]).find("gi_bounces") != std::string::npos) { 61 | if (char *num = (strchr(argv[i], '=') + 1)) { 62 | renderer.setGIBounces(atoi(num)); 63 | } 64 | } 65 | else if (std::string(argv[i]).find("gi_ratio") != std::string::npos) { 66 | if (char *num = (strchr(argv[i], '=') + 1)) { 67 | renderer.setGIRatio(atoi(num)); 68 | } 69 | } 70 | } 71 | // Fresnel 72 | if (std::string(argv[i]).find("fresnel") != std::string::npos) { 73 | ///////////////////////////////////////////////////////////// 74 | // FIX SDS // 75 | // renderer.setFresnelFlag(1); // 76 | ///////////////////////////////////////////////////////////// 77 | } 78 | // Spatial data structures 79 | if (std::string(argv[i]).find("sds") != std::string::npos) { 80 | renderer.setSpatialFlag(1); 81 | } 82 | // Percent printing 83 | if (std::string(argv[i]).find("percent") != std::string::npos) { 84 | renderer.setPercentFlag(1); 85 | } 86 | // Custom output file name 87 | if (std::string(argv[i]).find("out") != std::string::npos) { 88 | if (char *name = (strchr(argv[i], '=') + 1)) { 89 | renderer.setOutputName(name); 90 | } 91 | } 92 | // Threads 93 | if (std::string(argv[i]).find("threads") != std::string::npos) { 94 | if (char *num = (strchr(argv[i], '=') + 1)) { 95 | num_threads = atoi(num); 96 | } 97 | } 98 | } 99 | } 100 | 101 | std::cout << std::setprecision(4); 102 | 103 | // Parse file + create scene 104 | if (loader.parse(argv[2], scene)) { 105 | return 1; 106 | } 107 | 108 | /* TODO: Move out of main */ 109 | if (renderer.brdf.spatial_flag) { 110 | // Split objects into planes and other 111 | std::vector planes; 112 | std::vector other; 113 | for (unsigned int i = 0; i < scene.objects.size(); i++) { 114 | /* TODO: Give objects an enum defining their type. 115 | * Don't use parsing.. */ 116 | if (!strcmp(scene.objects[i]->type.c_str(), "Plane")) { 117 | planes.push_back(scene.objects[i]); 118 | } 119 | else { 120 | other.push_back(scene.objects[i]); 121 | } 122 | } 123 | scene.objects = planes; 124 | scene.createSpatialStructures(other, scene.rootBox, 0); 125 | } 126 | 127 | // Render 128 | if (arg_flags[0]) { 129 | int window_width = atoi(argv[3]); 130 | int window_height = atoi(argv[4]); 131 | std::cout << "Num threads: " << num_threads << std::endl; 132 | renderer.render(scene, window_width, window_height, (int) std::max(1, num_threads)); 133 | } 134 | 135 | // Sceneinfo 136 | if (arg_flags[1]) { 137 | scene.print(); 138 | } 139 | 140 | if (arg_flags[2] || arg_flags[3] || renderer.brdf.verbose_flag) { 141 | int window_width = atoi(argv[3]); 142 | int window_height = atoi(argv[4]); 143 | int pixel_x = atoi(argv[5]); 144 | int pixel_y = atoi(argv[6]); 145 | Ray ray = scene.createCameraRay(window_width, window_height, pixel_x, pixel_y, 1, 1, 1); 146 | Intersection in(scene, ray, renderer.brdf.spatial_flag); 147 | std::cout << "Pixel: [" << pixel_x << ", " << pixel_y << "] "; 148 | if (arg_flags[2] || arg_flags[3]) { 149 | std::cout << "Ray: "; 150 | ray.print(); 151 | } 152 | if (arg_flags[3] || renderer.brdf.verbose_flag) { 153 | if (arg_flags[3]) { 154 | in.print(); 155 | renderer.print(); 156 | } 157 | const glm::ivec2 size = glm::ivec2(window_width, window_height); 158 | glm::vec3 color = renderer.calculateColor(scene, size, pixel_x, pixel_y); 159 | std::cout << "Color: (" << color.x << ", " << color.y << ", " << color.z << ")" << std::endl; 160 | } 161 | } 162 | 163 | return 0; 164 | } 165 | -------------------------------------------------------------------------------- /src/BRDF.cpp: -------------------------------------------------------------------------------- 1 | #include "BRDF.hpp" 2 | 3 | #include "glm/gtc/matrix_transform.hpp" // Matrix transformations 4 | #include 5 | 6 | const static float EPSILON = 0.0001f; 7 | 8 | glm::vec3 BRDF::raytrace(Scene &scene, Ray &incident_ray, int recurse_count) { 9 | // Base case 10 | if (recurse_count <= 0) { 11 | return glm::vec3(0, 0, 0); 12 | } 13 | 14 | // If no intersection from camera to object, return black 15 | Intersection incident_int(scene, incident_ray, spatial_flag); 16 | if (!incident_int.hit) { 17 | return glm::vec3(0, 0, 0); 18 | } 19 | 20 | return calculateColor(scene, incident_int, recurse_count); 21 | } 22 | 23 | glm::vec3 BRDF::calculateColor(Scene &scene, Intersection &intersection, int recurse_count) { 24 | // Base case 25 | if (recurse_count <= 0 || !intersection.hit) { 26 | return glm::vec3(0, 0, 0); 27 | } 28 | 29 | // Easing variable names 30 | GeoObject::Finish *finish = &intersection.object->finish; 31 | 32 | // Colors 33 | glm::vec3 local_color = calculateLocalColor(scene, intersection, recurse_count); 34 | glm::vec3 reflection_color = calculateReflectionColor(scene, intersection, recurse_count); 35 | glm::vec3 refraction_color = calculateRefractionColor(scene, intersection, recurse_count); 36 | 37 | // Contributions 38 | float fresnel_reflectance = 0.f; 39 | if (fresnel_flag) { 40 | fresnel_reflectance = calculateFresnelReflectance(finish->ior, intersection); 41 | } 42 | float local_contribution = (1.f - finish->filter) * (1.f - finish->reflection); 43 | float reflectance_contribution = (1.f - finish->filter) * (finish->reflection) + (finish->filter) * (fresnel_reflectance); 44 | float transmission_contribution = (finish->filter) * (1 - fresnel_reflectance); 45 | 46 | glm::vec3 out_color = local_color * local_contribution + 47 | reflection_color * reflectance_contribution + 48 | refraction_color * transmission_contribution; 49 | 50 | return out_color; 51 | } 52 | 53 | glm::vec3 BRDF::calculateLocalColor(Scene &scene, Intersection &intersection, int recurse_count) { 54 | if (intersection.object->textures.colorMap != nullptr) { 55 | intersection.pigment = intersection.object->textures.colorMap->getColor(intersection.object->getUVCoords(intersection.objectPoint)); 56 | } 57 | else { 58 | intersection.pigment = intersection.object->finish.color; 59 | } 60 | 61 | glm::vec3 local_color = glm::vec3(0, 0, 0); 62 | 63 | // Ambient 64 | if (!gi_flag) { 65 | local_color = intersection.object->finish.ambient * intersection.pigment; 66 | } 67 | else { 68 | // Global illumination 69 | if (gi_bounces < recurse_count) { 70 | recurse_count = gi_bounces; 71 | } 72 | else { 73 | recurse_count--; 74 | } 75 | local_color = calculateGlobalIllumination(scene, intersection, recurse_count); 76 | } 77 | 78 | // Loop through lights 79 | for (unsigned int i = 0; i < scene.lights.size(); i++) { 80 | // Calculate ray from object to each light 81 | Light *light = scene.lights[i]; 82 | glm::vec3 light_dir = glm::normalize(light->position - intersection.point); 83 | Ray light_ray(intersection.point, light_dir); 84 | 85 | // If no objects are blocking incoming light, BRDF 86 | Intersection light_int(scene, light_ray, spatial_flag); 87 | if (!light_int.hit || distance(intersection.point, light->position) < distance(intersection.point, light_int.point)) { 88 | local_color += BlinnPhong(light, intersection); 89 | } 90 | } 91 | 92 | return local_color; 93 | } 94 | 95 | glm::vec3 BRDF::calculateGlobalIllumination(Scene &scene, Intersection &intersection, int recurse_count) { 96 | glm::vec3 out_color = glm::vec3(0, 0, 0); 97 | float num_samples = gi_samples; 98 | 99 | if (gi_bounces - recurse_count > 0) { 100 | num_samples /= ((gi_bounces - recurse_count) * gi_ratio); 101 | } 102 | 103 | float angle = glm::acos(glm::dot(glm::vec3(0, 0, 1), intersection.normal)); 104 | glm::vec3 axis = glm::cross(glm::vec3(0, 0, 1), intersection.normal); 105 | glm::mat4 matrix = glm::rotate(glm::mat4(1.0f), angle, axis); 106 | 107 | float root_num_samples = std::max(std::sqrt(num_samples), 0.01f); 108 | float ratio = root_num_samples / num_samples; 109 | 110 | // Stratified samples 111 | for (float x = 0.f; x <= num_samples; x += root_num_samples) { 112 | for (float y = 0.f; y <= num_samples; y += root_num_samples) { 113 | float gridX = x / num_samples + ratio * (rand() / (float) RAND_MAX); 114 | float gridY = y / num_samples + ratio * (rand() / (float) RAND_MAX); 115 | 116 | glm::vec3 sample_point = scene.createSamplePoint(intersection, matrix, gridX, gridY); 117 | Ray sample_ray(intersection.point + sample_point * EPSILON, sample_point); 118 | out_color += raytrace(scene, sample_ray, recurse_count); 119 | } 120 | } 121 | 122 | return out_color / (float) num_samples; 123 | } 124 | 125 | glm::vec3 BRDF::calculateReflectionColor(Scene &scene, Intersection &intersection, int recurse) { 126 | if (!intersection.object->finish.reflection) { 127 | return glm::vec3(0, 0, 0); 128 | } 129 | 130 | Ray reflection_ray = scene.createReflectionRay(intersection); 131 | glm::vec3 reflection_color = raytrace(scene, reflection_ray, recurse-1); 132 | 133 | // Reflection color is scaled by object's material 134 | reflection_color *= intersection.pigment; 135 | 136 | return reflection_color; 137 | } 138 | 139 | glm::vec3 BRDF::calculateRefractionColor(Scene &scene, Intersection &intersection, int recurse) { 140 | if (!intersection.object->finish.filter) { 141 | return glm::vec3(0, 0, 0); 142 | } 143 | 144 | Ray refraction_ray = scene.createRefractionRay(intersection); 145 | Intersection ref_intersection = Intersection(scene, refraction_ray, spatial_flag); 146 | // If no intersection 147 | if (!ref_intersection.hit) { 148 | return glm::vec3(0, 0, 0); 149 | } 150 | 151 | glm::vec3 refraction_color = calculateColor(scene, ref_intersection, recurse-1); 152 | 153 | // Beers law 154 | float dist = glm::distance(ref_intersection.point, intersection.point); 155 | glm::vec3 absorb = (1.f - intersection.pigment) * (0.15f) * -dist; 156 | glm::vec3 atten = glm::vec3( exp(absorb.r), exp(absorb.g), exp(absorb.b) ); 157 | refraction_color *= atten; 158 | 159 | return refraction_color; 160 | } 161 | 162 | 163 | glm::vec3 BRDF::BlinnPhong(Light *light, Intersection &object_in) { 164 | // Initialize local vars 165 | GeoObject::Finish *finish = &object_in.object->finish; 166 | glm::vec3 light_dir = glm::normalize(light->position - object_in.point); 167 | glm::vec3 half = glm::normalize(light_dir - object_in.ray.direction); 168 | float NdotL = std::max(0.f, dot(object_in.normal, light_dir)); 169 | float HdotN = std::max(0.f, dot(half, object_in.normal)); 170 | 171 | // Diffuse 172 | glm::vec3 diffuse = glm::vec3(0, 0, 0); 173 | if (NdotL && finish->diffuse) { 174 | diffuse = finish->diffuse * NdotL * light->color * object_in.pigment; 175 | } 176 | 177 | // Specular 178 | glm::vec3 specular = glm::vec3(0, 0, 0);; 179 | if (HdotN && finish->specular) { 180 | float r_squared = finish->roughness*finish->roughness; 181 | specular = finish->specular * object_in.pigment * (float) pow(HdotN, 2/r_squared - 2) * light->color; 182 | } 183 | 184 | return diffuse + specular; 185 | } 186 | 187 | float BRDF::calculateFresnelReflectance(float ior, Intersection &intersection) { 188 | glm::vec3 norm = intersection.normal; 189 | if (dot(intersection.normal, intersection.ray.direction) > 0) { 190 | norm = -norm; 191 | } 192 | 193 | return fresnel(ior, norm, -intersection.ray.direction); 194 | } 195 | 196 | float BRDF::fresnel(float n, glm::vec3 a, glm::vec3 b) { 197 | float F_z = pow(n-1.f, 2)/pow(n+1.f, 2); 198 | return F_z + (1.f-F_z) * pow(1.f-dot(a, b), 5.f); 199 | } 200 | 201 | -------------------------------------------------------------------------------- /src/Loader.cpp: -------------------------------------------------------------------------------- 1 | #include "Loader.hpp" 2 | #include "glm/gtc/matrix_transform.hpp" // Matrix transformations 3 | 4 | void Loader::createColor(GeoObject::Finish *f, std::vector line) { 5 | std::vector floats; 6 | for (unsigned int i = 1; i < line.size(); i++) { 7 | if (line[i].find("rgb") != std::string::npos) { 8 | floats = findFloatsInLine(line); 9 | f->color.r = floats[0]; 10 | f->color.g = floats[1]; 11 | f->color.b = floats[2]; 12 | if (floats.size() > 3) { 13 | f->filter = floats[3]; 14 | } 15 | } 16 | } 17 | } 18 | 19 | void Loader::createFinish(GeoObject::Finish *f, std::vector line) { 20 | for (unsigned int i = 1; i < line.size(); i++) { 21 | if (line[i].find("ambient") != std::string::npos) { 22 | f->ambient = findFloatsInWord(line[i+1])[0]; 23 | } 24 | if (line[i].find("diffuse") != std::string::npos) { 25 | f->diffuse = findFloatsInWord(line[i+1])[0]; 26 | } 27 | if (line[i].find("specular") != std::string::npos) { 28 | f->specular = findFloatsInWord(line[i+1])[0]; 29 | } 30 | if (line[i].find("roughness") != std::string::npos) { 31 | f->roughness = findFloatsInWord(line[i+1])[0]; 32 | } 33 | if (line[i].find("metallic") != std::string::npos) { 34 | f->metallic = findFloatsInWord(line[i+1])[0]; 35 | } 36 | if (line[i].find("reflection") != std::string::npos) { 37 | f->reflection = findFloatsInWord(line[i+1])[0]; 38 | } 39 | if (line[i].find("refraction") != std::string::npos) { 40 | f->refraction = findFloatsInWord(line[i+1])[0]; 41 | } 42 | if (line[i].find("ior") != std::string::npos) { 43 | f->ior = findFloatsInWord(line[i+1])[0]; 44 | } 45 | } 46 | } 47 | 48 | void Loader::createTextures(GeoObject::Textures *t, std::vector line) { 49 | for (unsigned int i = 1; i < line.size(); i++) { 50 | if (line[i].find("color") != std::string::npos) { 51 | t->colorMap = batch.getTexture(line[i+1], Texture::Type::ColorMap); 52 | } 53 | if (line[i].find("normal") != std::string::npos) { 54 | t->normalMap = batch.getTexture(line[i+1], Texture::Type::NormalMap); 55 | } 56 | if (line[i].find("bump") != std::string::npos) { 57 | t->bumpMap = batch.getTexture(line[i+1], Texture::Type::BumpMap); 58 | } 59 | } 60 | } 61 | 62 | void Loader::addProperties(GeoObject *object, std::vector line, std::ifstream& file) { 63 | std::vector floats; 64 | 65 | object->M = glm::mat4(1.f); 66 | // Continue parsing/storing file components until we reach '}' line 67 | while(line[0].compare("}")) { 68 | // Finish 69 | if (!line[0].compare("pigment")) { 70 | createColor(&object->finish, line); 71 | } 72 | if (!line[0].compare("finish")) { 73 | createFinish(&object->finish, line); 74 | } 75 | // Transformations 76 | if (!line[0].compare("scale")) { 77 | floats = findFloatsInLine(line); 78 | glm::vec3 scale = glm::vec3(floats[0], floats[1], floats[2]); 79 | object->M = glm::scale(glm::mat4(1.0f), scale) * object->M; 80 | } 81 | if (!line[0].compare("rotate")) { 82 | floats = findFloatsInLine(line); 83 | glm::vec3 rot = glm::vec3(glm::radians(floats[0]), glm::radians(floats[1]), glm::radians(floats[2])); 84 | object->M = glm::rotate(glm::mat4(1.0f), rot.z, glm::vec3(0, 0, 1)) * object->M; 85 | object->M = glm::rotate(glm::mat4(1.0f), rot.y, glm::vec3(0, 1, 0)) * object->M; 86 | object->M = glm::rotate(glm::mat4(1.0f), rot.x, glm::vec3(1, 0, 0)) * object->M; 87 | } 88 | if (!line[0].compare("translate")) { 89 | floats = findFloatsInLine(line); 90 | glm::vec3 translate = glm::vec3(floats[0], floats[1], floats[2]); 91 | object->M = glm::translate(glm::mat4(1.0f), translate) * object->M; 92 | } 93 | if (!line[0].compare("texture")) { 94 | createTextures(&object->textures, line); 95 | } 96 | // Stupid catch for faulty .pov files 97 | if (line[line.size() - 1].find("}}") != std::string::npos) { 98 | break; 99 | } 100 | line = getLine(&file); 101 | } 102 | 103 | // Invert model matrix before returning 104 | object->inv_M = glm::inverse(object->M); 105 | } 106 | 107 | BoxRenderable* Loader::createBox(int id, std::vector line, std::ifstream& file) { 108 | BoxRenderable *box = new BoxRenderable; 109 | box->id = id; 110 | 111 | std::vector floats; 112 | floats = findFloatsInLine(line); 113 | 114 | glm::vec3 minCorner = glm::vec3(floats[0], floats[1], floats[2]); 115 | glm::vec3 maxCorner = glm::vec3(floats[3], floats[4], floats[5]); 116 | box->updateBox(minCorner, maxCorner); 117 | 118 | addProperties(box, line, file); 119 | 120 | return box; 121 | } 122 | 123 | Triangle* Loader::createTriangle(int id, std::vector line, std::ifstream& file) { 124 | // Create empty triangle object 125 | Triangle *triangle = new Triangle; 126 | triangle->id = id; 127 | 128 | std::vector floats; 129 | 130 | // Same line vertices 131 | floats = findFloatsInLine(line); 132 | if (floats.size() > 0) { 133 | triangle->v1 = glm::vec3(floats[0], floats[1], floats[2]); 134 | triangle->v2 = glm::vec3(floats[3], floats[4], floats[5]); 135 | triangle->v3 = glm::vec3(floats[6], floats[7], floats[8]); 136 | } 137 | // Different line vertices 138 | else { 139 | // v1 140 | line = getLine(&file); 141 | floats = findFloatsInLine(line); 142 | triangle->v1 = glm::vec3(floats[0], floats[1], floats[2]); 143 | // v2 144 | line = getLine(&file); 145 | floats = findFloatsInLine(line); 146 | triangle->v2 = glm::vec3(floats[0], floats[1], floats[2]); 147 | // v3 148 | line = getLine(&file); 149 | floats = findFloatsInLine(line); 150 | triangle->v3 = glm::vec3(floats[0], floats[1], floats[2]); 151 | } 152 | 153 | // Object properties 154 | addProperties(triangle, line, file); 155 | 156 | return triangle; 157 | } 158 | 159 | Plane* Loader::createPlane(int id, std::vector line, std::ifstream& file) { 160 | // Create empty Plane object pointer 161 | Plane *plane = new Plane; 162 | plane->id = id; 163 | 164 | std::vector floats; 165 | 166 | // Normal 167 | floats = findFloatsInLine(line); 168 | plane->normal = normalize(glm::vec3(floats[0], floats[1], floats[2])); 169 | // Distance 170 | plane->distance = floats[3]; 171 | 172 | // Object properties 173 | addProperties(plane, line, file); 174 | 175 | return plane; 176 | } 177 | 178 | Sphere* Loader::createSphere(int id, std::vector line, std::ifstream& file) { 179 | // Create empty Sphere object pointer 180 | Sphere *sphere = new Sphere; 181 | sphere->id = id; 182 | 183 | std::vector floats; 184 | 185 | // Center 186 | floats = findFloatsInLine(line); 187 | sphere->center = glm::vec3(floats[0], floats[1], floats[2]); 188 | // Radius 189 | sphere->radius = floats[3]; 190 | 191 | // Object properties 192 | addProperties(sphere, line, file); 193 | 194 | return sphere; 195 | } 196 | 197 | Light* Loader::createLight(std::vector line, std::ifstream& file) { 198 | // Create empty Light object pointer 199 | Light *light = new Light; 200 | 201 | std::vector floats; 202 | 203 | // Position 204 | floats = findFloatsInLine(line); 205 | light->position = glm::vec3(floats[0], floats[1], floats[2]); 206 | // Color 207 | light->color = glm::vec3(floats[3], floats[4], floats[5]); 208 | 209 | return light; 210 | } 211 | 212 | Camera* Loader::createCamera(std::vector line, std::ifstream& file) { 213 | // Create empty Camera object pointer 214 | Camera *camera = new Camera; 215 | 216 | std::vector floats; 217 | 218 | // Continue parsing/storing file components until we reach '}' line 219 | while(line[0].compare("}")) { 220 | // Stupid catch for .pov formatting 221 | for (unsigned int i = 0; i < line.size(); i++) { 222 | if (line[i].find("location") != std::string::npos) { 223 | floats = findFloatsInLine(line); 224 | camera->location = glm::vec3(floats[0], floats[1], floats[2]); 225 | } 226 | } 227 | if (!line[0].compare("up")) { 228 | floats = findFloatsInLine(line); 229 | camera->up = glm::vec3(floats[0], floats[1], floats[2]); 230 | } 231 | if (!line[0].compare("right")) { 232 | floats = findFloatsInLine(line); 233 | camera->right = glm::vec3(floats[0], floats[1], floats[2]); 234 | } 235 | if (!line[0].compare("look_at")) { 236 | floats = findFloatsInLine(line); 237 | camera->lookAt = glm::vec3(floats[0], floats[1], floats[2]); 238 | } 239 | // Stupid catch for faulty .pov files 240 | if (line[line.size() - 1].find(">}") != std::string::npos) { 241 | break; 242 | } 243 | line = getLine(&file); 244 | } 245 | return camera; 246 | } 247 | 248 | int Loader::parse(const char *file_name, Scene &scene) { 249 | // Create file pointer 250 | std::ifstream inFile(file_name, std::ios::in | std::ios::binary); 251 | if (!inFile) { 252 | std::cout << "Error opening file: " << file_name << std::endl; 253 | return 1; 254 | } 255 | std::string word; 256 | 257 | // Walk through file line by line 258 | while(inFile) { 259 | // Store line as std::vector separating by whitespace 260 | std::vector line = getLine(&inFile); 261 | 262 | // Skip empty lines 263 | if (line.size() <= 0) { 264 | continue; 265 | } 266 | 267 | if(!line[0].compare("camera")) { 268 | scene.camera = createCamera(line, inFile); 269 | } 270 | else if (!line[0].compare("light_source")) { 271 | scene.lights.push_back(createLight(line, inFile)); 272 | } 273 | else if (!line[0].compare("sphere")){ 274 | Sphere *sphere = createSphere(scene.objects.size()+1, line, inFile); 275 | scene.objects.push_back(sphere); 276 | } 277 | else if (!line[0].compare("plane")) { 278 | Plane *plane = createPlane(scene.objects.size()+1, line, inFile); 279 | scene.objects.push_back(plane); 280 | } 281 | else if (!line[0].compare("triangle")) { 282 | Triangle *triangle = createTriangle(scene.objects.size()+1, line, inFile); 283 | scene.objects.push_back(triangle); 284 | } 285 | else if (!line[0].compare("box")) { 286 | BoxRenderable *box = createBox(scene.objects.size()+1, line, inFile); 287 | scene.objects.push_back(box); 288 | } 289 | } 290 | inFile.close(); 291 | 292 | batch.print(); 293 | 294 | return 0; 295 | } 296 | 297 | std::vector Loader::getLine(std::ifstream *file) { 298 | char line[256]; 299 | file->getline(line, 256); 300 | std::stringstream sstream(line); 301 | 302 | std::vector words; 303 | std::string word; 304 | 305 | while(sstream >> word) { 306 | words.push_back(word); 307 | } 308 | 309 | return words; 310 | } 311 | 312 | std::vector Loader::findFloatsInLine(std::vector line) { 313 | std::vector ret; 314 | 315 | for(std::string word : line) { 316 | std::vector wordFloats = findFloatsInWord(word); 317 | ret.insert(ret.end(), wordFloats.begin(), wordFloats.end()); 318 | } 319 | return ret; 320 | } 321 | 322 | std::vector Loader::findFloatsInWord(std::string word) { 323 | std::vector ret; 324 | 325 | // Loop through every char in std::string 326 | unsigned int w = 0; 327 | while(w < word.size()) { 328 | char c = word[w++]; 329 | char num[256]; 330 | int n = 0; 331 | 332 | // If number is found 333 | while ((c >= '0' && c <= '9') || c == '.' || c == '-') { 334 | // Continue looping through std::string collecting digits 335 | num[n++] = c; 336 | c = word[w++]; 337 | } 338 | // If a number was created, add it to the return 339 | if (n > 0) { 340 | num[n] = '\0'; 341 | ret.push_back(strtof(num, 0)); 342 | } 343 | } 344 | 345 | return ret; 346 | } 347 | -------------------------------------------------------------------------------- /src/stb_image_write.h: -------------------------------------------------------------------------------- 1 | /* stb_image_write - v1.05 - public domain - http://nothings.org/stb/stb_image_write.h 2 | writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 3 | no warranty implied; use at your own risk 4 | 5 | Before #including, 6 | 7 | #define STB_IMAGE_WRITE_IMPLEMENTATION 8 | 9 | in the file that you want to have the implementation. 10 | 11 | Will probably not work correctly with strict-aliasing optimizations. 12 | 13 | ABOUT: 14 | 15 | This header file is a library for writing images to C stdio. It could be 16 | adapted to write to memory or a general streaming interface; let me know. 17 | 18 | The PNG output is not optimal; it is 20-50% larger than the file 19 | written by a decent optimizing implementation. This library is designed 20 | for source code compactness and simplicity, not optimal image file size 21 | or run-time performance. 22 | 23 | BUILDING: 24 | 25 | You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. 26 | You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace 27 | malloc,realloc,free. 28 | You can define STBIW_MEMMOVE() to replace memmove() 29 | 30 | USAGE: 31 | 32 | There are four functions, one for each image file format: 33 | 34 | int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); 35 | int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); 36 | int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); 37 | int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); 38 | 39 | There are also four equivalent functions that use an arbitrary write function. You are 40 | expected to open/close your file-equivalent before and after calling these: 41 | 42 | int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); 43 | int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 44 | int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 45 | int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); 46 | 47 | where the callback is: 48 | void stbi_write_func(void *context, void *data, int size); 49 | 50 | You can define STBI_WRITE_NO_STDIO to disable the file variant of these 51 | functions, so the library will not use stdio.h at all. However, this will 52 | also disable HDR writing, because it requires stdio for formatted output. 53 | 54 | Each function returns 0 on failure and non-0 on success. 55 | 56 | The functions create an image file defined by the parameters. The image 57 | is a rectangle of pixels stored from left-to-right, top-to-bottom. 58 | Each pixel contains 'comp' channels of data stored interleaved with 8-bits 59 | per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is 60 | monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. 61 | The *data pointer points to the first byte of the top-left-most pixel. 62 | For PNG, "stride_in_bytes" is the distance in bytes from the first byte of 63 | a row of pixels to the first byte of the next row of pixels. 64 | 65 | PNG creates output files with the same number of components as the input. 66 | The BMP format expands Y to RGB in the file format and does not 67 | output alpha. 68 | 69 | PNG supports writing rectangles of data even when the bytes storing rows of 70 | data are not consecutive in memory (e.g. sub-rectangles of a larger image), 71 | by supplying the stride between the beginning of adjacent rows. The other 72 | formats do not. (Thus you cannot write a native-format BMP through the BMP 73 | writer, both because it is in BGR order and because it may have padding 74 | at the end of the line.) 75 | 76 | HDR expects linear float data. Since the format is always 32-bit rgb(e) 77 | data, alpha (if provided) is discarded, and for monochrome data it is 78 | replicated across all three channels. 79 | 80 | TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed 81 | data, set the global variable 'stbi_write_tga_with_rle' to 0. 82 | 83 | CREDITS: 84 | 85 | PNG/BMP/TGA 86 | Sean Barrett 87 | HDR 88 | Baldur Karlsson 89 | TGA monochrome: 90 | Jean-Sebastien Guay 91 | misc enhancements: 92 | Tim Kelsey 93 | TGA RLE 94 | Alan Hickman 95 | initial file IO callback implementation 96 | Emmanuel Julien 97 | bugfixes: 98 | github:Chribba 99 | Guillaume Chereau 100 | github:jry2 101 | github:romigrou 102 | Sergio Gonzalez 103 | Jonas Karlsson 104 | Filip Wasil 105 | Thatcher Ulrich 106 | github:poppolopoppo 107 | Patrick Boettcher 108 | 109 | LICENSE 110 | 111 | See end of file for license information. 112 | 113 | */ 114 | 115 | #ifndef INCLUDE_STB_IMAGE_WRITE_H 116 | #define INCLUDE_STB_IMAGE_WRITE_H 117 | 118 | #ifdef __cplusplus 119 | extern "C" { 120 | #endif 121 | 122 | #ifdef STB_IMAGE_WRITE_STATIC 123 | #define STBIWDEF static 124 | #else 125 | #define STBIWDEF extern 126 | extern int stbi_write_tga_with_rle; 127 | #endif 128 | 129 | #ifndef STBI_WRITE_NO_STDIO 130 | STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); 131 | STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); 132 | STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); 133 | STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); 134 | #endif 135 | 136 | typedef void stbi_write_func(void *context, void *data, int size); 137 | 138 | STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); 139 | STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 140 | STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); 141 | STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); 142 | 143 | #ifdef __cplusplus 144 | } 145 | #endif 146 | 147 | #endif//INCLUDE_STB_IMAGE_WRITE_H 148 | 149 | #ifdef STB_IMAGE_WRITE_IMPLEMENTATION 150 | 151 | #ifdef _WIN32 152 | #ifndef _CRT_SECURE_NO_WARNINGS 153 | #define _CRT_SECURE_NO_WARNINGS 154 | #endif 155 | #ifndef _CRT_NONSTDC_NO_DEPRECATE 156 | #define _CRT_NONSTDC_NO_DEPRECATE 157 | #endif 158 | #endif 159 | 160 | #ifndef STBI_WRITE_NO_STDIO 161 | #include 162 | #endif // STBI_WRITE_NO_STDIO 163 | 164 | #include 165 | #include 166 | #include 167 | #include 168 | 169 | #if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) 170 | // ok 171 | #elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) 172 | // ok 173 | #else 174 | #error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." 175 | #endif 176 | 177 | #ifndef STBIW_MALLOC 178 | #define STBIW_MALLOC(sz) malloc(sz) 179 | #define STBIW_REALLOC(p,newsz) realloc(p,newsz) 180 | #define STBIW_FREE(p) free(p) 181 | #endif 182 | 183 | #ifndef STBIW_REALLOC_SIZED 184 | #define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) 185 | #endif 186 | 187 | 188 | #ifndef STBIW_MEMMOVE 189 | #define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) 190 | #endif 191 | 192 | 193 | #ifndef STBIW_ASSERT 194 | #include 195 | #define STBIW_ASSERT(x) assert(x) 196 | #endif 197 | 198 | #define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) 199 | 200 | typedef struct 201 | { 202 | stbi_write_func *func; 203 | void *context; 204 | } stbi__write_context; 205 | 206 | // initialize a callback-based context 207 | static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) 208 | { 209 | s->func = c; 210 | s->context = context; 211 | } 212 | 213 | #ifndef STBI_WRITE_NO_STDIO 214 | 215 | static void stbi__stdio_write(void *context, void *data, int size) 216 | { 217 | fwrite(data,1,size,(FILE*) context); 218 | } 219 | 220 | static int stbi__start_write_file(stbi__write_context *s, const char *filename) 221 | { 222 | FILE *f = fopen(filename, "wb"); 223 | stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); 224 | return f != NULL; 225 | } 226 | 227 | static void stbi__end_write_file(stbi__write_context *s) 228 | { 229 | fclose((FILE *)s->context); 230 | } 231 | 232 | #endif // !STBI_WRITE_NO_STDIO 233 | 234 | typedef unsigned int stbiw_uint32; 235 | typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; 236 | 237 | #ifdef STB_IMAGE_WRITE_STATIC 238 | static int stbi_write_tga_with_rle = 1; 239 | #else 240 | int stbi_write_tga_with_rle = 1; 241 | #endif 242 | 243 | static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) 244 | { 245 | while (*fmt) { 246 | switch (*fmt++) { 247 | case ' ': break; 248 | case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); 249 | s->func(s->context,&x,1); 250 | break; } 251 | case '2': { int x = va_arg(v,int); 252 | unsigned char b[2]; 253 | b[0] = STBIW_UCHAR(x); 254 | b[1] = STBIW_UCHAR(x>>8); 255 | s->func(s->context,b,2); 256 | break; } 257 | case '4': { stbiw_uint32 x = va_arg(v,int); 258 | unsigned char b[4]; 259 | b[0]=STBIW_UCHAR(x); 260 | b[1]=STBIW_UCHAR(x>>8); 261 | b[2]=STBIW_UCHAR(x>>16); 262 | b[3]=STBIW_UCHAR(x>>24); 263 | s->func(s->context,b,4); 264 | break; } 265 | default: 266 | STBIW_ASSERT(0); 267 | return; 268 | } 269 | } 270 | } 271 | 272 | static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) 273 | { 274 | va_list v; 275 | va_start(v, fmt); 276 | stbiw__writefv(s, fmt, v); 277 | va_end(v); 278 | } 279 | 280 | static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) 281 | { 282 | unsigned char arr[3]; 283 | arr[0] = a, arr[1] = b, arr[2] = c; 284 | s->func(s->context, arr, 3); 285 | } 286 | 287 | static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) 288 | { 289 | unsigned char bg[3] = { 255, 0, 255}, px[3]; 290 | int k; 291 | 292 | if (write_alpha < 0) 293 | s->func(s->context, &d[comp - 1], 1); 294 | 295 | switch (comp) { 296 | case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case 297 | case 1: 298 | if (expand_mono) 299 | stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp 300 | else 301 | s->func(s->context, d, 1); // monochrome TGA 302 | break; 303 | case 4: 304 | if (!write_alpha) { 305 | // composite against pink background 306 | for (k = 0; k < 3; ++k) 307 | px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; 308 | stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); 309 | break; 310 | } 311 | /* FALLTHROUGH */ 312 | case 3: 313 | stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); 314 | break; 315 | } 316 | if (write_alpha > 0) 317 | s->func(s->context, &d[comp - 1], 1); 318 | } 319 | 320 | static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) 321 | { 322 | stbiw_uint32 zero = 0; 323 | int i,j, j_end; 324 | 325 | if (y <= 0) 326 | return; 327 | 328 | if (vdir < 0) 329 | j_end = -1, j = y-1; 330 | else 331 | j_end = y, j = 0; 332 | 333 | for (; j != j_end; j += vdir) { 334 | for (i=0; i < x; ++i) { 335 | unsigned char *d = (unsigned char *) data + (j*x+i)*comp; 336 | stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); 337 | } 338 | s->func(s->context, &zero, scanline_pad); 339 | } 340 | } 341 | 342 | static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) 343 | { 344 | if (y < 0 || x < 0) { 345 | return 0; 346 | } else { 347 | va_list v; 348 | va_start(v, fmt); 349 | stbiw__writefv(s, fmt, v); 350 | va_end(v); 351 | stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); 352 | return 1; 353 | } 354 | } 355 | 356 | static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) 357 | { 358 | int pad = (-x*3) & 3; 359 | return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, 360 | "11 4 22 4" "4 44 22 444444", 361 | 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 362 | 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header 363 | } 364 | 365 | STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) 366 | { 367 | stbi__write_context s; 368 | stbi__start_write_callbacks(&s, func, context); 369 | return stbi_write_bmp_core(&s, x, y, comp, data); 370 | } 371 | 372 | #ifndef STBI_WRITE_NO_STDIO 373 | STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) 374 | { 375 | stbi__write_context s; 376 | if (stbi__start_write_file(&s,filename)) { 377 | int r = stbi_write_bmp_core(&s, x, y, comp, data); 378 | stbi__end_write_file(&s); 379 | return r; 380 | } else 381 | return 0; 382 | } 383 | #endif //!STBI_WRITE_NO_STDIO 384 | 385 | static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) 386 | { 387 | int has_alpha = (comp == 2 || comp == 4); 388 | int colorbytes = has_alpha ? comp-1 : comp; 389 | int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 390 | 391 | if (y < 0 || x < 0) 392 | return 0; 393 | 394 | if (!stbi_write_tga_with_rle) { 395 | return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, 396 | "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); 397 | } else { 398 | int i,j,k; 399 | 400 | stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); 401 | 402 | for (j = y - 1; j >= 0; --j) { 403 | unsigned char *row = (unsigned char *) data + j * x * comp; 404 | int len; 405 | 406 | for (i = 0; i < x; i += len) { 407 | unsigned char *begin = row + i * comp; 408 | int diff = 1; 409 | len = 1; 410 | 411 | if (i < x - 1) { 412 | ++len; 413 | diff = memcmp(begin, row + (i + 1) * comp, comp); 414 | if (diff) { 415 | const unsigned char *prev = begin; 416 | for (k = i + 2; k < x && len < 128; ++k) { 417 | if (memcmp(prev, row + k * comp, comp)) { 418 | prev += comp; 419 | ++len; 420 | } else { 421 | --len; 422 | break; 423 | } 424 | } 425 | } else { 426 | for (k = i + 2; k < x && len < 128; ++k) { 427 | if (!memcmp(begin, row + k * comp, comp)) { 428 | ++len; 429 | } else { 430 | break; 431 | } 432 | } 433 | } 434 | } 435 | 436 | if (diff) { 437 | unsigned char header = STBIW_UCHAR(len - 1); 438 | s->func(s->context, &header, 1); 439 | for (k = 0; k < len; ++k) { 440 | stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); 441 | } 442 | } else { 443 | unsigned char header = STBIW_UCHAR(len - 129); 444 | s->func(s->context, &header, 1); 445 | stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); 446 | } 447 | } 448 | } 449 | } 450 | return 1; 451 | } 452 | 453 | int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) 454 | { 455 | stbi__write_context s; 456 | stbi__start_write_callbacks(&s, func, context); 457 | return stbi_write_tga_core(&s, x, y, comp, (void *) data); 458 | } 459 | 460 | #ifndef STBI_WRITE_NO_STDIO 461 | int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) 462 | { 463 | stbi__write_context s; 464 | if (stbi__start_write_file(&s,filename)) { 465 | int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); 466 | stbi__end_write_file(&s); 467 | return r; 468 | } else 469 | return 0; 470 | } 471 | #endif 472 | 473 | // ************************************************************************************************* 474 | // Radiance RGBE HDR writer 475 | // by Baldur Karlsson 476 | 477 | #define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) 478 | 479 | void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) 480 | { 481 | int exponent; 482 | float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); 483 | 484 | if (maxcomp < 1e-32f) { 485 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; 486 | } else { 487 | float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; 488 | 489 | rgbe[0] = (unsigned char)(linear[0] * normalize); 490 | rgbe[1] = (unsigned char)(linear[1] * normalize); 491 | rgbe[2] = (unsigned char)(linear[2] * normalize); 492 | rgbe[3] = (unsigned char)(exponent + 128); 493 | } 494 | } 495 | 496 | void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) 497 | { 498 | unsigned char lengthbyte = STBIW_UCHAR(length+128); 499 | STBIW_ASSERT(length+128 <= 255); 500 | s->func(s->context, &lengthbyte, 1); 501 | s->func(s->context, &databyte, 1); 502 | } 503 | 504 | void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) 505 | { 506 | unsigned char lengthbyte = STBIW_UCHAR(length); 507 | STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code 508 | s->func(s->context, &lengthbyte, 1); 509 | s->func(s->context, data, length); 510 | } 511 | 512 | void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) 513 | { 514 | unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; 515 | unsigned char rgbe[4]; 516 | float linear[3]; 517 | int x; 518 | 519 | scanlineheader[2] = (width&0xff00)>>8; 520 | scanlineheader[3] = (width&0x00ff); 521 | 522 | /* skip RLE for images too small or large */ 523 | if (width < 8 || width >= 32768) { 524 | for (x=0; x < width; x++) { 525 | switch (ncomp) { 526 | case 4: /* fallthrough */ 527 | case 3: linear[2] = scanline[x*ncomp + 2]; 528 | linear[1] = scanline[x*ncomp + 1]; 529 | linear[0] = scanline[x*ncomp + 0]; 530 | break; 531 | default: 532 | linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; 533 | break; 534 | } 535 | stbiw__linear_to_rgbe(rgbe, linear); 536 | s->func(s->context, rgbe, 4); 537 | } 538 | } else { 539 | int c,r; 540 | /* encode into scratch buffer */ 541 | for (x=0; x < width; x++) { 542 | switch(ncomp) { 543 | case 4: /* fallthrough */ 544 | case 3: linear[2] = scanline[x*ncomp + 2]; 545 | linear[1] = scanline[x*ncomp + 1]; 546 | linear[0] = scanline[x*ncomp + 0]; 547 | break; 548 | default: 549 | linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; 550 | break; 551 | } 552 | stbiw__linear_to_rgbe(rgbe, linear); 553 | scratch[x + width*0] = rgbe[0]; 554 | scratch[x + width*1] = rgbe[1]; 555 | scratch[x + width*2] = rgbe[2]; 556 | scratch[x + width*3] = rgbe[3]; 557 | } 558 | 559 | s->func(s->context, scanlineheader, 4); 560 | 561 | /* RLE each component separately */ 562 | for (c=0; c < 4; c++) { 563 | unsigned char *comp = &scratch[width*c]; 564 | 565 | x = 0; 566 | while (x < width) { 567 | // find first run 568 | r = x; 569 | while (r+2 < width) { 570 | if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) 571 | break; 572 | ++r; 573 | } 574 | if (r+2 >= width) 575 | r = width; 576 | // dump up to first run 577 | while (x < r) { 578 | int len = r-x; 579 | if (len > 128) len = 128; 580 | stbiw__write_dump_data(s, len, &comp[x]); 581 | x += len; 582 | } 583 | // if there's a run, output it 584 | if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd 585 | // find next byte after run 586 | while (r < width && comp[r] == comp[x]) 587 | ++r; 588 | // output run up to r 589 | while (x < r) { 590 | int len = r-x; 591 | if (len > 127) len = 127; 592 | stbiw__write_run_data(s, len, comp[x]); 593 | x += len; 594 | } 595 | } 596 | } 597 | } 598 | } 599 | } 600 | 601 | static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) 602 | { 603 | if (y <= 0 || x <= 0 || data == NULL) 604 | return 0; 605 | else { 606 | // Each component is stored separately. Allocate scratch space for full output scanline. 607 | unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); 608 | int i, len; 609 | char buffer[128]; 610 | char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; 611 | s->func(s->context, header, sizeof(header)-1); 612 | 613 | len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); 614 | s->func(s->context, buffer, len); 615 | 616 | for(i=0; i < y; i++) 617 | stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); 618 | STBIW_FREE(scratch); 619 | return 1; 620 | } 621 | } 622 | 623 | int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) 624 | { 625 | stbi__write_context s; 626 | stbi__start_write_callbacks(&s, func, context); 627 | return stbi_write_hdr_core(&s, x, y, comp, (float *) data); 628 | } 629 | 630 | #ifndef STBI_WRITE_NO_STDIO 631 | int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) 632 | { 633 | stbi__write_context s; 634 | if (stbi__start_write_file(&s,filename)) { 635 | int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); 636 | stbi__end_write_file(&s); 637 | return r; 638 | } else 639 | return 0; 640 | } 641 | #endif // STBI_WRITE_NO_STDIO 642 | 643 | 644 | ////////////////////////////////////////////////////////////////////////////// 645 | // 646 | // PNG writer 647 | // 648 | 649 | // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() 650 | #define stbiw__sbraw(a) ((int *) (a) - 2) 651 | #define stbiw__sbm(a) stbiw__sbraw(a)[0] 652 | #define stbiw__sbn(a) stbiw__sbraw(a)[1] 653 | 654 | #define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) 655 | #define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) 656 | #define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) 657 | 658 | #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) 659 | #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) 660 | #define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) 661 | 662 | static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) 663 | { 664 | int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; 665 | void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); 666 | STBIW_ASSERT(p); 667 | if (p) { 668 | if (!*arr) ((int *) p)[1] = 0; 669 | *arr = (void *) ((int *) p + 2); 670 | stbiw__sbm(*arr) = m; 671 | } 672 | return *arr; 673 | } 674 | 675 | static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) 676 | { 677 | while (*bitcount >= 8) { 678 | stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); 679 | *bitbuffer >>= 8; 680 | *bitcount -= 8; 681 | } 682 | return data; 683 | } 684 | 685 | static int stbiw__zlib_bitrev(int code, int codebits) 686 | { 687 | int res=0; 688 | while (codebits--) { 689 | res = (res << 1) | (code & 1); 690 | code >>= 1; 691 | } 692 | return res; 693 | } 694 | 695 | static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) 696 | { 697 | int i; 698 | for (i=0; i < limit && i < 258; ++i) 699 | if (a[i] != b[i]) break; 700 | return i; 701 | } 702 | 703 | static unsigned int stbiw__zhash(unsigned char *data) 704 | { 705 | stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); 706 | hash ^= hash << 3; 707 | hash += hash >> 5; 708 | hash ^= hash << 4; 709 | hash += hash >> 17; 710 | hash ^= hash << 25; 711 | hash += hash >> 6; 712 | return hash; 713 | } 714 | 715 | #define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) 716 | #define stbiw__zlib_add(code,codebits) \ 717 | (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) 718 | #define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) 719 | // default huffman tables 720 | #define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) 721 | #define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) 722 | #define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) 723 | #define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) 724 | #define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) 725 | #define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) 726 | 727 | #define stbiw__ZHASH 16384 728 | 729 | unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) 730 | { 731 | static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; 732 | static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; 733 | static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; 734 | static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; 735 | unsigned int bitbuf=0; 736 | int i,j, bitcount=0; 737 | unsigned char *out = NULL; 738 | unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); 739 | if (quality < 5) quality = 5; 740 | 741 | stbiw__sbpush(out, 0x78); // DEFLATE 32K window 742 | stbiw__sbpush(out, 0x5e); // FLEVEL = 1 743 | stbiw__zlib_add(1,1); // BFINAL = 1 744 | stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman 745 | 746 | for (i=0; i < stbiw__ZHASH; ++i) 747 | hash_table[i] = NULL; 748 | 749 | i=0; 750 | while (i < data_len-3) { 751 | // hash next 3 bytes of data to be compressed 752 | int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; 753 | unsigned char *bestloc = 0; 754 | unsigned char **hlist = hash_table[h]; 755 | int n = stbiw__sbcount(hlist); 756 | for (j=0; j < n; ++j) { 757 | if (hlist[j]-data > i-32768) { // if entry lies within window 758 | int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); 759 | if (d >= best) best=d,bestloc=hlist[j]; 760 | } 761 | } 762 | // when hash table entry is too long, delete half the entries 763 | if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { 764 | STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); 765 | stbiw__sbn(hash_table[h]) = quality; 766 | } 767 | stbiw__sbpush(hash_table[h],data+i); 768 | 769 | if (bestloc) { 770 | // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal 771 | h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); 772 | hlist = hash_table[h]; 773 | n = stbiw__sbcount(hlist); 774 | for (j=0; j < n; ++j) { 775 | if (hlist[j]-data > i-32767) { 776 | int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); 777 | if (e > best) { // if next match is better, bail on current match 778 | bestloc = NULL; 779 | break; 780 | } 781 | } 782 | } 783 | } 784 | 785 | if (bestloc) { 786 | int d = (int) (data+i - bestloc); // distance back 787 | STBIW_ASSERT(d <= 32767 && best <= 258); 788 | for (j=0; best > lengthc[j+1]-1; ++j); 789 | stbiw__zlib_huff(j+257); 790 | if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); 791 | for (j=0; d > distc[j+1]-1; ++j); 792 | stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); 793 | if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); 794 | i += best; 795 | } else { 796 | stbiw__zlib_huffb(data[i]); 797 | ++i; 798 | } 799 | } 800 | // write out final bytes 801 | for (;i < data_len; ++i) 802 | stbiw__zlib_huffb(data[i]); 803 | stbiw__zlib_huff(256); // end of block 804 | // pad with 0 bits to byte boundary 805 | while (bitcount) 806 | stbiw__zlib_add(0,1); 807 | 808 | for (i=0; i < stbiw__ZHASH; ++i) 809 | (void) stbiw__sbfree(hash_table[i]); 810 | STBIW_FREE(hash_table); 811 | 812 | { 813 | // compute adler32 on input 814 | unsigned int s1=1, s2=0; 815 | int blocklen = (int) (data_len % 5552); 816 | j=0; 817 | while (j < data_len) { 818 | for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; 819 | s1 %= 65521, s2 %= 65521; 820 | j += blocklen; 821 | blocklen = 5552; 822 | } 823 | stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); 824 | stbiw__sbpush(out, STBIW_UCHAR(s2)); 825 | stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); 826 | stbiw__sbpush(out, STBIW_UCHAR(s1)); 827 | } 828 | *out_len = stbiw__sbn(out); 829 | // make returned pointer freeable 830 | STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); 831 | return (unsigned char *) stbiw__sbraw(out); 832 | } 833 | 834 | static unsigned int stbiw__crc32(unsigned char *buffer, int len) 835 | { 836 | static unsigned int crc_table[256] = 837 | { 838 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 839 | 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 840 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 841 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 842 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 843 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 844 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 845 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 846 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 847 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 848 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 849 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 850 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 851 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 852 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 853 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 854 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 855 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 856 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 857 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 858 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 859 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 860 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 861 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 862 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 863 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 864 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 865 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 866 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 867 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 868 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 869 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D 870 | }; 871 | 872 | unsigned int crc = ~0u; 873 | int i; 874 | for (i=0; i < len; ++i) 875 | crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; 876 | return ~crc; 877 | } 878 | 879 | #define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) 880 | #define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); 881 | #define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) 882 | 883 | static void stbiw__wpcrc(unsigned char **data, int len) 884 | { 885 | unsigned int crc = stbiw__crc32(*data - len - 4, len+4); 886 | stbiw__wp32(*data, crc); 887 | } 888 | 889 | static unsigned char stbiw__paeth(int a, int b, int c) 890 | { 891 | int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); 892 | if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); 893 | if (pb <= pc) return STBIW_UCHAR(b); 894 | return STBIW_UCHAR(c); 895 | } 896 | 897 | // @OPTIMIZE: provide an option that always forces left-predict or paeth predict 898 | unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) 899 | { 900 | int ctype[5] = { -1, 0, 4, 2, 6 }; 901 | unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; 902 | unsigned char *out,*o, *filt, *zlib; 903 | signed char *line_buffer; 904 | int i,j,k,p,zlen; 905 | 906 | if (stride_bytes == 0) 907 | stride_bytes = x * n; 908 | 909 | filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; 910 | line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } 911 | for (j=0; j < y; ++j) { 912 | static int mapping[] = { 0,1,2,3,4 }; 913 | static int firstmap[] = { 0,1,0,5,6 }; 914 | int *mymap = (j != 0) ? mapping : firstmap; 915 | int best = 0, bestval = 0x7fffffff; 916 | for (p=0; p < 2; ++p) { 917 | for (k= p?best:0; k < 5; ++k) { 918 | int type = mymap[k],est=0; 919 | unsigned char *z = pixels + stride_bytes*j; 920 | for (i=0; i < n; ++i) 921 | switch (type) { 922 | case 0: line_buffer[i] = z[i]; break; 923 | case 1: line_buffer[i] = z[i]; break; 924 | case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; 925 | case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; 926 | case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; 927 | case 5: line_buffer[i] = z[i]; break; 928 | case 6: line_buffer[i] = z[i]; break; 929 | } 930 | for (i=n; i < x*n; ++i) { 931 | switch (type) { 932 | case 0: line_buffer[i] = z[i]; break; 933 | case 1: line_buffer[i] = z[i] - z[i-n]; break; 934 | case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; 935 | case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; 936 | case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; 937 | case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; 938 | case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; 939 | } 940 | } 941 | if (p) break; 942 | for (i=0; i < x*n; ++i) 943 | est += abs((signed char) line_buffer[i]); 944 | if (est < bestval) { bestval = est; best = k; } 945 | } 946 | } 947 | // when we get here, best contains the filter type, and line_buffer contains the data 948 | filt[j*(x*n+1)] = (unsigned char) best; 949 | STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); 950 | } 951 | STBIW_FREE(line_buffer); 952 | zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory 953 | STBIW_FREE(filt); 954 | if (!zlib) return 0; 955 | 956 | // each tag requires 12 bytes of overhead 957 | out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); 958 | if (!out) return 0; 959 | *out_len = 8 + 12+13 + 12+zlen + 12; 960 | 961 | o=out; 962 | STBIW_MEMMOVE(o,sig,8); o+= 8; 963 | stbiw__wp32(o, 13); // header length 964 | stbiw__wptag(o, "IHDR"); 965 | stbiw__wp32(o, x); 966 | stbiw__wp32(o, y); 967 | *o++ = 8; 968 | *o++ = STBIW_UCHAR(ctype[n]); 969 | *o++ = 0; 970 | *o++ = 0; 971 | *o++ = 0; 972 | stbiw__wpcrc(&o,13); 973 | 974 | stbiw__wp32(o, zlen); 975 | stbiw__wptag(o, "IDAT"); 976 | STBIW_MEMMOVE(o, zlib, zlen); 977 | o += zlen; 978 | STBIW_FREE(zlib); 979 | stbiw__wpcrc(&o, zlen); 980 | 981 | stbiw__wp32(o,0); 982 | stbiw__wptag(o, "IEND"); 983 | stbiw__wpcrc(&o,0); 984 | 985 | STBIW_ASSERT(o == out + *out_len); 986 | 987 | return out; 988 | } 989 | 990 | #ifndef STBI_WRITE_NO_STDIO 991 | STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) 992 | { 993 | FILE *f; 994 | int len; 995 | unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); 996 | if (png == NULL) return 0; 997 | f = fopen(filename, "wb"); 998 | if (!f) { STBIW_FREE(png); return 0; } 999 | fwrite(png, 1, len, f); 1000 | fclose(f); 1001 | STBIW_FREE(png); 1002 | return 1; 1003 | } 1004 | #endif 1005 | 1006 | STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) 1007 | { 1008 | int len; 1009 | unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); 1010 | if (png == NULL) return 0; 1011 | func(context, png, len); 1012 | STBIW_FREE(png); 1013 | return 1; 1014 | } 1015 | 1016 | #endif // STB_IMAGE_WRITE_IMPLEMENTATION 1017 | 1018 | /* Revision history 1019 | 1.04 (2017-03-03) 1020 | monochrome BMP expansion 1021 | 1.03 ??? 1022 | 1.02 (2016-04-02) 1023 | avoid allocating large structures on the stack 1024 | 1.01 (2016-01-16) 1025 | STBIW_REALLOC_SIZED: support allocators with no realloc support 1026 | avoid race-condition in crc initialization 1027 | minor compile issues 1028 | 1.00 (2015-09-14) 1029 | installable file IO function 1030 | 0.99 (2015-09-13) 1031 | warning fixes; TGA rle support 1032 | 0.98 (2015-04-08) 1033 | added STBIW_MALLOC, STBIW_ASSERT etc 1034 | 0.97 (2015-01-18) 1035 | fixed HDR asserts, rewrote HDR rle logic 1036 | 0.96 (2015-01-17) 1037 | add HDR output 1038 | fix monochrome BMP 1039 | 0.95 (2014-08-17) 1040 | add monochrome TGA output 1041 | 0.94 (2014-05-31) 1042 | rename private functions to avoid conflicts with stb_image.h 1043 | 0.93 (2014-05-27) 1044 | warning fixes 1045 | 0.92 (2010-08-01) 1046 | casts to unsigned char to fix warnings 1047 | 0.91 (2010-07-17) 1048 | first public release 1049 | 0.90 first internal release 1050 | */ 1051 | 1052 | /* 1053 | ------------------------------------------------------------------------------ 1054 | This software is available under 2 licenses -- choose whichever you prefer. 1055 | ------------------------------------------------------------------------------ 1056 | ALTERNATIVE A - MIT License 1057 | Copyright (c) 2017 Sean Barrett 1058 | Permission is hereby granted, free of charge, to any person obtaining a copy of 1059 | this software and associated documentation files (the "Software"), to deal in 1060 | the Software without restriction, including without limitation the rights to 1061 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 1062 | of the Software, and to permit persons to whom the Software is furnished to do 1063 | so, subject to the following conditions: 1064 | The above copyright notice and this permission notice shall be included in all 1065 | copies or substantial portions of the Software. 1066 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1067 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1068 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1069 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1070 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 1071 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 1072 | SOFTWARE. 1073 | ------------------------------------------------------------------------------ 1074 | ALTERNATIVE B - Public Domain (www.unlicense.org) 1075 | This is free and unencumbered software released into the public domain. 1076 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 1077 | software, either in source code form or as a compiled binary, for any purpose, 1078 | commercial or non-commercial, and by any means. 1079 | In jurisdictions that recognize copyright laws, the author or authors of this 1080 | software dedicate any and all copyright interest in the software to the public 1081 | domain. We make this dedication for the benefit of the public at large and to 1082 | the detriment of our heirs and successors. We intend this dedication to be an 1083 | overt act of relinquishment in perpetuity of all present and future rights to 1084 | this software under copyright law. 1085 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1086 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1087 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 1088 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 1089 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 1090 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1091 | ------------------------------------------------------------------------------ 1092 | */ --------------------------------------------------------------------------------