├── .gitignore ├── src ├── main.cpp ├── scene.cpp ├── texture.cpp ├── display.cpp ├── model.cpp ├── camera.cpp ├── engine.cpp ├── transform.cpp ├── renderer.cpp ├── mesh.cpp └── rasterizer.cpp ├── include ├── texture.h ├── engine.h ├── transform.h ├── renderer.h ├── camera.h ├── display.h ├── scene.h ├── model.h ├── rasterizer.h ├── mesh.h ├── vector3d.h ├── matrix.h └── shader.h ├── CMakeLists.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode 3 | Release/ 4 | test_models/ -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "engine.h" 2 | 3 | int main() { 4 | Engine engine; 5 | engine.run(); 6 | return 0; 7 | } -------------------------------------------------------------------------------- /include/texture.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "vector3d.h" 3 | 4 | struct Texture { 5 | unsigned char* image; 6 | int width; 7 | int height; 8 | int channels; 9 | 10 | Texture(); 11 | ~Texture(); 12 | 13 | Vector3i getColor(int i, int j); 14 | bool load_texture(std::string filename); 15 | }; 16 | -------------------------------------------------------------------------------- /include/engine.h: -------------------------------------------------------------------------------- 1 | #ifndef ENGINE_H 2 | #define ENGINE_H 3 | 4 | #include 5 | 6 | class Engine { 7 | private: 8 | bool m_IsWindowClosed; 9 | float m_Rotation; 10 | private: 11 | void processInput(const sf::Event& event); 12 | 13 | public: 14 | Engine(); 15 | ~Engine(); 16 | 17 | void run(); 18 | }; 19 | 20 | #endif // ENGINE_H -------------------------------------------------------------------------------- /include/transform.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSFORM_H 2 | #define TRANSFORM_H 3 | 4 | #include "matrix.h" 5 | 6 | namespace Transform { 7 | Matrix4f translate(Vector3f translateVector); 8 | Matrix4f scale(Vector3f scalingFactors); 9 | Matrix4f rotate(Vector3f angles); 10 | Matrix4f lookAt(const Vector3f& eye, const Vector3f& center, const Vector3f& up); 11 | Matrix4f viewportMatrix(int x, int y, int w, int h); 12 | } 13 | 14 | #endif // TRANSFORM_H -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(Renderer) 4 | 5 | # import library target for SFML 6 | find_package(SFML 2.5 COMPONENTS graphics window REQUIRED) 7 | find_package(OpenMP REQUIRED) 8 | 9 | include_directories(include lib) 10 | file(GLOB SOURCES 11 | include/*.h 12 | src/*.cpp 13 | lib/stb_image.h) 14 | add_executable(Renderer ${SOURCES}) 15 | target_link_libraries(Renderer PRIVATE OpenMP::OpenMP_CXX sfml-graphics sfml-window) 16 | -------------------------------------------------------------------------------- /src/scene.cpp: -------------------------------------------------------------------------------- 1 | #include "scene.h" 2 | 3 | Scene::Scene() {} 4 | 5 | Scene::~Scene() {} 6 | 7 | Model* Scene::getModel(){ 8 | return m_Model; 9 | } 10 | 11 | Camera* Scene::getCamera() { 12 | // TODO: Add a vector to hold camera, also have a default camera 13 | // to render the scene from. 14 | return m_Camera; 15 | } 16 | 17 | void Scene::loadModelInScene(Model* model) { 18 | /** 19 | * TODO: Add a vector to hold every model. 20 | * */ 21 | 22 | m_Model = model; 23 | } 24 | 25 | void Scene::loadCameraInScene(Camera* camera) { 26 | m_Camera = camera; 27 | } 28 | -------------------------------------------------------------------------------- /include/renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER_H 2 | #define RENDERER_H 3 | 4 | #include "scene.h" 5 | #include 6 | 7 | class Renderer { 8 | private: 9 | sf::Uint8* m_PixelBuffer; 10 | float* m_Zbuffer; 11 | Camera* m_Camera; 12 | Model* m_Model; 13 | // Matrix4f m_Viewport; // viewport matrix for rendering scene 14 | public: 15 | Renderer(); 16 | ~Renderer(); 17 | void renderScene(Scene& scene, float cameraRotation); 18 | void perspectiveDivide(Vector4f* verts); 19 | sf::Uint8* getPixelBuffer(); 20 | // Matrix4f& getViewportMatrix(); 21 | }; 22 | 23 | #endif // RENDERER_H 24 | -------------------------------------------------------------------------------- /include/camera.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERA_H 2 | #define CAMERA_H 3 | 4 | #include "matrix.h" 5 | 6 | class Camera { 7 | private: 8 | Vector3f m_Position; 9 | Vector3f m_Up; 10 | Vector3f m_Target; 11 | 12 | float m_FOV; 13 | float m_Near; 14 | float m_Far; 15 | public: 16 | Camera(); 17 | Camera(Vector3f position, Vector3f target); 18 | ~Camera(); 19 | void setPosition(Vector3f position); 20 | void setTarget(Vector3f target); 21 | void setFOV(float fov); 22 | void setNear(float near); 23 | void setFar(float far); 24 | Vector3f getCameraDirection(); 25 | Matrix4f getProjectionMatrix(); 26 | Matrix4f getViewMatrix(); 27 | }; 28 | 29 | #endif // CAMERA_H -------------------------------------------------------------------------------- /include/display.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_H 2 | #define DISPLAY_H 3 | 4 | #include 5 | #include "vector3d.h" 6 | #include "mesh.h" 7 | 8 | class DisplayBackend { 9 | private: 10 | sf::RenderWindow m_Window; 11 | sf::Texture m_Buffer; 12 | sf::Sprite m_DrawBuffer; 13 | sf::Uint8* m_PixelBuffer; 14 | 15 | public: 16 | const static int WINDOW_WIDTH = 800; 17 | const static int WINDOW_HEIGHT = 800; 18 | 19 | public: 20 | DisplayBackend(); 21 | ~DisplayBackend(); 22 | 23 | void update(); 24 | void createWindow(); 25 | void swapBuffers(sf::Uint8* pixelBuffer); 26 | 27 | sf::RenderWindow& getWindowInstance(); 28 | }; 29 | 30 | #endif // DISPLAY_H 31 | -------------------------------------------------------------------------------- /include/scene.h: -------------------------------------------------------------------------------- 1 | #ifndef SCENE_H 2 | #define SCENE_H 3 | 4 | /** 5 | * TODO: 6 | * This class will contain every model that is to be rendered in the scene 7 | * along with the cameras and the InputManager class will handle how user 8 | * interacts with this scene such as camera movement. Renderer class will have a 9 | * render method to render a given scene. 10 | * */ 11 | 12 | #include "model.h" 13 | #include "camera.h" 14 | 15 | #include 16 | 17 | class Scene { 18 | private: 19 | Camera* m_Camera; 20 | Model* m_Model; 21 | std::string m_Path; 22 | public: 23 | Scene(); 24 | ~Scene(); 25 | Model* getModel(); 26 | Camera* getCamera(); 27 | void loadModelInScene(Model* model); 28 | void loadCameraInScene(Camera* camera); 29 | }; 30 | 31 | #endif // SCENE_H -------------------------------------------------------------------------------- /src/texture.cpp: -------------------------------------------------------------------------------- 1 | #include "texture.h" 2 | #include 3 | 4 | #define STB_IMAGE_IMPLEMENTATION 5 | #include "stb_image.h" 6 | 7 | Texture::Texture() { image = nullptr; } 8 | 9 | Texture::~Texture() { delete [] image; } 10 | 11 | Vector3i Texture::getColor(int i, int j) { 12 | if (!image) return Vector3i(0, 0, 0); 13 | i = i % width; 14 | j = j % height; 15 | unsigned char* pixelOffset = image + (i + j * width) * channels; 16 | return Vector3i(pixelOffset[0], pixelOffset[1], pixelOffset[2]); 17 | } 18 | 19 | bool Texture::load_texture(std::string filename) { 20 | stbi_set_flip_vertically_on_load(true); 21 | image = stbi_load(filename.c_str(), &width, &height, &channels, 0); 22 | if (!image) { 23 | std::cout << "Error loading texture file " << filename << std::endl; 24 | return false; 25 | } 26 | std::cout << "Successfully loaded texture file " << filename << std::endl; 27 | return true; 28 | } 29 | -------------------------------------------------------------------------------- /include/model.h: -------------------------------------------------------------------------------- 1 | #ifndef MODEL_H 2 | #define MODEL_H 3 | 4 | #include "mesh.h" 5 | #include "matrix.h" 6 | #include "texture.h" 7 | 8 | /** 9 | * This class contains every model which is to be rendered on the scene 10 | * it will contain the mesh, along with other properties like 11 | * model matrix, textures and material files. 12 | * */ 13 | 14 | 15 | class Model { 16 | private: 17 | Mesh* m_Mesh; 18 | Matrix4f m_ModelMatrix; 19 | std::string m_filepath; 20 | Texture *m_Normal; 21 | Texture *m_Diffuse; 22 | 23 | public: 24 | Model(std::string path); 25 | Model(); 26 | ~Model(); 27 | void transformModel(Vector3f translate, Vector3f rotate, Vector3f scale); 28 | void loadDiffuse(std::string filename); 29 | void loadNormal(std::string filename); 30 | const Matrix4f& getModelMatrix() const; 31 | Texture* getDiffuse(); 32 | Texture* getNormal(); 33 | Mesh* getMesh(); 34 | }; 35 | 36 | #endif // MODEL_H 37 | -------------------------------------------------------------------------------- /src/display.cpp: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | #include 3 | #include 4 | 5 | DisplayBackend::DisplayBackend() { 6 | m_PixelBuffer = new sf::Uint8[WINDOW_WIDTH*WINDOW_HEIGHT*4]; 7 | m_Buffer.create(WINDOW_WIDTH, WINDOW_HEIGHT); 8 | m_Buffer.setRepeated(false); 9 | m_DrawBuffer.setTexture(m_Buffer); 10 | } 11 | 12 | DisplayBackend::~DisplayBackend() { 13 | delete [] m_PixelBuffer; 14 | } 15 | 16 | void DisplayBackend::createWindow() { 17 | m_Window.create(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "3D Software Renderer"); 18 | } 19 | 20 | void DisplayBackend::update() { 21 | m_Window.clear(); 22 | m_Window.draw(m_DrawBuffer); 23 | m_Window.display(); 24 | } 25 | 26 | void DisplayBackend::swapBuffers(sf::Uint8* pixelBuffer) { 27 | std::copy_n(pixelBuffer, 4*WINDOW_HEIGHT*WINDOW_WIDTH, m_PixelBuffer); 28 | m_Buffer.update(m_PixelBuffer); 29 | std::fill(pixelBuffer, pixelBuffer + 4*WINDOW_WIDTH*WINDOW_HEIGHT, 0); 30 | } 31 | 32 | sf::RenderWindow& DisplayBackend::getWindowInstance() { 33 | return m_Window; 34 | } -------------------------------------------------------------------------------- /include/rasterizer.h: -------------------------------------------------------------------------------- 1 | #ifndef RASTERIZER_H 2 | #define RASTERIZER_H 3 | 4 | #include 5 | #include "mesh.h" 6 | #include "vector3d.h" 7 | #include "model.h" 8 | #include "shader.h" 9 | 10 | /** 11 | * Rasterizer class holds functions which are required to rasterize a triangle 12 | * on the screen. Contains only static methods as it doesn't hold any data, 13 | * maybe create a namespace instead of a class just like Transform namespace? 14 | * */ 15 | 16 | class Rasterizer { 17 | public: 18 | Rasterizer(); 19 | ~Rasterizer(); 20 | static Vector3f barycentric(const Vector4f& v0, const Vector4f& v1, const Vector4f& v2, const Vector2i& P); 21 | static float edgeFunction(const Vector4f& a, const Vector4f& b, const Vector2i& p); 22 | static float edgeFunction(const Vector4f& a, const Vector4f& b, const Vector4f& c); 23 | static bool isInside(const Vector4f& v0, const Vector4f& v1, const Vector4f& v2, const Vector2i& P); 24 | static void setPixel(int x, int y, sf::Color color, sf::Uint8* pixelBuffer); 25 | static void drawLine(Vector2i p1, Vector2i p2, sf::Color color, sf::Uint8* pixelBuffer); 26 | static void drawTriangle(Vector4f *pts, IShader& shader, sf::Uint8* pixelBuffer, float* zbuffer); 27 | static void drawWireframe(Vector4f *pts, sf::Uint8* pixelBuffer); 28 | static Matrix4f Viewport; 29 | }; 30 | 31 | #endif // RASTERIZER_H 32 | -------------------------------------------------------------------------------- /src/model.cpp: -------------------------------------------------------------------------------- 1 | #include "model.h" 2 | #include "transform.h" 3 | 4 | Model::Model() : m_filepath("teapot.obj") { 5 | m_ModelMatrix = identity(m_ModelMatrix); 6 | m_Mesh = new Mesh; 7 | if (!m_Mesh->loadFile(m_filepath)) { 8 | std::cout << "Error loading " << m_filepath << std::endl; 9 | } 10 | m_Diffuse = new Texture; 11 | m_Normal = new Texture; 12 | } 13 | 14 | Model::Model(std::string path) : m_filepath(path) { 15 | m_ModelMatrix = identity(m_ModelMatrix); 16 | m_Mesh = new Mesh; 17 | if(!m_Mesh->loadFile(path)) { 18 | std::cout << "Error loading " << path << std::endl; 19 | } 20 | m_Diffuse = new Texture; 21 | m_Normal = new Texture; 22 | } 23 | 24 | Model::~Model() { 25 | delete m_Mesh; 26 | } 27 | 28 | void Model::transformModel(Vector3f translate, Vector3f rotate, Vector3f scale) { 29 | // takes the parameters for transformations and sets the 30 | // Model matrix with transformation applied in this order 31 | // M = Translate * rotate * scale 32 | 33 | Matrix4f T = Transform::translate(translate); 34 | Matrix4f R = Transform::rotate(rotate); 35 | Matrix4f S = Transform::scale(scale); 36 | 37 | m_ModelMatrix = T*R*S; 38 | } 39 | 40 | const Matrix4f& Model::getModelMatrix() const { return m_ModelMatrix; } 41 | 42 | Mesh* Model::getMesh() { 43 | return m_Mesh; 44 | } 45 | 46 | Texture* Model::getDiffuse() { return m_Diffuse; } 47 | Texture* Model::getNormal() { return m_Normal; } 48 | 49 | void Model::loadNormal(std::string filename) { 50 | m_Normal->load_texture(filename); 51 | } 52 | 53 | void Model::loadDiffuse(std::string filename) { 54 | m_Diffuse->load_texture(filename); 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D Software Renderer 2 | A simple 3D software renderer built in C++ from scratch without using OpenGL, DirectX or Vulkan. 3 | Only dependencies are for graphics output and image (texture) loading. 4 | 5 | This project is heavily inspired from [Tinyrenderer]. I created this to get a deeper understanding of 3D Graphics and OpenGL pipeline along with solidifying my concepts of C++ programming. 6 | 7 | ## Features 8 | * 3D Software Rasterization pipeline 9 | * Parallelized renderer (using OpenMP) 10 | * Extendable vertex and fragment shaders using C++ virtual functions 11 | * Tangent space normal texture mapping 12 | * Backface culling 13 | * Perspective correct interpolation in fragment shaders 14 | * Hidden faces removal using Z-buffer 15 | * Implementations of Flat, Gouraud, Phong and Normal map shaders. 16 | 17 | ### Engine features 18 | * SFML Backend for graphics output 19 | * Camera movement around an axis 20 | * Obj model parser 21 | * Templated vector and matrix math library 22 | * Texture loading (using [stbimage]) 23 | 24 | # Samples 25 | ![Head](https://i.ibb.co/whfRFFb/african-head.png "Head") 26 | ![Fire hydrant](https://i.ibb.co/V2N0CVz/fire-hydrant.png "Fire hydrant") 27 | ![Leather Shoes](https://i.ibb.co/8X2TXHv/leather-shoes.png "Leather Shoes") 28 | 29 | # Todo features 30 | * Add Scene loader for loading multiple objects 31 | * Camera control in every directions (currently it has only orbiting controls) 32 | * PBR Shading 33 | * Ambient Occlusion 34 | * Shadows 35 | 36 | ## References 37 | * [Tinyrenderer][tinyrenderer] 38 | * [Scratchapixel 2.0][scratchapixel] 39 | 40 | [tinyrenderer]: https://github.com/ssloy/tinyrenderer/wiki 41 | [scratchapixel]: https://www.scratchapixel.com/index.php 42 | [stbimage]: https://github.com/nothings/stb 43 | -------------------------------------------------------------------------------- /include/mesh.h: -------------------------------------------------------------------------------- 1 | #ifndef MESH_H 2 | #define MESH_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "vector3d.h" 8 | #include "matrix.h" 9 | 10 | class Mesh { 11 | private: 12 | int m_NumVertices; 13 | int m_NumFaces; 14 | std::string m_FileName; 15 | std::vector m_Vertices; 16 | std::vector m_Textures; 17 | std::vector m_Normals; 18 | std::vector m_faceNormal; 19 | 20 | // indices which form a face 21 | std::vector m_VertexIndices; 22 | std::vector m_TextureIndices; 23 | std::vector m_NormalIndices; 24 | 25 | // tangent basis vectors 26 | std::vector m_Tangents; 27 | std::vector m_Bitangents; 28 | 29 | std::vector m_FaceTangents; 30 | std::vector m_FaceBitangents; 31 | 32 | std::map > m_FaceList; 33 | 34 | private: 35 | void computeFaceNormals(); 36 | void computeTangentFace(); 37 | void computeTangentVertex(); 38 | // void computeTangentVertex(int vertexIndex, std::vector faceList, Vector3f tangent, Vector3f bitangent, Vector3f normal); 39 | void computeFaceList(); 40 | Matrix3f getTBNFace(int face_idx); 41 | 42 | public: 43 | Mesh(); 44 | ~Mesh(); 45 | 46 | int getNumVertices(); 47 | int getNumFaces(); 48 | bool loadFile(std::string path); 49 | void normalizeMesh(); 50 | Vector3f& getVertex(int index); 51 | Vector3f& getTexture(int index); 52 | Vector3f& getNormal(int index); 53 | Vector3f& getTangent(int face_idx); 54 | Vector3f& getBitangent(int index); 55 | Vector3f& getFaceNormal(int index); 56 | std::vector& getVertexIndices(); 57 | std::vector& getTextureIndices(); 58 | std::vector& getNormalIndices(); 59 | }; 60 | 61 | #endif // MESH_H 62 | -------------------------------------------------------------------------------- /src/camera.cpp: -------------------------------------------------------------------------------- 1 | #include "camera.h" 2 | #include "display.h" 3 | #include "transform.h" 4 | 5 | #include 6 | 7 | Camera::Camera() 8 | : m_Position(Vector3f(0., 0., -1.)) 9 | , m_Up(Vector3f(0., 1., 0.)) 10 | , m_Target(Vector3f(0., 0., 0.)) 11 | , m_FOV(60.) 12 | , m_Near(.1f) 13 | , m_Far(10.f) {} 14 | 15 | Camera::Camera(Vector3f position, Vector3f target) 16 | : m_Position(position) 17 | , m_Up(Vector3f(0., 1., 0.)) 18 | , m_Target(target) 19 | , m_FOV(60.) 20 | , m_Near(.1f) 21 | , m_Far(10.f) {} 22 | 23 | Camera::~Camera() {} 24 | 25 | // SETTER METHODS 26 | 27 | void Camera::setPosition(Vector3f position) { 28 | m_Position = position; 29 | } 30 | 31 | void Camera::setTarget(Vector3f target) { m_Target = target; } 32 | 33 | void Camera::setFOV(float fov) { m_FOV = fov; } 34 | 35 | void Camera::setNear(float near) { m_Near = near; } 36 | 37 | void Camera::setFar(float far) { m_Far = far; } 38 | 39 | // build the following projection matrix for the camera 40 | /** 41 | * returns the following matrix 42 | * 43 | * 1/AR*tan(fov/2) 0 0 0 44 | * 0 1/tan(fov/2) 0 0 45 | * 0 0 (n+f)/(n-f) (-2*f*n)/(n-f) 46 | * 0 0 -1 0 47 | * 48 | * */ 49 | 50 | Matrix4f Camera::getProjectionMatrix() { 51 | Matrix4f P; 52 | P = identity(P); 53 | float aspectRatio = DisplayBackend::WINDOW_WIDTH/DisplayBackend::WINDOW_HEIGHT; 54 | float cotangent = 1 / tan((m_FOV / 2) * (M_PI / 180)); // angle in radians 55 | P[0][0] = cotangent / aspectRatio; 56 | P[1][1] = cotangent; 57 | // P[2][2] = (m_Near + m_Far) / (m_Near - m_Far); 58 | // P[2][3] = (-2 * m_Far * m_Near) / (m_Near - m_Far); 59 | P[2][2] = -m_Far / (m_Far - m_Near); 60 | P[2][3] = -m_Far * m_Near / (m_Far - m_Near); 61 | P[3][2] = -1; 62 | P[3][3] = 0; 63 | return P; 64 | } 65 | 66 | Matrix4f Camera::getViewMatrix() { return Transform::lookAt(m_Position, m_Target, m_Up); } 67 | 68 | Vector3f Camera::getCameraDirection() { 69 | return m_Position - m_Target; 70 | } -------------------------------------------------------------------------------- /src/engine.cpp: -------------------------------------------------------------------------------- 1 | #include "engine.h" 2 | #include "display.h" 3 | #include "renderer.h" 4 | #include "scene.h" 5 | 6 | Engine::Engine() : m_IsWindowClosed(false), m_Rotation(0.f) {} 7 | Engine::~Engine() {} 8 | 9 | void Engine::processInput(const sf::Event& event) { 10 | if (event.type == sf::Event::Closed) { 11 | m_IsWindowClosed = true; 12 | return; 13 | } 14 | else if (event.type == sf::Event::KeyPressed) { 15 | if (event.key.code == sf::Keyboard::Left) m_Rotation -= 0.05f; 16 | else if (event.key.code == sf::Keyboard::Right) m_Rotation += 0.05f; 17 | } 18 | } 19 | 20 | void Engine::run() { 21 | DisplayBackend display; 22 | display.createWindow(); 23 | sf::RenderWindow& window = display.getWindowInstance(); 24 | 25 | // TODO: Move this into scene loader, which loads all the models and cams by itself. 26 | 27 | // the files have to be changed here manually for now, later I will use cmdline arguments for this 28 | Model* model = new Model("./test_models/leather_shoes.obj"); 29 | model->loadDiffuse("./test_models/leather_shoes_diffuse.png"); 30 | model->loadNormal("./test_models/leather_shoes_normal.png"); 31 | model->transformModel( 32 | Vector3f(0.3, 0.5, 0), 33 | Vector3f(0, 0, 0), 34 | Vector3f(0.5, 0.5, 0.5) 35 | ); 36 | 37 | Camera* camera = new Camera(Vector3f(0., 0., -3.f), Vector3f(0., 0., 0.)); 38 | 39 | Scene scene; 40 | scene.loadModelInScene(model); 41 | scene.loadCameraInScene(camera); 42 | 43 | Renderer renderer; 44 | 45 | sf::Clock clock; 46 | while (window.isOpen()) { 47 | sf::Event event; 48 | while (window.pollEvent(event)) { 49 | processInput(event); 50 | if (m_IsWindowClosed) window.close(); 51 | } 52 | 53 | sf::Time time = clock.getElapsedTime(); 54 | std::cout << 1.f / time.asSeconds() << std::endl; 55 | clock.restart().asSeconds(); 56 | renderer.renderScene(scene, m_Rotation); 57 | display.swapBuffers(renderer.getPixelBuffer()); 58 | display.update(); 59 | } 60 | 61 | delete camera; 62 | delete model; 63 | } 64 | -------------------------------------------------------------------------------- /src/transform.cpp: -------------------------------------------------------------------------------- 1 | #include "transform.h" 2 | 3 | Matrix4f Transform::translate(Vector3f translateVector) { 4 | // translate to a position pointed to by the given vector. 5 | Matrix4f T; 6 | T = identity(T); 7 | 8 | T[0][3] = -translateVector[0]; 9 | T[1][3] = -translateVector[1]; 10 | T[2][3] = -translateVector[2]; 11 | 12 | return T; 13 | } 14 | 15 | Matrix4f Transform::scale(Vector3f factors) { 16 | // scale across x, y and z axis according to the factors given. 17 | Matrix4f S; 18 | S = identity(S); 19 | 20 | S[0][0] = factors.x; 21 | S[1][1] = factors.y; 22 | S[2][2] = factors.z; 23 | 24 | return S; 25 | } 26 | 27 | Matrix4f Transform::rotate(Vector3f angles) { 28 | // the given argument gives yaw, pitch and roll respectively 29 | // using euler transform, the generated matrix is returned 30 | 31 | // x-axis rotation 32 | float sinAlpha = sin(angles.x); 33 | float cosAlpha = cos(angles.x); 34 | 35 | // y-axis rotation 36 | float sinBeta = sin(angles.y); 37 | float cosBeta = cos(angles.y); 38 | 39 | // z-axis rotation 40 | float sinGamma = sin(angles.z); 41 | float cosGamma = cos(angles.z); 42 | 43 | Matrix4f R; 44 | R = identity(R); 45 | 46 | R[0][0] = cosAlpha * cosBeta; 47 | R[0][1] = cosAlpha * sinBeta * sinGamma - sinAlpha * cosGamma; 48 | R[0][2] = cosAlpha * sinBeta * cosGamma + sinAlpha * sinGamma; 49 | 50 | R[1][0] = sinAlpha * cosBeta; 51 | R[1][1] = sinAlpha * sinBeta * sinGamma + cosAlpha * cosGamma; 52 | R[1][2] = sinAlpha * sinBeta * cosGamma - cosAlpha * sinGamma; 53 | 54 | R[2][0] = -sinBeta; 55 | R[2][1] = cosBeta * sinGamma; 56 | R[2][2] = cosBeta * cosGamma; 57 | 58 | return R; 59 | } 60 | 61 | Matrix4f Transform::lookAt(const Vector3f& eye, const Vector3f& center, const Vector3f& up) { 62 | Vector3f n = normalize(center - eye); 63 | Vector3f u = normalize(cross(n, up)); 64 | Vector3f v = normalize(cross(u, n)); 65 | 66 | Matrix4f c, T; 67 | c = identity(c); 68 | T = identity(T); 69 | for (int i = 0; i < 3; i++) { 70 | c[0][i] = u[i]; 71 | c[1][i] = v[i]; 72 | c[2][i] = -n[i]; 73 | T[i][3] = -eye[i]; 74 | } 75 | return c * T; 76 | } 77 | 78 | Matrix4f Transform::viewportMatrix(int x, int y, int w, int h) { 79 | Matrix4f vp; 80 | vp = identity(vp); 81 | vp[0][3] = x+w/2.f; 82 | vp[1][3] = y+h/2.f; 83 | vp[2][3] = 1.f; 84 | vp[0][0] = w/2.f; 85 | vp[1][1] = h/2.f; 86 | vp[2][2] = 0; 87 | return vp; 88 | } -------------------------------------------------------------------------------- /src/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | #include "display.h" 3 | #include "transform.h" 4 | #include "rasterizer.h" 5 | #include "shader.h" 6 | 7 | #include 8 | #include 9 | 10 | Renderer::Renderer() { 11 | m_PixelBuffer = new sf::Uint8[4 * DisplayBackend::WINDOW_WIDTH * DisplayBackend::WINDOW_HEIGHT]; 12 | m_Zbuffer = new float[DisplayBackend::WINDOW_WIDTH * DisplayBackend::WINDOW_HEIGHT]; 13 | std::fill_n(m_Zbuffer, DisplayBackend::WINDOW_WIDTH * DisplayBackend::WINDOW_HEIGHT, std::numeric_limits::max()); 14 | m_Camera = nullptr; 15 | } 16 | 17 | Renderer::~Renderer() { 18 | delete [] m_PixelBuffer; 19 | } 20 | 21 | void Renderer::perspectiveDivide(Vector4f* verts) { 22 | // verts[0] = verts[0]/verts[0].w; 23 | // verts[1] = verts[1]/verts[1].w; 24 | // verts[2] = verts[2]/verts[2].w; 25 | 26 | for (int i = 0; i < 3; i++) { 27 | verts[i].x /= verts[i].w; 28 | verts[i].y /= verts[i].w; 29 | verts[i].z /= verts[i].w; 30 | } 31 | } 32 | 33 | // Matrix4f& Renderer::getViewportMatrix() { return m_Viewport; } 34 | 35 | void Renderer::renderScene(Scene& scene, float cameraRotation) { 36 | m_Model = scene.getModel(); 37 | m_Camera = scene.getCamera(); 38 | 39 | // get the required data of the mesh. 40 | Mesh* mesh = m_Model->getMesh(); 41 | std::vector& vertices = mesh->getVertexIndices(); 42 | std::vector& textures = mesh->getTextureIndices(); 43 | std::vector& normals = mesh->getNormalIndices(); 44 | 45 | Matrix4f M = m_Model->getModelMatrix(); 46 | Matrix4f MInv = inverse(m_Model->getModelMatrix()); // world to object matrix 47 | 48 | m_Camera->setPosition(Vector3f(sin(cameraRotation) * 2, 0, cos(cameraRotation) * 2)); 49 | 50 | const int totalFaces = mesh->getNumFaces(); 51 | 52 | #pragma omp parallel 53 | { 54 | NormalMap shader; 55 | Vector4f pts[3]; 56 | Vector3f uv[3]; 57 | shader.V = m_Camera->getViewMatrix(); 58 | shader.MV = shader.V * M; 59 | shader.MVP = m_Camera->getProjectionMatrix() * shader.MV; 60 | shader.M = M; 61 | shader.N = transpose(inverse(shader.M)); 62 | shader.diffuse_map = m_Model->getDiffuse(); 63 | shader.normal_map = m_Model->getNormal(); 64 | shader.cameraPos = m_Camera->getCameraDirection(); 65 | 66 | #pragma omp for schedule(dynamic) 67 | for (int i = 0; i < totalFaces; i++) { 68 | 69 | // perfrom early backface culling by checking the dot product of viewer in object space to vertex and vertex normal 70 | if (dot(normalize(multMatrixVec(MInv, m_Camera->getCameraDirection()) - mesh->getVertex(vertices[i][0])), mesh->getFaceNormal(i)) < 0) continue; 71 | 72 | // run vertex shader for every vertex. 73 | for (int j = 0; j < 3; j++) { 74 | pts[j] = shader.vertex(mesh->getVertex(vertices[i][j]), mesh->getNormal(normals[i][j]), mesh->getTexture(textures[i][j]), 75 | mesh->getTangent(vertices[i][j]), mesh->getBitangent(vertices[i][j]), j); 76 | } 77 | 78 | // perform perspective divide: dividing every element of vector by homogenous coordinate 79 | perspectiveDivide(pts); 80 | Rasterizer::drawTriangle(pts, shader, m_PixelBuffer, m_Zbuffer); 81 | } 82 | } 83 | // clear the z-buffer 84 | std::fill(m_Zbuffer, m_Zbuffer + DisplayBackend::WINDOW_WIDTH*DisplayBackend::WINDOW_HEIGHT, std::numeric_limits::max()); 85 | } 86 | 87 | sf::Uint8* Renderer::getPixelBuffer() { return m_PixelBuffer; }; 88 | -------------------------------------------------------------------------------- /include/vector3d.h: -------------------------------------------------------------------------------- 1 | #ifndef VECTOR3D_H 2 | #define VECTOR3D_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | template 9 | class Vector { 10 | public: 11 | T data[N]; 12 | 13 | Vector(); 14 | Vector(const T d[]); 15 | 16 | T& operator[] (size_t index); 17 | const T& operator[] (size_t index) const; 18 | }; 19 | 20 | template 21 | class Vector<2, T> { 22 | public: 23 | T x, y; 24 | 25 | Vector(); 26 | Vector(T x, T y); 27 | 28 | T& operator[] (size_t index); 29 | const T& operator[] (size_t index) const; 30 | }; 31 | 32 | template 33 | class Vector<3, T> { 34 | public: 35 | T x, y, z; 36 | 37 | Vector(); 38 | Vector(T x, T y, T z); 39 | 40 | T& operator[] (size_t index); 41 | const T& operator[] (size_t index) const; 42 | }; 43 | 44 | template 45 | class Vector<4, T> { 46 | public: 47 | T x, y, z, w; 48 | 49 | Vector(); 50 | Vector(T x, T y, T z, T w); 51 | 52 | T& operator[] (size_t index); 53 | const T& operator[] (size_t index) const; 54 | }; 55 | 56 | template 57 | Vector operator +(const Vector& left, const Vector& right); 58 | 59 | template 60 | Vector operator -(const Vector& left, const Vector& right); 61 | 62 | // Vector multiplied by scalar 63 | template 64 | Vector operator *(T lhs, const Vector& rhs); 65 | 66 | template 67 | Vector operator *(const Vector& lhs, T rhs); 68 | 69 | template 70 | Vector operator/(const Vector& lhs, const T& rhs); 71 | 72 | // output the vector values, for debugging purposes 73 | template 74 | std::ostream& operator <<(std::ostream& out, Vector& vector); 75 | 76 | // Dot product of two vectors 77 | template 78 | T dot(const Vector& left, const Vector& right); 79 | 80 | // Cross product of two Vector3T vectors 81 | template 82 | Vector<3, T> cross(Vector<3, T> left, Vector<3, T> right); 83 | 84 | //////////////////////////////////////////////////////////////////////////////// 85 | // DECLARATIONS 86 | /////////////////////////////////////////////////////////////////////////////// 87 | template 88 | Vector::Vector() {} 89 | 90 | template 91 | Vector<2, T>::Vector() {} 92 | 93 | template 94 | Vector<3, T>::Vector() {} 95 | 96 | template 97 | Vector<4, T>::Vector() {} 98 | 99 | template 100 | Vector<2, T>::Vector(T X, T Y) { 101 | x = X; 102 | y = Y; 103 | } 104 | 105 | template 106 | Vector<3, T>::Vector(T X, T Y, T Z) { 107 | x = X; 108 | y = Y; 109 | z = Z; 110 | } 111 | 112 | template 113 | Vector<4, T>::Vector(T X, T Y, T Z, T W) { 114 | x = X; 115 | y = Y; 116 | z = Z; 117 | w = W; 118 | } 119 | 120 | template 121 | Vector::Vector(const T d[]) { 122 | for (int i = 0; i < N; i++) { 123 | data[i] = d[i]; 124 | } 125 | } 126 | 127 | template 128 | T& Vector::operator[](size_t index) { 129 | assert(index >= 0 && index < N); 130 | return data[index]; 131 | } 132 | 133 | template 134 | const T& Vector::operator[](size_t index) const { 135 | assert(index >= 0 && index < N); 136 | return data[index]; 137 | } 138 | 139 | template 140 | T& Vector<2, T>::operator[](size_t index) { 141 | assert(index >= 0 && index < 2); 142 | return index == 0 ? x : y; 143 | } 144 | 145 | template 146 | const T& Vector<2, T>::operator[](size_t index) const { 147 | assert(index >= 0 && index < 2); 148 | return index == 0 ? x : y; 149 | } 150 | 151 | template 152 | T& Vector<3, T>::operator[](size_t index) { 153 | assert(index >= 0 && index < 3); 154 | if (index == 0) return x; 155 | else if (index == 1) return y; 156 | else return z; 157 | } 158 | 159 | template 160 | const T& Vector<3, T>::operator[](size_t index) const { 161 | assert(index >= 0 && index < 3); 162 | if (index == 0) return x; 163 | else if (index == 1) return y; 164 | else return z; 165 | } 166 | 167 | template 168 | T& Vector<4, T>::operator[](size_t index) { 169 | assert(index >= 0 && index < 4); 170 | if (index == 0) return x; 171 | else if (index == 1) return y; 172 | else if (index == 2) return z; 173 | else return w; 174 | } 175 | 176 | template 177 | const T& Vector<4, T>::operator[](size_t index) const { 178 | assert(index >= 0 && index < 4); 179 | if (index == 0) return x; 180 | else if (index == 1) return y; 181 | else if (index == 2) return z; 182 | else return w; 183 | } 184 | 185 | template 186 | Vector operator +(const Vector& left, const Vector& right) { 187 | Vector tmp; 188 | for (int i = 0; i < N; i++) tmp[i] = left[i] + right[i]; 189 | return tmp; 190 | } 191 | 192 | template 193 | Vector operator -(const Vector& left, const Vector& right) { 194 | Vector tmp; 195 | for (int i = 0; i < N; i++) tmp[i] = left[i] - right[i]; 196 | return tmp; 197 | } 198 | 199 | template 200 | Vector operator *(const Vector& lhs, T rhs) { 201 | Vector tmp; 202 | for (int i = 0; i < N; i++) tmp[i] = lhs[i] * rhs; 203 | return tmp; 204 | } 205 | 206 | template 207 | Vector operator *(T lhs, const Vector& rhs) { 208 | Vector tmp; 209 | for (int i = 0; i < N; i++) tmp[i] = lhs * rhs[i]; 210 | return tmp; 211 | } 212 | 213 | template 214 | Vector operator/(const Vector& lhs, const T& rhs) { 215 | Vector tmp; 216 | for (int i = 0; i < N; i++) tmp[i] = lhs[i]/rhs; 217 | return tmp; 218 | } 219 | 220 | template 221 | std::ostream& operator <<(std::ostream& out, Vector& vector) { 222 | out << "( " << vector[0]; 223 | for (int i = 1; i < N; i++) { 224 | out << ",\t" << vector[i]; 225 | } 226 | out << ")"; 227 | return out; 228 | } 229 | 230 | template 231 | T dot(const Vector& left, const Vector& right) { 232 | T res = left[0] * right[0]; 233 | for (int i = 1; i < N; i++) res += left[i] * right[i]; 234 | return res; 235 | } 236 | 237 | template 238 | Vector<3, T> cross(Vector<3, T> left, Vector<3, T> right) { 239 | return Vector<3, T>( 240 | left[1] * right[2] - right[1] * left[2], 241 | right[0] * left[2] - left[0] * right[2], 242 | left[0] * right[1] - right[0] * left[1] 243 | ); 244 | } 245 | 246 | template 247 | Vector<3, T> normalize(const Vector<3, T>& vec) { 248 | return Vector<3, T>(vec/std::sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z)); 249 | } 250 | 251 | template 252 | T norm(const Vector<3, T>& vec) { 253 | return std::sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z); 254 | } 255 | 256 | typedef Vector<2, int> Vector2i; 257 | typedef Vector<2, float> Vector2f; 258 | typedef Vector<3, int> Vector3i; 259 | typedef Vector<3, float> Vector3f; 260 | typedef Vector<4, int> Vector4i; 261 | typedef Vector<4, float> Vector4f; 262 | 263 | #endif // VECTOR3D_H -------------------------------------------------------------------------------- /src/mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "mesh.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Mesh::Mesh() : m_NumVertices(0), m_NumFaces(0) {}; 10 | Mesh::~Mesh() {} 11 | 12 | int Mesh::getNumFaces() { return m_NumFaces; } 13 | int Mesh::getNumVertices() { return m_NumVertices; } 14 | 15 | void Mesh::computeFaceList() { 16 | // computes the list of all faces which a vertex is forming and stores it 17 | // as a map from the vertex index to the vector of faces it is forming. 18 | // required for calculating smooth per vertex tangent space basis. 19 | 20 | Vector3i indices; 21 | for (int i = 0; i < m_NumFaces; i++) { 22 | indices = m_VertexIndices[i]; 23 | for (int j = 0; j < 3; j++) { 24 | if (m_FaceList.find(indices[j]) != m_FaceList.end()) { 25 | // index is seen for the first time 26 | m_FaceList.insert({indices[j], std::vector{i}}); 27 | } 28 | else m_FaceList[indices[j]].push_back(i); 29 | } 30 | } 31 | } 32 | 33 | void Mesh::computeTangentVertex() { 34 | computeFaceList(); 35 | 36 | // computes TBN for every vertex in the mesh 37 | Vector3i face_idxs; 38 | Vector3f T(0, 0, 0), B(0, 0, 0), N(0, 0, 0); 39 | 40 | int faceCount; // number of faces a vertex in included in 41 | 42 | for (int i = 0; i < m_NumVertices; i++) { 43 | faceCount = 0; 44 | 45 | // for every vertex (index i) calculate the corresponding tanget and bitangent 46 | for (auto ele : m_FaceList[i]) { 47 | T = T + m_FaceTangents[ele]; 48 | B = B + m_FaceBitangents[ele]; 49 | 50 | ++faceCount; 51 | } 52 | 53 | if (faceCount > 0) { 54 | T = T / (float)faceCount; 55 | B = B / (float)faceCount; 56 | N = N / (float)faceCount; 57 | normalize(T); 58 | normalize(B); 59 | normalize(N); 60 | 61 | // tangent and bitangent for ith vertex. 62 | m_Tangents.push_back(T); 63 | m_Bitangents.push_back(B); 64 | } 65 | T.x = 0; T.y = 0; T.z = 0; 66 | B.x = 0; B.y = 0; B.z = 0; 67 | } 68 | } 69 | 70 | void Mesh::computeTangentFace() { 71 | // computes the tangent basis represented by T, B and N for normal mapping of textures. 72 | Vector3f uv[3]; // texture coordinates 73 | Vector3f v[3]; // vertex coordiantes 74 | for (int i = 0; i < m_NumFaces; i++) { 75 | for (int j = 0; j < 3; j++) { 76 | v[j] = m_Vertices[m_VertexIndices[i][j]]; 77 | uv[j] = m_Textures[m_TextureIndices[i][j]]; 78 | } 79 | 80 | Vector3f edge1 = v[1] - v[0]; 81 | Vector3f edge2 = v[2] - v[0]; 82 | 83 | Vector3f deltaUV1 = uv[1] - uv[0]; 84 | Vector3f deltaUV2 = uv[2] - uv[0]; 85 | 86 | // T = (E1 * deltaV2 - E2 * deltaV1) / deltaU1*deltaV2 - deltaU2*deltaV1; 87 | // B = (-E1 * deltaU2 - E2 * deltaU1)/ deltaU1*deltaV2 - deltaU2*deltaV1; 88 | 89 | float denom = 1 / ((deltaUV1.x * deltaUV2.y) - (deltaUV1.y * deltaUV2.x)); 90 | Vector3f tangent = (edge1 * deltaUV2.y - edge2 * deltaUV1.y) * denom; 91 | Vector3f bitangent = (edge2 * deltaUV1.x - edge1 * deltaUV2.x) * denom; 92 | 93 | m_FaceTangents.push_back(tangent); 94 | m_FaceBitangents.push_back(bitangent); 95 | } 96 | } 97 | 98 | void Mesh::computeFaceNormals() { 99 | // pre-calculates surface normal for every face in the mesh for techniques likes backface culling 100 | Vector3f tmp[3]; 101 | for (int i = 0; i < m_NumFaces; i++) { 102 | for (int j = 0; j < 3; j++) { 103 | tmp[j] = m_Vertices[m_VertexIndices[i][j]]; 104 | } 105 | m_faceNormal.push_back(normalize(cross(tmp[1] - tmp[0], tmp[2] - tmp[0]))); 106 | } 107 | } 108 | 109 | void Mesh::normalizeMesh() { 110 | // bring down to -1 and +1 dimensions 111 | Vector3f maxPosition = m_Vertices[0]; 112 | Vector3f minPosition = m_Vertices[0]; 113 | 114 | for (size_t i = 0; i < m_Vertices.size(); i++) { 115 | maxPosition.x = std::max(maxPosition.x, m_Vertices[i].x); 116 | minPosition.x = std::min(minPosition.x, m_Vertices[i].x); 117 | maxPosition.y = std::max(maxPosition.y, m_Vertices[i].y); 118 | minPosition.y = std::min(minPosition.y, m_Vertices[i].y); 119 | maxPosition.z = std::max(maxPosition.z, m_Vertices[i].z); 120 | minPosition.z = std::min(minPosition.z, m_Vertices[i].z); 121 | } 122 | float maxCoord = std::max(std::max(maxPosition.x, maxPosition.y), maxPosition.z); 123 | float minCoord = std::min(std::min(minPosition.x, minPosition.y), minPosition.z); 124 | minCoord = std::abs(minCoord); 125 | float scale = std::max(minCoord, maxCoord); 126 | for (size_t i = 0; i < m_Vertices.size(); i++) { 127 | m_Vertices[i] = m_Vertices[i]/scale; 128 | } 129 | } 130 | 131 | bool Mesh::loadFile(std::string path) { 132 | std::cout << "loading " << path << std::endl; 133 | std::ifstream infile(path); 134 | if (!infile.is_open()) return false; 135 | 136 | std::string curline; 137 | while(std::getline(infile, curline)) { 138 | std::istringstream iss(curline); 139 | std::string data; 140 | iss >> data; // check if vertex data, texture data and so on 141 | 142 | if (data == "v") { 143 | float x, y, z; 144 | iss >> x >> y >> z; 145 | m_Vertices.push_back(Vector3f(x, y, z)); 146 | m_NumVertices++; 147 | } else if (data == "vt") { 148 | float u, v; 149 | iss >> u >> v; 150 | m_Textures.push_back(Vector3f(u, v, 0)); 151 | } else if (data == "vn") { 152 | float i, j, k; 153 | iss >> i >> j >> k; 154 | m_Normals.push_back(Vector3f(i, j, k)); 155 | } else if (data == "f") { 156 | // char slash; 157 | // int x, y, z; 158 | // iss >> x >> y >> z; 159 | // x--; y--; z--; 160 | // m_VertexIndices.push_back(Vector3i(x, y, z)); 161 | char slash; 162 | int vi[3], vti[3], vni[3]; 163 | iss >> vi[0] >> slash >> vti[0] >> slash >> vni[0]; 164 | iss >> vi[1] >> slash >> vti[1] >> slash >> vni[1]; 165 | iss >> vi[2] >> slash >> vti[2] >> slash >> vni[2]; 166 | for (int i = 0; i < 3; i++) { 167 | vi[i]--; 168 | vti[i]--; 169 | vni[i]--; 170 | } 171 | m_VertexIndices.push_back(Vector3i(vi[0], vi[1], vi[2])); 172 | m_TextureIndices.push_back(Vector3i(vti[0], vti[1], vti[2])); 173 | m_NormalIndices.push_back(Vector3i(vni[0], vni[1], vni[2])); 174 | m_NumFaces++; 175 | } 176 | } 177 | 178 | // for backface culling and flat shading 179 | computeFaceNormals(); 180 | 181 | // compute per vertex tangent basis vectors 182 | computeTangentFace(); 183 | computeTangentVertex(); 184 | 185 | return true; 186 | } 187 | 188 | Vector3f& Mesh::getFaceNormal(int face_idx) { 189 | return m_faceNormal[face_idx]; 190 | } 191 | 192 | Vector3f& Mesh::getVertex(int index) { 193 | return m_Vertices[index]; 194 | } 195 | 196 | Vector3f& Mesh::getTexture(int index) { 197 | return m_Textures[index]; 198 | } 199 | 200 | Vector3f& Mesh::getNormal(int index) { 201 | return m_Normals[index]; 202 | } 203 | 204 | Vector3f& Mesh::getTangent(int index) { 205 | return m_Tangents[index]; 206 | } 207 | 208 | Vector3f& Mesh::getBitangent(int index) { 209 | return m_Bitangents[index]; 210 | } 211 | 212 | std::vector& Mesh::getVertexIndices() { 213 | return m_VertexIndices; 214 | } 215 | 216 | std::vector& Mesh::getTextureIndices() { 217 | return m_TextureIndices; 218 | } 219 | 220 | std::vector& Mesh::getNormalIndices() { 221 | return m_NormalIndices; 222 | } 223 | -------------------------------------------------------------------------------- /src/rasterizer.cpp: -------------------------------------------------------------------------------- 1 | #include "rasterizer.h" 2 | #include "math.h" 3 | #include "camera.h" 4 | #include "matrix.h" 5 | #include "display.h" 6 | #include "transform.h" 7 | 8 | #include 9 | 10 | Rasterizer::Rasterizer() {} 11 | 12 | Rasterizer::~Rasterizer() {} 13 | 14 | Matrix4f Rasterizer::Viewport = Transform::viewportMatrix( 15 | DisplayBackend::WINDOW_WIDTH/8, 16 | DisplayBackend::WINDOW_HEIGHT/8, 17 | DisplayBackend::WINDOW_WIDTH*3/4, 18 | DisplayBackend::WINDOW_HEIGHT*3/4 19 | ); 20 | 21 | void Rasterizer::setPixel(int i, int j, sf::Color color, sf::Uint8* pixelBuffer) { 22 | if (i < 0 || j < 0) return; 23 | if (i < 0 || j < 0) return; 24 | if (i > DisplayBackend::WINDOW_WIDTH || j > DisplayBackend::WINDOW_HEIGHT) return; 25 | if (i > DisplayBackend::WINDOW_WIDTH || j > DisplayBackend::WINDOW_HEIGHT) return; 26 | size_t pixelOffset = ((DisplayBackend::WINDOW_HEIGHT - j) * DisplayBackend::WINDOW_WIDTH + i) * 4; 27 | pixelBuffer[pixelOffset] = color.r; 28 | pixelBuffer[pixelOffset + 1] = color.g; 29 | pixelBuffer[pixelOffset + 2] = color.b; 30 | pixelBuffer[pixelOffset + 3] = color.a; 31 | } 32 | 33 | void Rasterizer::drawLine(Vector2i p1, Vector2i p2, sf::Color color, sf::Uint8 *pixelBuffer) { 34 | if (p1.x < 0 || p1.y < 0) return; 35 | if (p2.x < 0 || p2.y < 0) return; 36 | if (p1.x > DisplayBackend::WINDOW_WIDTH || p1.y > DisplayBackend::WINDOW_HEIGHT) return; 37 | if (p2.x > DisplayBackend::WINDOW_WIDTH || p2.y > DisplayBackend::WINDOW_HEIGHT) return; 38 | 39 | bool steep = false; 40 | if (std::abs(p1.x-p2.x) < std::abs(p1.y-p2.y)) { // line is steep 41 | std::swap(p1.x, p1.y); 42 | std::swap(p2.x, p2.y); 43 | steep = true; 44 | } 45 | 46 | if (p1.x > p2.x) { 47 | std::swap(p1.x, p2.x); 48 | std::swap(p1.y, p2.y); 49 | } 50 | 51 | int dx = p2.x-p1.x; 52 | int dy = p2.y-p1.y; 53 | 54 | int derror = std::abs(dy) * 2; 55 | int slopeError = 0; 56 | int y = p1.y; 57 | 58 | if (steep) { 59 | for (int x = p1.x; x <= p2.x; x++) { 60 | setPixel(y, x, color, pixelBuffer); 61 | slopeError += derror; 62 | if (slopeError > dx) { 63 | y += (p2.y > p1.y ? 1: -1); 64 | slopeError -= dx * 2; 65 | } 66 | } 67 | } else { 68 | for (int x = p1.x; x <= p2.x; x++) { 69 | setPixel(x, y, color, pixelBuffer); 70 | slopeError += derror; 71 | if (slopeError > dx) { 72 | y += (p2.y > p1.y ? 1: -1); 73 | slopeError -= dx * 2; 74 | } 75 | } 76 | } 77 | } 78 | 79 | void Rasterizer::drawWireframe(Vector4f *pts, sf::Uint8* pixelBuffer) { 80 | 81 | Vector2i v[3]; 82 | 83 | v[0].x = (pts[0].x+1.) * DisplayBackend::WINDOW_WIDTH/2.f; 84 | v[0].y = (pts[0].y+1.) * DisplayBackend::WINDOW_HEIGHT/2.f; 85 | v[1].x = (pts[1].x+1.) * DisplayBackend::WINDOW_WIDTH/2.f; 86 | v[1].y = (pts[1].y+1.) * DisplayBackend::WINDOW_HEIGHT/2.f; 87 | v[2].x = (pts[2].x+1.) * DisplayBackend::WINDOW_WIDTH/2.f; 88 | v[2].y = (pts[2].y+1.) * DisplayBackend::WINDOW_HEIGHT/2.f; 89 | 90 | drawLine(v[0], v[1], sf::Color::White, pixelBuffer); 91 | drawLine(v[0], v[2], sf::Color::White, pixelBuffer); 92 | drawLine(v[1], v[2], sf::Color::White, pixelBuffer); 93 | } 94 | 95 | float Rasterizer::edgeFunction(const Vector4f& a, const Vector4f& b, const Vector2i& p) { 96 | // returns whether the point p is on the right side of the edge formed by vertices a and b 97 | return (a.y - b.y) * (p.x - a.x) - (a.x - b.x) * (p.y - a.y); 98 | } 99 | 100 | float Rasterizer::edgeFunction(const Vector4f& a, const Vector4f& b, const Vector4f& c) { 101 | return (a.y - b.y) * (c.x - a.x) - (a.x - b.x) * (c.y - a.y); 102 | } 103 | 104 | bool Rasterizer::isInside(const Vector4f& v0, const Vector4f& v1, const Vector4f& v2, const Vector2i& P) { 105 | // checks if the point P is inside the triangle formed by vertices v0, v1 and v2; 106 | 107 | return (edgeFunction(v0, v1, P) && edgeFunction(v1, v2, P) && edgeFunction(v2, v0, P)); 108 | } 109 | 110 | Vector3f Rasterizer::barycentric(const Vector4f& v0, const Vector4f& v1, const Vector4f& v2, const Vector2i& P) { 111 | // compute barycentric coordinates of the triangle represented by the given vertices 112 | 113 | float denom = ((v1.x-v0.x) * (v2.y-v0.y) - (v2.x-v0.x) * (v1.y-v0.y)); 114 | if (denom < std::abs(1e-3)) return Vector3f(-1.f, 0.f, 0.f); // degenerate triangle 115 | float invDenom = 1 / ((v1.x-v0.x) * (v2.y-v0.y) - (v2.x-v0.x) * (v1.y-v0.y)); 116 | float u = ((P.x-v0.x) * (v2.y-v0.y) - (v2.x-v0.x) * (P.y-v0.y)) * invDenom; 117 | float v = ((v1.x-v0.x) * (P.y-v0.y) - (P.x-v0.x) * (v1.y-v0.y)) * invDenom; 118 | 119 | return Vector3f(1.f - u - v, u, v); 120 | } 121 | 122 | void Rasterizer::drawTriangle(Vector4f *pts, IShader &shader, sf::Uint8* pixelBuffer, float* zbuffer) { 123 | 124 | Vector3f zc = Vector3f(pts[0].z, pts[1].z, pts[2].z); 125 | Vector3f pc = Vector3f(1./pts[0].w, 1./pts[1].w, 1./pts[2].w); 126 | 127 | // pts[0] = Viewport * pts[0]; 128 | // pts[1] = Viewport * pts[1]; 129 | // pts[2] = Viewport * pts[2]; 130 | 131 | pts[0].x = (pts[0].x+1.) * DisplayBackend::WINDOW_WIDTH/2.f; 132 | pts[0].y = (pts[0].y+1.) * DisplayBackend::WINDOW_HEIGHT/2.f; 133 | pts[1].x = (pts[1].x+1.) * DisplayBackend::WINDOW_WIDTH/2.f; 134 | pts[1].y = (pts[1].y+1.) * DisplayBackend::WINDOW_HEIGHT/2.f; 135 | pts[2].x = (pts[2].x+1.) * DisplayBackend::WINDOW_WIDTH/2.f; 136 | pts[2].y = (pts[2].y+1.) * DisplayBackend::WINDOW_HEIGHT/2.f; 137 | 138 | // calculate bounding box of the three coordinates. 139 | float minX = std::min(pts[0].x, std::min(pts[1].x, pts[2].x)); 140 | float maxX = std::max(pts[0].x, std::max(pts[1].x, pts[2].x)); 141 | float minY = std::min(pts[0].y, std::min(pts[1].y, pts[2].y)); 142 | float maxY = std::max(pts[0].y, std::max(pts[1].y, pts[2].y)); 143 | 144 | // TODO: Add frustum clipping and remove these tests below. 145 | // If the model is too big, or reaches out of screen then 146 | // segfault occurs if not checked since these ranges are used to index in pixel and z-buffer. 147 | 148 | if (minX < 0 || maxX < 0 || minY < 0 || maxY < 0) return; 149 | if (minX > 800 || maxX > 800 || minY > 800 || maxY > 800) return; 150 | Vector2i pixel; 151 | pixel.x = minX; 152 | pixel.y = minY; 153 | 154 | // values for linearly interpolating the edge function, requires less calculation 155 | // makes use of the fact that edgefunction is linear, so we can avoid calculating it at every step 156 | float w0_row = edgeFunction(pts[1], pts[2], pixel); 157 | float dx0 = pts[1].x - pts[2].x; 158 | float dy0 = pts[1].y - pts[2].y; 159 | float w1_row = edgeFunction(pts[2], pts[0], pixel); 160 | float dx1 = pts[2].x - pts[0].x; 161 | float dy1 = pts[2].y - pts[0].y; 162 | float w2_row = edgeFunction(pts[0], pts[1], pixel); 163 | float dx2 = pts[0].x - pts[1].x; 164 | float dy2 = pts[0].y - pts[1].y; 165 | float area = edgeFunction(pts[0], pts[1], pts[2]); 166 | 167 | float w0, w1, w2; 168 | 169 | for (pixel.y = minY; pixel.y < maxY; pixel.y++) { // implicit conversion to int 170 | w0 = w0_row; 171 | w1 = w1_row; 172 | w2 = w2_row; 173 | for (pixel.x = minX; pixel.x < maxX; pixel.x++) { 174 | 175 | // if point p is inside the triangle then proceeed with calculating barycentric coordinates 176 | if (w0 >= 0 && w1 >= 0 && w2 >= 0) { 177 | Vector3f barc{w0/area, w1/area, w2/area}; 178 | // Vector3f barc = barycentric(pts[0], pts[1], pts[2], pixel); 179 | 180 | if (barc.x < 0 || barc.y < 0 || barc.z < 0) continue; // the point is not inside the triangle 181 | float z_dist = dot(zc, barc); // interpolate z axis 182 | float oneOverpersc = 1/dot(pc, barc); // interpolating inverse w coordinate 183 | 184 | // calculate perspective correct barycentric coordinates 185 | barc[0] = barc[0] * pc[0] * oneOverpersc; 186 | barc[1] = barc[1] * pc[1] * oneOverpersc; 187 | barc[2] = barc[2] * pc[2] * oneOverpersc; 188 | 189 | Vector3f rgb = shader.fragment(barc); 190 | sf::Color color = sf::Color(rgb.x, rgb.y, rgb.z, 0xff); 191 | 192 | // check and update z-zbuffer 193 | if (z_dist < zbuffer[pixel.x + pixel.y*DisplayBackend::WINDOW_WIDTH]) { 194 | zbuffer[pixel.x + pixel.y*DisplayBackend::WINDOW_WIDTH] = z_dist; 195 | setPixel(pixel.x, pixel.y, color, pixelBuffer); 196 | } 197 | } 198 | w0 += dy0; 199 | w1 += dy1; 200 | w2 += dy2; 201 | } 202 | 203 | w0_row -= dx0; 204 | w1_row -= dx1; 205 | w2_row -= dx2; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /include/matrix.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIX_H 2 | #define MATRIX_H 3 | 4 | #include "vector3d.h" 5 | 6 | /* 7 | * Design: Consider vector3d as building blocks of matrices. 8 | * and define every operation in terms of vectors 9 | * No need of overload [] operators 10 | * not need to check dimension mismatch 11 | * rather the compiler will throw error for the templates not being defined 12 | * This features the most used and required operations and 13 | * does not contain every matrix operation known. 14 | */ 15 | 16 | // 1. Define matrix in terms of vectors 17 | // 2. Multiplication of MxN with Nx1 matrices 18 | // 3. Specializations for common methods in 3x3 and 4x4 matrices 19 | // 4. set NxN matrix to be identity 20 | // 5. adj, determinant methods for computing inverse of matrices 21 | // 6. inverse, inverse_transpose of 4x4 matrices 22 | // 7. transpose for 4x4 matrix 23 | 24 | // multiplication of MxN matrix with Nx1 array 25 | template 26 | Vector operator *(const Vector >& lhs, const Vector& rhs) { 27 | Vector tmp; 28 | for (int i = 0; i < M; i++) { 29 | tmp[i] = dot(lhs[i], rhs); 30 | } 31 | } 32 | 33 | // specialization for 3x3 matrix and 3x1 vector 34 | template 35 | Vector<3, T> operator *(const Vector<3, Vector<3, T> >& lhs, const Vector<3, T>& rhs) { 36 | return Vector<3, T>(dot(lhs[0], rhs), dot(lhs[1], rhs), dot(lhs[2], rhs)); 37 | } 38 | 39 | template 40 | Vector<4, T> operator *(const Vector<4, Vector<4, T> >& lhs, const Vector<4, T>& rhs) { 41 | return Vector<4, T>( 42 | dot(lhs[0], rhs), 43 | dot(lhs[1], rhs), 44 | dot(lhs[2], rhs), 45 | dot(lhs[3], rhs) 46 | ); 47 | } 48 | 49 | // generalized matrix multiplication 50 | // multiplication of MxN with NxK 51 | template 52 | Vector > operator *(const Vector >& lhs, const Vector >& rhs) { 53 | Vector > ret; 54 | T c; 55 | for (int i = 0; i < M; i++) { 56 | for (int k = 0; k < K; k++) { 57 | c = 0; 58 | for (int j = 0; j < N; j++) { 59 | c += lhs[i][j] * rhs[j][k]; 60 | } 61 | ret[i][k] = c; 62 | } 63 | } 64 | 65 | return ret; 66 | } 67 | 68 | template 69 | Vector > operator *(const Vector >& lhs, const T& rhs) { 70 | Vector > ret; 71 | for (int i = 0; i < M; i++) ret[i] = lhs[i] * rhs; 72 | return ret; 73 | } 74 | 75 | template 76 | Vector > operator *(const Vector >& lhs, const T& rhs) { 77 | Vector > ret; 78 | for (int i = 0; i < N; i++) ret[i] = lhs[i] * rhs; 79 | return ret; 80 | } 81 | 82 | template 83 | Vector > operator /(const Vector >& lhs, const T& rhs) { 84 | Vector > ret; 85 | for (int i = 0; i < M; i++) ret[i] = lhs[i]/rhs; 86 | return ret; 87 | } 88 | 89 | template 90 | Vector > operator /(const Vector >& lhs, const T& rhs) { 91 | Vector > ret; 92 | for (int i = 0; i < N; i++) ret[i] = lhs[i]/rhs; 93 | return ret; 94 | } 95 | 96 | template 97 | Vector > identity(Vector>& mat) { 98 | Vector< N, Vector > ret; 99 | for (int i = 0; i < N; i++) { 100 | for (int j = 0; j < N; j++) { 101 | ret[i][j] = (i==j); 102 | } 103 | } 104 | return ret; 105 | } 106 | 107 | template 108 | Vector > transpose(const Vector >& mat) { 109 | Vector > ret; 110 | for (int i = 0; i < M; i++) { 111 | for (int j = 0; j < N; j++) { 112 | ret[j][i] = mat[i][j]; 113 | } 114 | } 115 | return ret; 116 | } 117 | 118 | template 119 | T getMinor(const Vector >& mat, int row, int col) { 120 | Vector > tmp; 121 | int drow, dcol; 122 | drow = 0; 123 | for (int i = 0; i < N; i++) { 124 | if (i == row) continue; // excluding the rows and col which include given indices 125 | dcol = 0; 126 | for (int j = 0; j < N; j++) { 127 | if (j == col) continue; 128 | tmp[drow][dcol] = mat[i][j]; 129 | dcol++; 130 | } 131 | drow++; 132 | } 133 | 134 | return determinant(tmp); 135 | } 136 | 137 | template 138 | T determinant(const Vector >& mat) { 139 | T sum = 0; 140 | 141 | for (int i = 0; i < N; i++) { 142 | T minor = getMinor(mat, i, 0); 143 | // std::cout << i << " " << minor << std::endl; 144 | sum += mat[i][0] * minor * ((i & 1) ? -1 : 1); // check if odd or even 145 | 146 | } 147 | 148 | return sum; 149 | } 150 | 151 | template 152 | T determinant(const Vector<1, Vector<1, T> >& mat) { 153 | return mat[0][0]; 154 | } 155 | 156 | template 157 | T determinant(const Vector<2, Vector<2, T> >& mat) { 158 | return mat[0][0]* mat[1][1]- mat[1][0]* mat[0][1]; 159 | } 160 | 161 | template 162 | T determinant(const Vector<3, Vector<3, T> >& mat) { 163 | return 164 | mat[0][0] * (mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2]) - 165 | mat[1][0] * (mat[0][1] * mat[2][2] - mat[2][1] * mat[0][2]) + 166 | mat[2][0] * (mat[0][1] * mat[1][2] - mat[1][1] * mat[0][2]); 167 | } 168 | 169 | template 170 | Vector > adj(const Vector >& mat) { 171 | Vector > ret; 172 | for (int i = 0; i < N; i++) { 173 | for (int j = 0; j < N; j++) { 174 | T minor = getMinor(mat, i, j); 175 | T cofactor = ((i + j) & 1) ? -minor : minor; 176 | ret[j][i] = cofactor; 177 | } 178 | } 179 | 180 | return ret; 181 | } 182 | 183 | template 184 | Vector > inverse(const Vector >& mat) { 185 | T d = determinant(mat); 186 | 187 | // linearly dependent basis, should never happen in program. 188 | assert (d != 0); 189 | 190 | return adj(mat)/d; 191 | } 192 | 193 | template 194 | Vector<3, Vector<3, T> > inverse(const Vector<3, Vector<3, T> >& mat) { 195 | Vector<3, Vector<3, T> > res; 196 | 197 | T m0 = mat[1][1] * mat[2][2] - mat[2][1] * mat[1][2]; 198 | T m1 = mat[0][1] * mat[2][2] - mat[2][1] * mat[0][2]; 199 | T m2 = mat[0][1] * mat[1][2] - mat[1][1] * mat[0][2]; 200 | T det = mat[0][0] * m0 - mat[1][0] * m1 + mat[2][0] * m2; 201 | if (det == (T) 0) return mat; 202 | T inv_det = (T) 1 / det; 203 | 204 | res[0][0] = inv_det * m0; 205 | res[0][1] = inv_det * -m1; 206 | res[0][2] = inv_det * m2; 207 | 208 | res[1][0]= inv_det * (mat[2][0]* mat[1][2]- mat[1][0]* mat[2][2]); 209 | res[1][1]= inv_det * (mat[0][0]* mat[2][2]- mat[2][0]* mat[0][2]); 210 | res[1][2]= inv_det * (mat[1][0]* mat[0][2]- mat[0][0]* mat[1][2]); 211 | 212 | res[2][0]= inv_det * (mat[1][0]* mat[2][1]- mat[2][0]* mat[1][1]); 213 | res[2][1]= inv_det * (mat[2][0]* mat[0][1]- mat[0][0]* mat[2][1]); 214 | res[2][2]= inv_det * (mat[0][0]* mat[1][1]- mat[1][0]* mat[0][1]); 215 | 216 | return res; 217 | } 218 | 219 | // multiplication for 4x4 matrix and 3x1 vector by emdedding w-component as 1 in the vector 220 | // helpful in numerous transformations involving multiplication of transformation matrices with 3d vectors 221 | template 222 | Vector<3, T> multMatrixVec(const Vector<4, Vector<4, T> >& mat, const Vector<3, T>& vec) { 223 | // this function takes into consideration the w coordinate and performs 224 | // homogenous divide in the resulting vector 225 | Vector3f ret; 226 | T a, b, c, w; 227 | a = vec[0] * mat[0][0] + vec[1] * mat[0][1] + vec[2] * mat[0][2] + mat[0][3]; 228 | b = vec[0] * mat[1][0] + vec[1] * mat[1][1] + vec[2] * mat[1][2] + mat[1][3]; 229 | c = vec[0] * mat[2][0] + vec[1] * mat[2][1] + vec[2] * mat[2][2] + mat[2][3]; 230 | w = vec[0] * mat[3][0] + vec[1] * mat[3][1] + vec[2] * mat[3][2] + mat[3][3]; 231 | 232 | ret.x = a/w; 233 | ret.y = b/w; 234 | ret.z = c/w; 235 | 236 | return ret; 237 | } 238 | 239 | template 240 | Vector<3, T> multMatrixDir(const Vector<4, Vector<4, T> >& mat, const Vector<3, T>& vec) { 241 | // this function simply ignores the w dimension i.e. doesn't take into account the translation 242 | 243 | Vector3f ret; 244 | 245 | ret.x = vec[0] * mat[0][0] + vec[1] * mat[0][1] + vec[2] * mat[0][2]; 246 | ret.y = vec[0] * mat[1][0] + vec[1] * mat[1][1] + vec[2] * mat[1][2]; 247 | ret.z = vec[0] * mat[2][0] + vec[1] * mat[2][1] + vec[2] * mat[2][2]; 248 | 249 | return ret; 250 | } 251 | 252 | typedef Vector<3, Vector<3, int> > Matrix3i; 253 | typedef Vector<4, Vector<4, int> > Matrix4i; 254 | typedef Vector<3, Vector<3, float> > Matrix3f; 255 | typedef Vector<4, Vector<4, float> > Matrix4f; 256 | 257 | #endif // MATRIX_H -------------------------------------------------------------------------------- /include/shader.h: -------------------------------------------------------------------------------- 1 | #ifndef SHADER_H 2 | #define SHADER_H 3 | 4 | #include "vector3d.h" 5 | #include "matrix.h" 6 | 7 | /** 8 | * This shader class will change a lot in future, now this is just 9 | * to get a rough idea of how I will integrate shaders in the code. 10 | * */ 11 | 12 | struct IShader { 13 | virtual ~IShader() {} 14 | virtual Vector4f vertex(const Vector3f& vertex, const Vector3f& normal, const Vector3f& texture, 15 | const Vector3f& tangent, const Vector3f& bitangent, int nth) = 0; 16 | virtual Vector3f fragment(const Vector3f& barycentric) = 0; 17 | }; 18 | 19 | // simplest and fastest shader; uses face normals to compute light values. 20 | struct FlatShader : IShader { 21 | Matrix4f MVP; 22 | float varying_intensity; 23 | Vector3f light; 24 | // TODO: Remove light and add it to the scene class. 25 | // I am declaring it here for testing purposes.; 26 | 27 | Vector4f vertex(const Vector3f& vertex, const Vector3f& normal, const Vector3f& texture, 28 | const Vector3f& tangent, const Vector3f& bitangent, int nth) override { 29 | light = Vector3f(0.f, 0.f, 1.f); 30 | varying_intensity = std::max(0.f, dot(normal, light)); 31 | Vector4f gl_Vertex = Vector4f(vertex.x, vertex.y, vertex.z, 1.f); 32 | return MVP * gl_Vertex; 33 | } 34 | 35 | Vector3f fragment(const Vector3f& barycentric) override { 36 | assert(varying_intensity <= 1); 37 | return Vector3f{255 * varying_intensity, 255 * varying_intensity, 255 * varying_intensity}; 38 | } 39 | }; 40 | 41 | // Much better than flat shader, interpolates normal for every point and computes 42 | // light values for every pixel on the surface. Requires more processing that flat shader. 43 | // This implementation uses phong reflection model and gourard shading. 44 | struct GouraudShader : IShader { 45 | Matrix4f MVP, N, V, MV; 46 | Vector3f varIntensity, varSpec, varUV[3], nrm, viewDir; 47 | Vector3f light; 48 | Texture* diffuse_map; 49 | 50 | Vector4f vertex(const Vector3f& vertex, const Vector3f& normal, const Vector3f& texture, 51 | const Vector3f& tangent, const Vector3f& bitangent, int nth) override { 52 | 53 | nrm = normalize(multMatrixDir(N, normal)); // correct normal 54 | light = normalize(Vector3f{1, 1, 1}); 55 | light = normalize(multMatrixDir(V, light)); 56 | viewDir = normalize(multMatrixVec(MV, vertex)); 57 | 58 | Vector3f reflection = normalize(nrm * (dot(light, nrm) * 2.f) - light); 59 | 60 | varUV[nth] = texture; 61 | varSpec[nth] = pow(std::max(dot(normalize(light-viewDir), reflection), 0.0f), 32); 62 | varIntensity[nth] = std::max(0.f, dot(nrm, light)); 63 | 64 | Vector4f gl_Vertex = Vector4f(vertex.x, vertex.y, vertex.z, 1.f); 65 | return MVP * gl_Vertex; 66 | } 67 | 68 | Vector3f fragment(const Vector3f& barycentric) override { 69 | // interpolating attributes 70 | Vector2f uv; 71 | uv.x = varUV[0].x * barycentric[0] + varUV[1].x * barycentric[1] + varUV[2].x * barycentric[2]; 72 | uv.y = varUV[0].y * barycentric[0] + varUV[1].y * barycentric[1] + varUV[2].y * barycentric[2]; 73 | float intensity = dot(varIntensity, barycentric); 74 | float spec = dot(varSpec, barycentric); 75 | 76 | float a = (.1f + intensity + .6f * spec); // multiplier ambient + diffuse + specular 77 | 78 | Vector3i color = diffuse_map->getColor(uv.x * diffuse_map->width, uv.y * diffuse_map->height); 79 | float r = std::min(color.x*a, 255.f); 80 | float g = std::min(color.y*a, 255.f); 81 | float b = std::min(color.z*a, 255.f); 82 | // std::cout << r << " " << g << " " << b << std::endl; 83 | return Vector3f(r, g, b); 84 | } 85 | }; 86 | 87 | // calculate attributes at every fragment as opposed to only in vertex by 88 | // interpolating the normals in fragment shader and then calculating lighting. 89 | // this shader uses Phong Shading and Phong reflection model 90 | struct PhongShader : IShader { 91 | Matrix4f MVP, N, V, MV, M; 92 | Vector3f varNormal[3], varUV[3], varPos[3]; // varying attributes, set by vertex shader, used by fragment shader 93 | Vector3f light; // light pos 94 | 95 | Texture* diffuse_map; // textures to use for mapping 96 | 97 | Vector4f vertex(const Vector3f& vertex, const Vector3f& normal, const Vector3f& texture, 98 | const Vector3f& tangent, const Vector3f& bitangent, int nth) override { 99 | 100 | varNormal[nth] = normalize(multMatrixDir(N, normal)); // correct normal in view space 101 | varUV[nth] = texture; 102 | varPos[nth] = normalize(multMatrixVec(MV, vertex)); 103 | 104 | // setting light position. Later it'll be as an argument in vertex shader. 105 | light = normalize(Vector3f{1, 1, 1}); 106 | light = normalize(multMatrixDir(M, light)); // light in view space 107 | 108 | Vector4f gl_Vertex = Vector4f(vertex.x, vertex.y, vertex.z, 1); 109 | 110 | return MVP * gl_Vertex; 111 | } 112 | 113 | Vector3f fragment(const Vector3f& barycentric) { 114 | Vector3f nrm, uv, viewDir; 115 | // interpolating normal for every fragment 116 | nrm.x = varNormal[0].x * barycentric[0] + varNormal[1].x * barycentric[1] + varNormal[2].x * barycentric[2]; 117 | nrm.y = varNormal[0].y * barycentric[0] + varNormal[1].y * barycentric[1] + varNormal[2].y * barycentric[2]; 118 | nrm.z = varNormal[0].z * barycentric[0] + varNormal[1].z * barycentric[1] + varNormal[2].z * barycentric[2]; 119 | 120 | // interpolating uv coordinates 121 | uv.x = varUV[0].x * barycentric[0] + varUV[1].x * barycentric[1] + varUV[2].x * barycentric[2]; 122 | uv.y = varUV[0].y * barycentric[0] + varUV[1].y * barycentric[1] + varUV[2].y * barycentric[2]; 123 | 124 | // interpolating fragment position 125 | viewDir.x = varPos[0].x * barycentric[0] + varPos[1].x * barycentric[0] + varPos[2].x * barycentric[2]; 126 | viewDir.y = varPos[0].y * barycentric[0] + varPos[1].y * barycentric[0] + varPos[2].y * barycentric[2]; 127 | viewDir.z = varPos[0].z * barycentric[0] + varPos[1].z * barycentric[0] + varPos[2].z * barycentric[2]; 128 | 129 | // setting up the necessary vectors for phong reflection 130 | Vector3f reflection = normalize(nrm * (dot(light, nrm) * 2.f) - light); 131 | viewDir = normalize(viewDir*-1.f); 132 | 133 | // calculating specular and diffuse values 134 | float spec = pow(std::max(dot(viewDir, reflection), 0.f), 32); 135 | float diff = std::max(0.0f, dot(nrm, light)); 136 | 137 | uv.x *= diffuse_map->width; 138 | uv.y *= diffuse_map->height; 139 | Vector3i color = diffuse_map->getColor(uv.x, uv.y); 140 | 141 | // setting colors 142 | float r = std::min(color.x * (.1 + diff + .6*spec), 255); 143 | float g = std::min(color.y * (.1 + diff + .6*spec), 255); 144 | float b = std::min(color.z * (.1 + diff + .6*spec), 255); 145 | 146 | return Vector3f(r, g, b); 147 | } 148 | }; 149 | 150 | struct NormalMap : IShader { 151 | Matrix4f MVP, N, V, MV, M; 152 | Vector3f varNormal[3], varUV[3], varPos[3], varTangent[3], varBitangent[3], varLight[3]; // varying attributes, set by vertex shader, used by fragment shader 153 | Vector3f light; // light pos 154 | Texture *diffuse_map, *normal_map; // textures to use for mapping 155 | Matrix3f TBN; 156 | Vector3f cameraPos; 157 | 158 | Vector4f vertex(const Vector3f& vertex, const Vector3f& normal, const Vector3f& texture, 159 | const Vector3f& tangent, const Vector3f& bitangent, int nth) { 160 | 161 | varNormal[nth] = normalize(multMatrixDir(M, normal)); // correct normal in view space 162 | // varNormal[nth] = normalize(normal); 163 | varUV[nth] = texture; 164 | 165 | varTangent[nth] = normalize(multMatrixDir(M, tangent)); 166 | varBitangent[nth] = normalize(multMatrixDir(M, bitangent)); 167 | TBN.x = varTangent[nth]; 168 | TBN.y = varBitangent[nth]; 169 | TBN.z = varNormal[nth]; 170 | 171 | // setting light position. Later it'll be as an argument in vertex shader. 172 | light = normalize(Vector3f{1, 1, 1}); 173 | light = normalize(multMatrixDir(M, light)); // light in view space 174 | varLight[nth] = TBN * light; 175 | 176 | // varPos[nth] = normalize(multMatrixVec(MV, vertex)); 177 | varPos[nth] = TBN * normalize(multMatrixVec(MV, vertex)); 178 | 179 | Vector4f gl_Vertex = Vector4f(vertex.x, vertex.y, vertex.z, 1); 180 | 181 | return MVP * gl_Vertex; 182 | } 183 | 184 | Vector3f fragment(const Vector3f& barycentric) { 185 | Vector3f uv, viewDir, lightDir; 186 | 187 | // interpolating uv coordinates 188 | uv.x = varUV[0].x * barycentric[0] + varUV[1].x * barycentric[1] + varUV[2].x * barycentric[2]; 189 | uv.y = varUV[0].y * barycentric[0] + varUV[1].y * barycentric[1] + varUV[2].y * barycentric[2]; 190 | 191 | lightDir.x = varLight[0].x * barycentric[0] + varLight[1].x * barycentric[1] + varLight[2].x * barycentric[2]; 192 | lightDir.y = varLight[0].y * barycentric[0] + varLight[1].y * barycentric[1] + varLight[2].y * barycentric[2]; 193 | lightDir.z = varLight[0].z * barycentric[0] + varLight[1].z * barycentric[1] + varLight[2].z * barycentric[2]; 194 | 195 | viewDir.x = varPos[0].x * barycentric[0] + varPos[1].x * barycentric[0] + varPos[2].x * barycentric[2]; 196 | viewDir.y = varPos[0].y * barycentric[0] + varPos[1].y * barycentric[0] + varPos[2].y * barycentric[2]; 197 | viewDir.z = varPos[0].z * barycentric[0] + varPos[1].z * barycentric[0] + varPos[2].z * barycentric[2]; 198 | 199 | Vector3i nc = normal_map->getColor(uv.x * normal_map->width, uv.y * normal_map->height); 200 | Vector3f n; 201 | n.x = (nc.x * 2.0f)/255 - 1.0f; 202 | n.y = (nc.y * 2.0f)/255 - 1.0f; 203 | n.z = (nc.z * 2.0f)/255 - 1.0f; 204 | 205 | float diff = std::max(0.f, dot(n, lightDir)); 206 | 207 | Vector3f reflection = normalize(n * (dot(lightDir, n) * 2.f) - lightDir); 208 | viewDir = normalize(cameraPos + viewDir*-1.f); 209 | 210 | // calculating specular and diffuse values 211 | float spec = pow(std::max(dot(viewDir, reflection), 0.f), 32); 212 | 213 | Vector3i color = diffuse_map->getColor(uv.x * diffuse_map->width, uv.y * diffuse_map->height); 214 | 215 | // setting colors 216 | float r = std::min(color.x * (.1 + diff + .6*spec), 255); 217 | float g = std::min(color.y * (.1 + diff + .6*spec), 255); 218 | float b = std::min(color.z * (.1 + diff + .6*spec), 255); 219 | 220 | return Vector3f(r, g, b); 221 | } 222 | }; 223 | 224 | #endif // SHADER_H 225 | --------------------------------------------------------------------------------