From 706b2dfecff65daeb93de568ee2c2bd87f277860 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sat, 22 Feb 2025 08:17:33 +0100 Subject: [PATCH 01/33] Let us start from here --- .gitpod.yml | 9 --- CMakeLists.txt | 2 +- Dockerfile | 6 -- LICENSE.txt | 13 ---- README.md | 66 ----------------- geometry.h | 187 ------------------------------------------------- main.cpp | 88 +++++------------------ model.cpp | 80 --------------------- model.h | 26 ------- our_gl.cpp | 52 -------------- our_gl.h | 16 ----- 11 files changed, 17 insertions(+), 528 deletions(-) delete mode 100644 .gitpod.yml delete mode 100644 Dockerfile delete mode 100644 LICENSE.txt delete mode 100644 README.md delete mode 100644 geometry.h delete mode 100644 model.cpp delete mode 100644 model.h delete mode 100644 our_gl.cpp delete mode 100644 our_gl.h diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index c6b757a8..00000000 --- a/.gitpod.yml +++ /dev/null @@ -1,9 +0,0 @@ -image: - file: Dockerfile -tasks: -- command: > - cmake -Bbuild && - cmake --build build --parallel && - build/tinyrenderer obj/diablo3_pose/diablo3_pose.obj obj/floor.obj && - convert framebuffer.tga framebuffer.png && - open framebuffer.png diff --git a/CMakeLists.txt b/CMakeLists.txt index c3c3372a..9b7a58cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() find_package(OpenMP COMPONENTS CXX) -set(SOURCES main.cpp model.cpp our_gl.cpp tgaimage.cpp) +set(SOURCES main.cpp tgaimage.cpp) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PRIVATE $<$:OpenMP::OpenMP_CXX>) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index bfd2bfb1..00000000 --- a/Dockerfile +++ /dev/null @@ -1,6 +0,0 @@ -FROM gitpod/workspace-full - -USER root -# add your tools here -RUN apt-get update && apt-get install -y \ - imagemagick diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 95dbe5b4..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,13 +0,0 @@ -Tiny Renderer, https://github.com/ssloy/tinyrenderer -Copyright Dmitry V. Sokolov - -This software is provided 'as-is', without any express or implied warranty. -In no event will the authors be held liable for any damages arising from the use of this software. -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it freely, -subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - diff --git a/README.md b/README.md deleted file mode 100644 index 069b0fe3..00000000 --- a/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# Tiny Renderer or how OpenGL works: software rendering in 500 lines of code - -# Check [the wiki](https://github.com/ssloy/tinyrenderer/wiki) for the detailed lessons. - -## compilation - -```sh -git clone https://github.com/ssloy/tinyrenderer.git && -cd tinyrenderer && -cmake -Bbuild && -cmake --build build -j && -build/tinyrenderer obj/diablo3_pose/diablo3_pose.obj obj/floor.obj -``` -The rendered image is saved to `framebuffer.tga`. - -You can open the project in Gitpod, a free online dev environment for GitHub: -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ssloy/tinyrenderer) - -On open, the editor will compile & run the program as well as open the resulting image in the editor's preview. -Just change the code in the editor and rerun the script (use the terminal's history) to see updated images. - -## The main idea - -**My source code is irrelevant. Read the wiki and implement your own renderer. Only when you suffer through all the tiny details, you will learn what is going on.** - -In [this series of articles](https://github.com/ssloy/tinyrenderer/wiki), I want to show how OpenGL works by writing its clone (a much simplified one). Surprisingly enough, I often meet people who cannot overcome the initial hurdle of learning OpenGL / DirectX. Thus, I have prepared a short series of lectures, after which my students show quite good renderers. - -So, the task is formulated as follows: using no third-party libraries (especially graphic ones), get something like this picture: - -![](https://raw.githubusercontent.com/ssloy/tinyrenderer/gh-pages/img/00-home/africanhead.png) - -_Warning: this is a training material that will loosely repeat the structure of the OpenGL library. It will be a software renderer. **I do not want to show how to write applications for OpenGL. I want to show how OpenGL works.** I am deeply convinced that it is impossible to write efficient applications using 3D libraries without understanding this._ - -I will try to make the final code about 500 lines. My students need 10 to 20 programming hours to begin making such renderers. At the input, we get a test file with a polygonal wire + pictures with textures. At the output, we’ll get a rendered model-no graphical interface, and the program simply generates an image. - - -Since the goal is to minimize external dependencies, I give my students just one class that allows working with [TGA](http://en.wikipedia.org/wiki/Truevision_TGA) files. It’s one of the simplest formats that supports images in RGB/RGBA/black and white formats. So, as a starting point, we’ll obtain a simple way to work with pictures. You should note that the only functionality available at the very beginning (in addition to loading and saving images) is the ability to set one pixel's color. - -There are no functions for drawing line segments and triangles. We’ll have to do all of this by hand. I provide my source code that I write in parallel with students. But I would not recommend using it, as this doesn’t make sense. The entire code is available on GitHub, and [here](https://github.com/ssloy/tinyrenderer/tree/909fe20934ba5334144d2c748805690a1fa4c89f) you will find the source code I give to my students. - -```C++ -#include "tgaimage.h" -const TGAColor white = TGAColor(255, 255, 255, 255); -const TGAColor red = TGAColor(255, 0, 0, 255); -int main(int argc, char** argv) { - TGAImage image(100, 100, TGAImage::RGB); - image.set(52, 41, red); - image.write_tga_file("output.tga");` - return 0; -} -``` - -output.tga should look something like this: - -![](https://raw.githubusercontent.com/ssloy/tinyrenderer/gh-pages/img/00-home/reddot.png) - - -# Teaser: few examples made with the renderer - -![](https://raw.githubusercontent.com/ssloy/tinyrenderer/gh-pages/img/00-home/demon.png) - -![](https://raw.githubusercontent.com/ssloy/tinyrenderer/gh-pages/img/00-home/diablo-glow.png) - -![](https://raw.githubusercontent.com/ssloy/tinyrenderer/gh-pages/img/00-home/boggie.png) - -![](https://raw.githubusercontent.com/ssloy/tinyrenderer/gh-pages/img/00-home/diablo-ssao.png) diff --git a/geometry.h b/geometry.h deleted file mode 100644 index 1195a281..00000000 --- a/geometry.h +++ /dev/null @@ -1,187 +0,0 @@ -#pragma once -#include -#include -#include - -template struct vec { - double data[n] = {0}; - double& operator[](const int i) { assert(i>=0 && i=0 && i double operator*(const vec& lhs, const vec& rhs) { - double ret = 0; // N.B. Do not ever, ever use such for loops! They are highly confusing. - for (int i=n; i--; ret+=lhs[i]*rhs[i]); // Here I used them as a tribute to old-school game programmers fighting for every CPU cycle. - return ret; // Once upon a time reverse loops were faster than the normal ones, it is not the case anymore. -} - -template vec operator+(const vec& lhs, const vec& rhs) { - vec ret = lhs; - for (int i=n; i--; ret[i]+=rhs[i]); - return ret; -} - -template vec operator-(const vec& lhs, const vec& rhs) { - vec ret = lhs; - for (int i=n; i--; ret[i]-=rhs[i]); - return ret; -} - -template vec operator*(const vec& lhs, const double& rhs) { - vec ret = lhs; - for (int i=n; i--; ret[i]*=rhs); - return ret; -} - -template vec operator*(const double& lhs, const vec &rhs) { - return rhs * lhs; -} - -template vec operator/(const vec& lhs, const double& rhs) { - vec ret = lhs; - for (int i=n; i--; ret[i]/=rhs); - return ret; -} - -template std::ostream& operator<<(std::ostream& out, const vec& v) { - for (int i=0; i struct vec<2> { - double x = 0, y = 0; - double& operator[](const int i) { assert(i>=0 && i<2); return i ? y : x; } - double operator[](const int i) const { assert(i>=0 && i<2); return i ? y : x; } -}; - -template<> struct vec<3> { - double x = 0, y = 0, z = 0; - double& operator[](const int i) { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; } - double operator[](const int i) const { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; } -}; - -template<> struct vec<4> { - double x = 0, y = 0, z = 0, w = 0; - double& operator[](const int i) { assert(i>=0 && i<4); return i<2 ? (i ? y : x) : (2==i ? z : w); } - double operator[](const int i) const { assert(i>=0 && i<4); return i<2 ? (i ? y : x) : (2==i ? z : w); } - vec<2> xy() const { return {x, y}; } - vec<3> xyz() const { return {x, y, z}; } -}; - -typedef vec<2> vec2; -typedef vec<3> vec3; -typedef vec<4> vec4; - -template double norm(const vec& v) { - return std::sqrt(v*v); -} - -template vec normalized(const vec& v) { - return v / norm(v); -} - -inline vec3 cross(const vec3 &v1, const vec3 &v2) { - return {v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x}; -} - -template struct dt; - -template struct mat { - vec rows[nrows] = {{}}; - - vec& operator[] (const int idx) { assert(idx>=0 && idx& operator[] (const int idx) const { assert(idx>=0 && idx::det(*this); - } - - double cofactor(const int row, const int col) const { - mat submatrix; - for (int i=nrows-1; i--; ) - for (int j=ncols-1;j--; submatrix[i][j]=rows[i+int(i>=row)][j+int(j>=col)]); - return submatrix.det() * ((row+col)%2 ? -1 : 1); - } - - mat invert_transpose() const { - mat adjugate_transpose; // transpose to ease determinant computation, check the last line - for (int i=nrows; i--; ) - for (int j=ncols; j--; adjugate_transpose[i][j]=cofactor(i,j)); - return adjugate_transpose/(adjugate_transpose[0]*rows[0]); - } - - mat invert() const { - return invert_transpose().transpose(); - } - - mat transpose() const { - mat ret; - for (int i=ncols; i--; ) - for (int j=nrows; j--; ret[i][j]=rows[j][i]); - return ret; - } -}; - -template vec operator*(const vec& lhs, const mat& rhs) { - return (mat<1,nrows>{{lhs}}*rhs)[0]; -} - -template vec operator*(const mat& lhs, const vec& rhs) { - vec ret; - for (int i=nrows; i--; ret[i]=lhs[i]*rhs); - return ret; -} - -templatemat operator*(const mat& lhs, const mat& rhs) { - mat result; - for (int i=R1; i--; ) - for (int j=C2; j--; ) - for (int k=C1; k--; result[i][j]+=lhs[i][k]*rhs[k][j]); - return result; -} - -templatemat operator*(const mat& lhs, const double& val) { - mat result; - for (int i=nrows; i--; result[i] = lhs[i]*val); - return result; -} - -templatemat operator/(const mat& lhs, const double& val) { - mat result; - for (int i=nrows; i--; result[i] = lhs[i]/val); - return result; -} - -templatemat operator+(const mat& lhs, const mat& rhs) { - mat result; - for (int i=nrows; i--; ) - for (int j=ncols; j--; result[i][j]=lhs[i][j]+rhs[i][j]); - return result; -} - -templatemat operator-(const mat& lhs, const mat& rhs) { - mat result; - for (int i=nrows; i--; ) - for (int j=ncols; j--; result[i][j]=lhs[i][j]-rhs[i][j]); - return result; -} - -template std::ostream& operator<<(std::ostream& out, const mat& m) { - for (int i=0; i struct dt { // template metaprogramming to compute the determinant recursively - static double det(const mat& src) { - double ret = 0; - for (int i=n; i--; ret += src[0][i] * src.cofactor(0,i)); - return ret; - } -}; - -template<> struct dt<1> { // template specialization to stop the recursion - static double det(const mat<1,1>& src) { - return src[0][0]; - } -}; - diff --git a/main.cpp b/main.cpp index eda62883..3edda536 100644 --- a/main.cpp +++ b/main.cpp @@ -1,81 +1,25 @@ -#include -#include "model.h" -#include "our_gl.h" +#include +#include "tgaimage.h" -extern mat<4,4> ModelView; // "OpenGL" state matrices -extern mat<4,4> Projection; - -struct Shader : IShader { - const Model &model; - vec3 uniform_l; // light direction in view coordinates - mat<3,2> varying_uv; // triangle uv coordinates, written by the vertex shader, read by the fragment shader - mat<3,3> varying_nrm; // normal per vertex to be interpolated by FS - mat<3,3> view_tri; // triangle in view coordinates - - Shader(const vec3 l, const Model &m) : model(m) { - uniform_l = normalized((ModelView*vec4{l.x, l.y, l.z, 0.}).xyz()); // transform the light vector to view coordinates - } - - virtual void vertex(const int iface, const int nthvert, vec4& gl_Position) { - vec3 n = model.normal(iface, nthvert); - vec3 v = model.vert(iface, nthvert); - gl_Position = ModelView * vec4{v.x, v.y, v.z, 1.}; - varying_uv[nthvert] = model.uv(iface, nthvert); - varying_nrm[nthvert] = (ModelView.invert_transpose() * vec4{n.x, n.y, n.z, 0.}).xyz(); - view_tri[nthvert] = gl_Position.xyz(); - gl_Position = Projection * gl_Position; - } - - virtual bool fragment(const vec3 bar, TGAColor &gl_FragColor) const { - vec3 bn = normalized(bar * varying_nrm); // per-vertex normal interpolation - vec2 uv = bar * varying_uv; // tex coord interpolation - - mat<3,3> AI = mat<3,3>{ {view_tri[1] - view_tri[0], view_tri[2] - view_tri[0], bn} }.invert(); // for the math refer to the tangent space normal mapping lecture - vec3 i = AI * vec3{varying_uv[1].x - varying_uv[0].x, varying_uv[2].x - varying_uv[0].x, 0}; // https://github.com/ssloy/tinyrenderer/wiki/Lesson-6bis-tangent-space-normal-mapping - vec3 j = AI * vec3{varying_uv[1].y - varying_uv[0].y, varying_uv[2].y - varying_uv[0].y, 0}; - mat<3,3> B = mat<3,3>{ { normalized(i), normalized(j), bn } }.transpose(); - - vec3 n = normalized(B * model.normal(uv)); // transform the normal from the texture to the tangent space - vec3 r = normalized(n * (n * uniform_l)*2 - uniform_l); // reflected light direction, specular mapping is described here: https://github.com/ssloy/tinyrenderer/wiki/Lesson-6-Shaders-for-the-software-renderer - double diff = std::max(0., n * uniform_l); // diffuse light intensity - double spec = std::pow(std::max(-r.z, 0.), 5+sample2D(model.specular(), uv)[0]); // specular intensity, note that the camera lies on the z-axis (in view), therefore simple -r.z - - TGAColor c = sample2D(model.diffuse(), uv); - for (int i : {0,1,2}) - gl_FragColor[i] = std::min(10 + c[i]*(diff + spec), 255); // (a bit of ambient light, diff + spec), clamp the result - return false; // do not discard the pixel - } -}; +constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order +constexpr TGAColor green = { 0, 255, 0, 255}; +constexpr TGAColor red = { 0, 0, 255, 255}; +constexpr TGAColor blue = {255, 128, 64, 255}; +constexpr TGAColor yellow = { 0, 200, 255, 255}; int main(int argc, char** argv) { - if (2>argc) { - std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl; - return 1; - } + constexpr int width = 64; + constexpr int height = 64; + TGAImage framebuffer(width, height, TGAImage::RGB); - constexpr int width = 800; // output image size - constexpr int height = 800; - constexpr vec3 light_dir{1,1,1}; // light source - constexpr vec3 eye{1,1,3}; // camera position - constexpr vec3 center{0,0,0}; // camera direction - constexpr vec3 up{0,1,0}; // camera up vector + int ax = 7, ay = 3; + int bx = 12, by = 37; + int cx = 62, cy = 53; - lookat(eye, center, up); // build the ModelView matrix - viewport(width/8, height/8, width*3/4, height*3/4); // build the Viewport matrix - projection(norm(eye-center)); // build the Projection matrix - std::vector zbuffer(width*height, std::numeric_limits::max()); + framebuffer.set(ax, ay, white); + framebuffer.set(bx, by, white); + framebuffer.set(cx, cy, white); - TGAImage framebuffer(width, height, TGAImage::RGB); // the output image - for (int m=1; m -#include "model.h" - -Model::Model(const std::string filename) { - std::ifstream in; - in.open(filename, std::ifstream::in); - if (in.fail()) return; - std::string line; - while (!in.eof()) { - std::getline(in, line); - std::istringstream iss(line.c_str()); - char trash; - if (!line.compare(0, 2, "v ")) { - iss >> trash; - vec3 v; - for (int i : {0,1,2}) iss >> v[i]; - verts.push_back(v); - } else if (!line.compare(0, 3, "vn ")) { - iss >> trash >> trash; - vec3 n; - for (int i : {0,1,2}) iss >> n[i]; - norms.push_back(normalized(n)); - } else if (!line.compare(0, 3, "vt ")) { - iss >> trash >> trash; - vec2 uv; - for (int i : {0,1}) iss >> uv[i]; - tex.push_back({uv.x, 1-uv.y}); - } else if (!line.compare(0, 2, "f ")) { - int f,t,n, cnt = 0; - iss >> trash; - while (iss >> f >> trash >> t >> trash >> n) { - facet_vrt.push_back(--f); - facet_tex.push_back(--t); - facet_nrm.push_back(--n); - cnt++; - } - if (3!=cnt) { - std::cerr << "Error: the obj file is supposed to be triangulated" << std::endl; - return; - } - } - } - std::cerr << "# v# " << nverts() << " f# " << nfaces() << " vt# " << tex.size() << " vn# " << norms.size() << std::endl; - auto load_texture = [&filename](const std::string suffix, TGAImage &img) { - size_t dot = filename.find_last_of("."); - if (dot==std::string::npos) return; - std::string texfile = filename.substr(0,dot) + suffix; - std::cerr << "texture file " << texfile << " loading " << (img.read_tga_file(texfile.c_str()) ? "ok" : "failed") << std::endl; - }; - load_texture("_diffuse.tga", diffusemap ); - load_texture("_nm_tangent.tga", normalmap ); - load_texture("_spec.tga", specularmap); -} - -const TGAImage& Model::diffuse() const { return diffusemap; } -const TGAImage& Model::specular() const { return specularmap; } -int Model::nverts() const { return verts.size(); } -int Model::nfaces() const { return facet_vrt.size()/3; } - -vec3 Model::vert(const int i) const { - return verts[i]; -} - -vec3 Model::vert(const int iface, const int nthvert) const { - return verts[facet_vrt[iface*3+nthvert]]; -} - -vec3 Model::normal(const vec2 &uvf) const { - TGAColor c = normalmap.get(uvf[0]*normalmap.width(), uvf[1]*normalmap.height()); - return vec3{(double)c[2],(double)c[1],(double)c[0]}*2./255. - vec3{1,1,1}; -} - -vec2 Model::uv(const int iface, const int nthvert) const { - return tex[facet_tex[iface*3+nthvert]]; -} - -vec3 Model::normal(const int iface, const int nthvert) const { - return norms[facet_nrm[iface*3+nthvert]]; -} - diff --git a/model.h b/model.h deleted file mode 100644 index ce5786cc..00000000 --- a/model.h +++ /dev/null @@ -1,26 +0,0 @@ -#include "geometry.h" -#include "tgaimage.h" - -class Model { - std::vector verts = {}; // array of vertices ┐ generally speaking, these arrays - std::vector norms = {}; // array of normal vectors │ do not have the same size - std::vector tex = {}; // array of tex coords ┘ check the logs of the Model() constructor - std::vector facet_vrt = {}; // ┐ per-triangle indices in the above arrays, - std::vector facet_nrm = {}; // │ the size is supposed to be - std::vector facet_tex = {}; // ┘ nfaces()*3 - TGAImage diffusemap = {}; // diffuse color texture - TGAImage normalmap = {}; // normal map texture - TGAImage specularmap = {}; // specular texture -public: - Model(const std::string filename); - int nverts() const; // number of vertices - int nfaces() const; // number of triangles - vec3 vert(const int i) const; // 0 <= i < nverts() - vec3 vert(const int iface, const int nthvert) const; // 0 <= iface <= nfaces(), 0 <= nthvert < 3 - vec3 normal(const int iface, const int nthvert) const; // normal coming from the "vn x y z" entries in the .obj file - vec3 normal(const vec2 &uv) const; // normal vector from the normal map texture - vec2 uv(const int iface, const int nthvert) const; // uv coordinates of triangle corners - const TGAImage& diffuse() const; - const TGAImage& specular() const; -}; - diff --git a/our_gl.cpp b/our_gl.cpp deleted file mode 100644 index 0e0c4284..00000000 --- a/our_gl.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "our_gl.h" - -mat<4,4> ModelView; -mat<4,4> Viewport; -mat<4,4> Projection; - -void viewport(const int x, const int y, const int w, const int h) { - Viewport = {{{w/2., 0, 0, x+w/2.}, {0, h/2., 0, y+h/2.}, {0,0,1,0}, {0,0,0,1}}}; -} - -void projection(const double f) { // check https://en.wikipedia.org/wiki/Camera_matrix - Projection = {{{1,0,0,0}, {0,-1,0,0}, {0,0,1,0}, {0,0,-1/f,0}}}; -} - -void lookat(const vec3 eye, const vec3 center, const vec3 up) { // check https://github.com/ssloy/tinyrenderer/wiki/Lesson-5-Moving-the-camera - vec3 z = normalized(center-eye); - vec3 x = normalized(cross(up,z)); - vec3 y = normalized(cross(z, x)); - ModelView = mat<4,4>{{{x.x,x.y,x.z,0}, {y.x,y.y,y.z,0}, {z.x,z.y,z.z,0}, {0,0,0,1}}} * - mat<4,4>{{{1,0,0,-eye.x}, {0,1,0,-eye.y}, {0,0,1,-eye.z}, {0,0,0,1}}}; -} - -vec3 barycentric(const vec2 tri[3], const vec2 P) { - mat<3,3> ABC = {{ {tri[0].x, tri[0].y, 1.}, {tri[1].x, tri[1].y, 1.}, {tri[2].x, tri[2].y, 1.} }}; - if (ABC.det()<1) return {-1,1,1}; // for a degenerate triangle generate negative coordinates, it will be thrown away by the rasterizator - return ABC.invert_transpose() * vec3{P.x, P.y, 1.}; -} - -void rasterize(const vec4 clip_verts[3], const IShader &shader, TGAImage &image, std::vector &zbuffer) { - vec4 pts [3] = { Viewport*clip_verts[0], Viewport*clip_verts[1], Viewport*clip_verts[2] }; // screen coordinates before persp. division - vec2 pts2[3] = { (pts[0]/pts[0].w).xy(), (pts[1]/pts[1].w).xy(), (pts[2]/pts[2].w).xy() }; // screen coordinates after perps. division - - int bbminx = std::max(0, static_cast(std::min(std::min(pts2[0].x, pts2[1].x), pts2[2].x))); // bounding box for the triangle - int bbminy = std::max(0, static_cast(std::min(std::min(pts2[0].y, pts2[1].y), pts2[2].y))); // clipped by the screen - int bbmaxx = std::min(image.width() -1, static_cast(std::max(std::max(pts2[0].x, pts2[1].x), pts2[2].x))); - int bbmaxy = std::min(image.height()-1, static_cast(std::max(std::max(pts2[0].y, pts2[1].y), pts2[2].y))); -#pragma omp parallel for - for (int x=bbminx; x<=bbmaxx; x++) { // rasterize the bounding box - for (int y=bbminy; y<=bbmaxy; y++) { - vec3 bc_screen = barycentric(pts2, {static_cast(x), static_cast(y)}); - vec3 bc_clip = { bc_screen.x/pts[0].w, bc_screen.y/pts[1].w, bc_screen.z/pts[2].w }; // check https://github.com/ssloy/tinyrenderer/wiki/Technical-difficulties-linear-interpolation-with-perspective-deformations - bc_clip = bc_clip / (bc_clip.x + bc_clip.y + bc_clip.z); - double frag_depth = bc_clip * vec3{ clip_verts[0].z, clip_verts[1].z, clip_verts[2].z }; - if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0 || frag_depth > zbuffer[x+y*image.width()]) continue; - TGAColor color; - if (shader.fragment(bc_clip, color)) continue; // fragment shader can discard current fragment - zbuffer[x+y*image.width()] = frag_depth; - image.set(x, y, color); - } - } -} - diff --git a/our_gl.h b/our_gl.h deleted file mode 100644 index 44433433..00000000 --- a/our_gl.h +++ /dev/null @@ -1,16 +0,0 @@ -#include "tgaimage.h" -#include "geometry.h" - -void viewport(const int x, const int y, const int w, const int h); -void projection(const double coeff=0); // coeff = -1/c -void lookat(const vec3 eye, const vec3 center, const vec3 up); - -struct IShader { - static TGAColor sample2D(const TGAImage &img, const vec2 &uvf) { - return img.get(uvf[0] * img.width(), uvf[1] * img.height()); - } - virtual bool fragment(const vec3 bar, TGAColor &color) const = 0; -}; - -void rasterize(const vec4 clip_verts[3], const IShader &shader, TGAImage &image, std::vector &zbuffer); - From dd938ceada38c60a58afd7ec2cdb7dff60ce84d6 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sat, 22 Feb 2025 08:19:26 +0100 Subject: [PATCH 02/33] Bresenham #1 --- main.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/main.cpp b/main.cpp index 3edda536..5a407f20 100644 --- a/main.cpp +++ b/main.cpp @@ -7,6 +7,14 @@ constexpr TGAColor red = { 0, 0, 255, 255}; constexpr TGAColor blue = {255, 128, 64, 255}; constexpr TGAColor yellow = { 0, 200, 255, 255}; +void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { + for (float t=0.; t<1.; t+=.02) { + int x = std::round( ax + (bx-ax)*t ); + int y = std::round( ay + (by-ay)*t ); + framebuffer.set(x, y, color); + } +} + int main(int argc, char** argv) { constexpr int width = 64; constexpr int height = 64; @@ -16,6 +24,11 @@ int main(int argc, char** argv) { int bx = 12, by = 37; int cx = 62, cy = 53; + line(ax, ay, bx, by, framebuffer, blue); + line(cx, cy, bx, by, framebuffer, green); + line(cx, cy, ax, ay, framebuffer, yellow); + line(ax, ay, cx, cy, framebuffer, red); + framebuffer.set(ax, ay, white); framebuffer.set(bx, by, white); framebuffer.set(cx, cy, white); From 1971e01187d32e8936cbfc780a95df6efdaa75a0 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sat, 22 Feb 2025 08:21:42 +0100 Subject: [PATCH 03/33] Bresenham #2a --- main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 5a407f20..27503e97 100644 --- a/main.cpp +++ b/main.cpp @@ -8,8 +8,8 @@ constexpr TGAColor blue = {255, 128, 64, 255}; constexpr TGAColor yellow = { 0, 200, 255, 255}; void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { - for (float t=0.; t<1.; t+=.02) { - int x = std::round( ax + (bx-ax)*t ); + for (int x=ax; x<=bx; x++) { + float t = (x-ax) / static_cast(bx-ax); int y = std::round( ay + (by-ay)*t ); framebuffer.set(x, y, color); } From d7662fbc12d2c5a11f59111fa978412a19ff5f7f Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sat, 22 Feb 2025 08:22:57 +0100 Subject: [PATCH 04/33] Bresenham #2b --- main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main.cpp b/main.cpp index 27503e97..1162181a 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,10 @@ constexpr TGAColor blue = {255, 128, 64, 255}; constexpr TGAColor yellow = { 0, 200, 255, 255}; void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { + if (ax>bx) { // make it left−to−right + std::swap(ax, bx); + std::swap(ay, by); + } for (int x=ax; x<=bx; x++) { float t = (x-ax) / static_cast(bx-ax); int y = std::round( ay + (by-ay)*t ); From 3998cbe5a5d31c7d3ab188ae5c634370644bf977 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sat, 22 Feb 2025 08:31:23 +0100 Subject: [PATCH 05/33] Bresenham #3 --- main.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 1162181a..456cdcf8 100644 --- a/main.cpp +++ b/main.cpp @@ -8,6 +8,11 @@ constexpr TGAColor blue = {255, 128, 64, 255}; constexpr TGAColor yellow = { 0, 200, 255, 255}; void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { + bool steep = std::abs(ax-bx) < std::abs(ay-by); + if (steep) { // if the line is steep, we transpose the image + std::swap(ax, ay); + std::swap(bx, by); + } if (ax>bx) { // make it left−to−right std::swap(ax, bx); std::swap(ay, by); @@ -15,7 +20,10 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) for (int x=ax; x<=bx; x++) { float t = (x-ax) / static_cast(bx-ax); int y = std::round( ay + (by-ay)*t ); - framebuffer.set(x, y, color); + if (steep) // if transposed, de−transpose + framebuffer.set(y, x, color); + else + framebuffer.set(x, y, color); } } From 2054b8c6e0208cc877b6ee1aaa003660e705b656 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 18:49:25 +0100 Subject: [PATCH 06/33] Bresenham #4: first measurement --- main.cpp | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/main.cpp b/main.cpp index 456cdcf8..3f335653 100644 --- a/main.cpp +++ b/main.cpp @@ -1,12 +1,8 @@ #include +#include +#include #include "tgaimage.h" -constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order -constexpr TGAColor green = { 0, 255, 0, 255}; -constexpr TGAColor red = { 0, 0, 255, 255}; -constexpr TGAColor blue = {255, 128, 64, 255}; -constexpr TGAColor yellow = { 0, 200, 255, 255}; - void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { bool steep = std::abs(ax-bx) < std::abs(ay-by); if (steep) { // if the line is steep, we transpose the image @@ -32,18 +28,12 @@ int main(int argc, char** argv) { constexpr int height = 64; TGAImage framebuffer(width, height, TGAImage::RGB); - int ax = 7, ay = 3; - int bx = 12, by = 37; - int cx = 62, cy = 53; - - line(ax, ay, bx, by, framebuffer, blue); - line(cx, cy, bx, by, framebuffer, green); - line(cx, cy, ax, ay, framebuffer, yellow); - line(ax, ay, cx, cy, framebuffer, red); - - framebuffer.set(ax, ay, white); - framebuffer.set(bx, by, white); - framebuffer.set(cx, cy, white); + std::srand(std::time({})); + for (int i=0; i<(1<<24); i++) { + int ax = rand()%width, ay = rand()%height; + int bx = rand()%width, by = rand()%height; + line(ax, ay, bx, by, framebuffer, { rand()%255, rand()%255, rand()%255, rand()%255 }); + } framebuffer.write_tga_file("framebuffer.tga"); return 0; From 5c3a8108403ecc1fe51c1af4a9eefb3ca171b95f Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 18:55:44 +0100 Subject: [PATCH 07/33] Bresenham 4b: second measurement --- main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 3f335653..65103911 100644 --- a/main.cpp +++ b/main.cpp @@ -13,13 +13,13 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) std::swap(ax, bx); std::swap(ay, by); } + float y = ay; for (int x=ax; x<=bx; x++) { - float t = (x-ax) / static_cast(bx-ax); - int y = std::round( ay + (by-ay)*t ); if (steep) // if transposed, de−transpose framebuffer.set(y, x, color); else framebuffer.set(x, y, color); + y += (by-ay) / static_cast(bx-ax); } } From dbd11a905a6cd3947caf551a7bda5dcbd374425c Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 18:59:56 +0100 Subject: [PATCH 08/33] Bresenham 4c: third measurement --- main.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 65103911..b9db62d1 100644 --- a/main.cpp +++ b/main.cpp @@ -13,13 +13,18 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) std::swap(ax, bx); std::swap(ay, by); } - float y = ay; + int y = ay; + float error = 0; for (int x=ax; x<=bx; x++) { if (steep) // if transposed, de−transpose framebuffer.set(y, x, color); else framebuffer.set(x, y, color); - y += (by-ay) / static_cast(bx-ax); + error += std::abs(by-ay)/static_cast(bx-ax); + if (error>.5) { + y += by > ay ? 1 : -1; + error -= 1.; + } } } From 477b3cd686ed16cb2f5b723d51ae4d0ec728fdc5 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 19:03:08 +0100 Subject: [PATCH 09/33] Bresenham 4d: fourth measurement --- main.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.cpp b/main.cpp index b9db62d1..373f60a2 100644 --- a/main.cpp +++ b/main.cpp @@ -14,16 +14,16 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) std::swap(ay, by); } int y = ay; - float error = 0; + int ierror = 0; for (int x=ax; x<=bx; x++) { if (steep) // if transposed, de−transpose framebuffer.set(y, x, color); else framebuffer.set(x, y, color); - error += std::abs(by-ay)/static_cast(bx-ax); - if (error>.5) { + ierror += 2 * std::abs(by-ay); + if (ierror > bx - ax) { y += by > ay ? 1 : -1; - error -= 1.; + ierror -= 2 * (bx-ax); } } } From 5c8360eae3c3fde1264713c8f6a303a2956217f0 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 19:04:38 +0100 Subject: [PATCH 10/33] integer Bresenham --- main.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/main.cpp b/main.cpp index 373f60a2..de170b77 100644 --- a/main.cpp +++ b/main.cpp @@ -1,8 +1,12 @@ #include -#include -#include #include "tgaimage.h" +constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order +constexpr TGAColor green = { 0, 255, 0, 255}; +constexpr TGAColor red = { 0, 0, 255, 255}; +constexpr TGAColor blue = {255, 128, 64, 255}; +constexpr TGAColor yellow = { 0, 200, 255, 255}; + void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { bool steep = std::abs(ax-bx) < std::abs(ay-by); if (steep) { // if the line is steep, we transpose the image @@ -33,12 +37,18 @@ int main(int argc, char** argv) { constexpr int height = 64; TGAImage framebuffer(width, height, TGAImage::RGB); - std::srand(std::time({})); - for (int i=0; i<(1<<24); i++) { - int ax = rand()%width, ay = rand()%height; - int bx = rand()%width, by = rand()%height; - line(ax, ay, bx, by, framebuffer, { rand()%255, rand()%255, rand()%255, rand()%255 }); - } + int ax = 7, ay = 3; + int bx = 12, by = 37; + int cx = 62, cy = 53; + + line(ax, ay, bx, by, framebuffer, blue); + line(cx, cy, bx, by, framebuffer, green); + line(cx, cy, ax, ay, framebuffer, yellow); + line(ax, ay, cx, cy, framebuffer, red); + + framebuffer.set(ax, ay, white); + framebuffer.set(bx, by, white); + framebuffer.set(cx, cy, white); framebuffer.write_tga_file("framebuffer.tga"); return 0; From 6aa88245ab61cfbc2ce51e40cc550a55375d6908 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 19:38:30 +0100 Subject: [PATCH 11/33] Point cloud rendering --- CMakeLists.txt | 2 +- geometry.h | 24 ++++++++++++++++++++++++ main.cpp | 36 ++++++++++++++++++++++-------------- model.cpp | 45 +++++++++++++++++++++++++++++++++++++++++++++ model.h | 14 ++++++++++++++ 5 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 geometry.h create mode 100644 model.cpp create mode 100644 model.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b7a58cf..ec1a8a26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() find_package(OpenMP COMPONENTS CXX) -set(SOURCES main.cpp tgaimage.cpp) +set(SOURCES main.cpp model.cpp tgaimage.cpp) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PRIVATE $<$:OpenMP::OpenMP_CXX>) diff --git a/geometry.h b/geometry.h new file mode 100644 index 00000000..4ad7c846 --- /dev/null +++ b/geometry.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include + +template struct vec { + double data[n] = {0}; + double& operator[](const int i) { assert(i>=0 && i=0 && i std::ostream& operator<<(std::ostream& out, const vec& v) { + for (int i=0; i struct vec<3> { + double x = 0, y = 0, z = 0; + double& operator[](const int i) { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; } + double operator[](const int i) const { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; } +}; + +typedef vec<3> vec3; + diff --git a/main.cpp b/main.cpp index de170b77..89e1ead3 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,12 @@ #include +#include +#include "geometry.h" +#include "model.h" #include "tgaimage.h" +constexpr int width = 800; +constexpr int height = 800; + constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order constexpr TGAColor green = { 0, 255, 0, 255}; constexpr TGAColor red = { 0, 0, 255, 255}; @@ -32,23 +38,25 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) } } -int main(int argc, char** argv) { - constexpr int width = 64; - constexpr int height = 64; - TGAImage framebuffer(width, height, TGAImage::RGB); +std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). + return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, + (v.y + 1.) * height/2 }; // we want to shift the vector (x,y) and then scale it to span the entire screen. +} - int ax = 7, ay = 3; - int bx = 12, by = 37; - int cx = 62, cy = 53; +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl; + return 1; + } - line(ax, ay, bx, by, framebuffer, blue); - line(cx, cy, bx, by, framebuffer, green); - line(cx, cy, ax, ay, framebuffer, yellow); - line(ax, ay, cx, cy, framebuffer, red); + Model model(argv[1]); + TGAImage framebuffer(width, height, TGAImage::RGB); - framebuffer.set(ax, ay, white); - framebuffer.set(bx, by, white); - framebuffer.set(cx, cy, white); + for (int i=0; i +#include +#include "model.h" + +Model::Model(const std::string filename) { + std::ifstream in; + in.open(filename, std::ifstream::in); + if (in.fail()) return; + std::string line; + while (!in.eof()) { + std::getline(in, line); + std::istringstream iss(line.c_str()); + char trash; + if (!line.compare(0, 2, "v ")) { + iss >> trash; + vec3 v; + for (int i : {0,1,2}) iss >> v[i]; + verts.push_back(v); + } else if (!line.compare(0, 2, "f ")) { + int f,t,n, cnt = 0; + iss >> trash; + while (iss >> f >> trash >> t >> trash >> n) { + facet_vrt.push_back(--f); + cnt++; + } + if (3!=cnt) { + std::cerr << "Error: the obj file is supposed to be triangulated" << std::endl; + return; + } + } + } + std::cerr << "# v# " << nverts() << " f# " << nfaces() << std::endl; +} + +int Model::nverts() const { return verts.size(); } +int Model::nfaces() const { return facet_vrt.size()/3; } + +vec3 Model::vert(const int i) const { + return verts[i]; +} + +vec3 Model::vert(const int iface, const int nthvert) const { + return verts[facet_vrt[iface*3+nthvert]]; +} + diff --git a/model.h b/model.h new file mode 100644 index 00000000..e6f25548 --- /dev/null +++ b/model.h @@ -0,0 +1,14 @@ +#include +#include "geometry.h" + +class Model { + std::vector verts = {}; // array of vertices + std::vector facet_vrt = {}; // per-triangle index in the above array +public: + Model(const std::string filename); + int nverts() const; // number of vertices + int nfaces() const; // number of triangles + vec3 vert(const int i) const; // 0 <= i < nverts() + vec3 vert(const int iface, const int nthvert) const; // 0 <= iface <= nfaces(), 0 <= nthvert < 3 +}; + From 1e1d3392e8b4e650dd09b3345ff3d2463039675e Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Sun, 23 Feb 2025 20:15:56 +0100 Subject: [PATCH 12/33] Wireframe rendering --- main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/main.cpp b/main.cpp index 89e1ead3..e3242e35 100644 --- a/main.cpp +++ b/main.cpp @@ -52,6 +52,15 @@ int main(int argc, char** argv) { Model model(argv[1]); TGAImage framebuffer(width, height, TGAImage::RGB); + for (int i=0; i Date: Tue, 25 Feb 2025 21:18:17 +0100 Subject: [PATCH 13/33] Triangle rasterization, the starting point --- main.cpp | 36 +++++++++--------------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/main.cpp b/main.cpp index e3242e35..e798e05a 100644 --- a/main.cpp +++ b/main.cpp @@ -4,8 +4,8 @@ #include "model.h" #include "tgaimage.h" -constexpr int width = 800; -constexpr int height = 800; +constexpr int width = 128; +constexpr int height = 128; constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order constexpr TGAColor green = { 0, 255, 0, 255}; @@ -38,35 +38,17 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) } } -std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). - return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, - (v.y + 1.) * height/2 }; // we want to shift the vector (x,y) and then scale it to span the entire screen. +void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuffer, TGAColor color) { + line(ax, ay, bx, by, framebuffer, color); + line(bx, by, cx, cy, framebuffer, color); + line(cx, cy, ax, ay, framebuffer, color); } int main(int argc, char** argv) { - if (argc != 2) { - std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl; - return 1; - } - - Model model(argv[1]); TGAImage framebuffer(width, height, TGAImage::RGB); - - for (int i=0; i Date: Tue, 25 Feb 2025 21:19:18 +0100 Subject: [PATCH 14/33] Scanline #1 --- main.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index e798e05a..cc67a69f 100644 --- a/main.cpp +++ b/main.cpp @@ -39,9 +39,13 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) } void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuffer, TGAColor color) { - line(ax, ay, bx, by, framebuffer, color); - line(bx, by, cx, cy, framebuffer, color); - line(cx, cy, ax, ay, framebuffer, color); + // sort the vertices, a,b,c in ascending y order (bubblesort yay!) + if (ay>by) { std::swap(ax, bx); std::swap(ay, by); } + if (ay>cy) { std::swap(ax, cx); std::swap(ay, cy); } + if (by>cy) { std::swap(bx, cx); std::swap(by, cy); } + line(ax, ay, bx, by, framebuffer, green); + line(bx, by, cx, cy, framebuffer, green); + line(cx, cy, ax, ay, framebuffer, red); } int main(int argc, char** argv) { From 400b29515ea6dda6efe5103aef10aa8f0e0afc57 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Tue, 25 Feb 2025 21:21:14 +0100 Subject: [PATCH 15/33] Scanline #2 --- main.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index cc67a69f..10f971ef 100644 --- a/main.cpp +++ b/main.cpp @@ -43,9 +43,17 @@ void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuf if (ay>by) { std::swap(ax, bx); std::swap(ay, by); } if (ay>cy) { std::swap(ax, cx); std::swap(ay, cy); } if (by>cy) { std::swap(bx, cx); std::swap(by, cy); } - line(ax, ay, bx, by, framebuffer, green); - line(bx, by, cx, cy, framebuffer, green); - line(cx, cy, ax, ay, framebuffer, red); + int total_height = cy-ay; + + if (ay != by) { // if the bottom half is not degenerate + int segment_height = by - ay; + for (int y=ay; y<=by; y++) { // sweep the horizontal line from ay to by + int x1 = ax + ((cx - ax)*(y - ay)) / total_height; + int x2 = ax + ((bx - ax)*(y - ay)) / segment_height; + framebuffer.set(x1, y, red); + framebuffer.set(x2, y, green); + } + } } int main(int argc, char** argv) { From 9cb66c76e7344208f3fa1b97e74b40b09f0e5313 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Tue, 25 Feb 2025 21:21:58 +0100 Subject: [PATCH 16/33] Scanline #3 --- main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 10f971ef..be73d10a 100644 --- a/main.cpp +++ b/main.cpp @@ -50,8 +50,8 @@ void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuf for (int y=ay; y<=by; y++) { // sweep the horizontal line from ay to by int x1 = ax + ((cx - ax)*(y - ay)) / total_height; int x2 = ax + ((bx - ax)*(y - ay)) / segment_height; - framebuffer.set(x1, y, red); - framebuffer.set(x2, y, green); + for (int x=std::min(x1,x2); x Date: Tue, 25 Feb 2025 21:22:49 +0100 Subject: [PATCH 17/33] Scanline #4 --- main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/main.cpp b/main.cpp index be73d10a..fb38600d 100644 --- a/main.cpp +++ b/main.cpp @@ -54,6 +54,15 @@ void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuf framebuffer.set(x, y, color); } } + if (by != cy) { // if the upper half is not degenerate + int segment_height = cy - by; + for (int y=by; y<=cy; y++) { // sweep the horizontal line from by to cy + int x1 = ax + ((cx - ax)*(y - ay)) / total_height; + int x2 = bx + ((cx - bx)*(y - by)) / segment_height; + for (int x=std::min(x1,x2); x Date: Tue, 25 Feb 2025 21:25:17 +0100 Subject: [PATCH 18/33] Bounding box rasterization #1 --- main.cpp | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/main.cpp b/main.cpp index fb38600d..c335844e 100644 --- a/main.cpp +++ b/main.cpp @@ -39,28 +39,13 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) } void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuffer, TGAColor color) { - // sort the vertices, a,b,c in ascending y order (bubblesort yay!) - if (ay>by) { std::swap(ax, bx); std::swap(ay, by); } - if (ay>cy) { std::swap(ax, cx); std::swap(ay, cy); } - if (by>cy) { std::swap(bx, cx); std::swap(by, cy); } - int total_height = cy-ay; - - if (ay != by) { // if the bottom half is not degenerate - int segment_height = by - ay; - for (int y=ay; y<=by; y++) { // sweep the horizontal line from ay to by - int x1 = ax + ((cx - ax)*(y - ay)) / total_height; - int x2 = ax + ((bx - ax)*(y - ay)) / segment_height; - for (int x=std::min(x1,x2); x Date: Tue, 25 Feb 2025 21:26:43 +0100 Subject: [PATCH 19/33] Bounding box rasterization #2 --- main.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/main.cpp b/main.cpp index c335844e..874cbbce 100644 --- a/main.cpp +++ b/main.cpp @@ -38,13 +38,24 @@ void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) } } +double signed_triangle_area(int ax, int ay, int bx, int by, int cx, int cy) { + return .5*((by-ay)*(bx+ax) + (cy-by)*(cx+bx) + (ay-cy)*(ax+cx)); +} + void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuffer, TGAColor color) { int bbminx = std::min(std::min(ax, bx), cx); // bounding box for the triangle int bbminy = std::min(std::min(ay, by), cy); // defined by its top left and bottom right corners int bbmaxx = std::max(std::max(ax, bx), cx); int bbmaxy = std::max(std::max(ay, by), cy); + double total_area = signed_triangle_area(ax, ay, bx, by, cx, cy); + +#pragma omp parallel for for (int x=bbminx; x<=bbmaxx; x++) { for (int y=bbminy; y<=bbmaxy; y++) { + double alpha = signed_triangle_area(x, y, bx, by, cx, cy) / total_area; + double beta = signed_triangle_area(x, y, cx, cy, ax, ay) / total_area; + double gamma = signed_triangle_area(x, y, ax, ay, bx, by) / total_area; + if (alpha<0 || beta<0 || gamma<0) continue; // negative barycentric coordinate => the pixel is outside the triangle framebuffer.set(x, y, color); } } From e25bb49be00a43ab777e511baa4b72e8ab16d6b1 Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Tue, 25 Feb 2025 21:29:15 +0100 Subject: [PATCH 20/33] Model rendering, no back-face culling --- main.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/main.cpp b/main.cpp index 874cbbce..2bc31ea4 100644 --- a/main.cpp +++ b/main.cpp @@ -4,8 +4,8 @@ #include "model.h" #include "tgaimage.h" -constexpr int width = 128; -constexpr int height = 128; +constexpr int width = 800; +constexpr int height = 800; constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order constexpr TGAColor green = { 0, 255, 0, 255}; @@ -61,11 +61,29 @@ void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuf } } +std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). + return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, + (v.y + 1.) * height/2 }; // we want to shift the vector (x,y) and then scale it to span the entire screen. +} + int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl; + return 1; + } + + Model model(argv[1]); TGAImage framebuffer(width, height, TGAImage::RGB); - triangle( 7, 45, 35, 100, 45, 60, framebuffer, red); - triangle(120, 35, 90, 5, 45, 110, framebuffer, white); - triangle(115, 83, 80, 90, 85, 120, framebuffer, green); + + for (int i=0; i Date: Tue, 25 Feb 2025 21:29:55 +0100 Subject: [PATCH 21/33] Model rendering with back-face culling --- main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/main.cpp b/main.cpp index 2bc31ea4..bf70f84e 100644 --- a/main.cpp +++ b/main.cpp @@ -48,6 +48,7 @@ void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuf int bbmaxx = std::max(std::max(ax, bx), cx); int bbmaxy = std::max(std::max(ay, by), cy); double total_area = signed_triangle_area(ax, ay, bx, by, cx, cy); + if (total_area<1) return; // backface culling + discarding triangles that cover less than a pixel #pragma omp parallel for for (int x=bbminx; x<=bbmaxx; x++) { From 52c7f8898e1a71c412f89240d83e4b354a3e76bc Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Tue, 25 Feb 2025 21:41:54 +0100 Subject: [PATCH 22/33] Painter's algorithm --- model.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/model.cpp b/model.cpp index bb99e6d8..0814718b 100644 --- a/model.cpp +++ b/model.cpp @@ -1,6 +1,7 @@ #include #include #include "model.h" +#include Model::Model(const std::string filename) { std::ifstream in; @@ -30,6 +31,28 @@ Model::Model(const std::string filename) { } } std::cerr << "# v# " << nverts() << " f# " << nfaces() << std::endl; + + std::vector idx(nfaces()); // permutation, a map from new to old facet indices + for (int i = 0 ; i facet_vrt2(nfaces()*3); // allocate an array to store permutated facets + for (int i=0; i Date: Thu, 10 Apr 2025 13:38:47 +0200 Subject: [PATCH 23/33] depth interpolation --- main.cpp | 52 +++++++++++++--------------------------------------- model.cpp | 22 ---------------------- 2 files changed, 13 insertions(+), 61 deletions(-) diff --git a/main.cpp b/main.cpp index bf70f84e..aa487952 100644 --- a/main.cpp +++ b/main.cpp @@ -7,42 +7,11 @@ constexpr int width = 800; constexpr int height = 800; -constexpr TGAColor white = {255, 255, 255, 255}; // attention, BGRA order -constexpr TGAColor green = { 0, 255, 0, 255}; -constexpr TGAColor red = { 0, 0, 255, 255}; -constexpr TGAColor blue = {255, 128, 64, 255}; -constexpr TGAColor yellow = { 0, 200, 255, 255}; - -void line(int ax, int ay, int bx, int by, TGAImage &framebuffer, TGAColor color) { - bool steep = std::abs(ax-bx) < std::abs(ay-by); - if (steep) { // if the line is steep, we transpose the image - std::swap(ax, ay); - std::swap(bx, by); - } - if (ax>bx) { // make it left−to−right - std::swap(ax, bx); - std::swap(ay, by); - } - int y = ay; - int ierror = 0; - for (int x=ax; x<=bx; x++) { - if (steep) // if transposed, de−transpose - framebuffer.set(y, x, color); - else - framebuffer.set(x, y, color); - ierror += 2 * std::abs(by-ay); - if (ierror > bx - ax) { - y += by > ay ? 1 : -1; - ierror -= 2 * (bx-ax); - } - } -} - double signed_triangle_area(int ax, int ay, int bx, int by, int cx, int cy) { return .5*((by-ay)*(bx+ax) + (cy-by)*(cx+bx) + (ay-cy)*(ax+cx)); } -void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuffer, TGAColor color) { +void triangle(int ax, int ay, int az, int bx, int by, int bz, int cx, int cy, int cz, TGAImage &zbuffer, TGAImage &framebuffer, TGAColor color) { int bbminx = std::min(std::min(ax, bx), cx); // bounding box for the triangle int bbminy = std::min(std::min(ay, by), cy); // defined by its top left and bottom right corners int bbmaxx = std::max(std::max(ax, bx), cx); @@ -57,14 +26,17 @@ void triangle(int ax, int ay, int bx, int by, int cx, int cy, TGAImage &framebuf double beta = signed_triangle_area(x, y, cx, cy, ax, ay) / total_area; double gamma = signed_triangle_area(x, y, ax, ay, bx, by) / total_area; if (alpha<0 || beta<0 || gamma<0) continue; // negative barycentric coordinate => the pixel is outside the triangle + unsigned char z = static_cast(alpha * az + beta * bz + gamma * cz); + zbuffer.set(x, y, {z}); framebuffer.set(x, y, color); } } } -std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). - return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, - (v.y + 1.) * height/2 }; // we want to shift the vector (x,y) and then scale it to span the entire screen. +std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). + return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, + (v.y + 1.) * height/2, // we want to shift the vector (x,y) and then scale it to span the entire screen. + (v.z + 1.) * 255./2 }; } int main(int argc, char** argv) { @@ -75,17 +47,19 @@ int main(int argc, char** argv) { Model model(argv[1]); TGAImage framebuffer(width, height, TGAImage::RGB); + TGAImage zbuffer(width, height, TGAImage::GRAYSCALE); for (int i=0; i idx(nfaces()); // permutation, a map from new to old facet indices - for (int i = 0 ; i facet_vrt2(nfaces()*3); // allocate an array to store permutated facets - for (int i=0; i Date: Thu, 10 Apr 2025 13:39:18 +0200 Subject: [PATCH 24/33] z-buffer hidden faces removal --- main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/main.cpp b/main.cpp index aa487952..9c0f3b0a 100644 --- a/main.cpp +++ b/main.cpp @@ -27,6 +27,7 @@ void triangle(int ax, int ay, int az, int bx, int by, int bz, int cx, int cy, in double gamma = signed_triangle_area(x, y, ax, ay, bx, by) / total_area; if (alpha<0 || beta<0 || gamma<0) continue; // negative barycentric coordinate => the pixel is outside the triangle unsigned char z = static_cast(alpha * az + beta * bz + gamma * cz); + if (z <= zbuffer.get(x, y)[0]) continue; zbuffer.set(x, y, {z}); framebuffer.set(x, y, color); } From a61a783499c3fb84e2aaa7e05a645a6c3017818e Mon Sep 17 00:00:00 2001 From: Dmitry Sokolov Date: Tue, 15 Apr 2025 15:25:01 +0200 Subject: [PATCH 25/33] vectors/matrices --- geometry.h | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/geometry.h b/geometry.h index 4ad7c846..1195a281 100644 --- a/geometry.h +++ b/geometry.h @@ -9,16 +9,179 @@ template struct vec { double operator[](const int i) const { assert(i>=0 && i double operator*(const vec& lhs, const vec& rhs) { + double ret = 0; // N.B. Do not ever, ever use such for loops! They are highly confusing. + for (int i=n; i--; ret+=lhs[i]*rhs[i]); // Here I used them as a tribute to old-school game programmers fighting for every CPU cycle. + return ret; // Once upon a time reverse loops were faster than the normal ones, it is not the case anymore. +} + +template vec operator+(const vec& lhs, const vec& rhs) { + vec ret = lhs; + for (int i=n; i--; ret[i]+=rhs[i]); + return ret; +} + +template vec operator-(const vec& lhs, const vec& rhs) { + vec ret = lhs; + for (int i=n; i--; ret[i]-=rhs[i]); + return ret; +} + +template vec operator*(const vec& lhs, const double& rhs) { + vec ret = lhs; + for (int i=n; i--; ret[i]*=rhs); + return ret; +} + +template vec operator*(const double& lhs, const vec &rhs) { + return rhs * lhs; +} + +template vec operator/(const vec& lhs, const double& rhs) { + vec ret = lhs; + for (int i=n; i--; ret[i]/=rhs); + return ret; +} + template std::ostream& operator<<(std::ostream& out, const vec& v) { for (int i=0; i struct vec<2> { + double x = 0, y = 0; + double& operator[](const int i) { assert(i>=0 && i<2); return i ? y : x; } + double operator[](const int i) const { assert(i>=0 && i<2); return i ? y : x; } +}; + template<> struct vec<3> { double x = 0, y = 0, z = 0; double& operator[](const int i) { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; } double operator[](const int i) const { assert(i>=0 && i<3); return i ? (1==i ? y : z) : x; } }; +template<> struct vec<4> { + double x = 0, y = 0, z = 0, w = 0; + double& operator[](const int i) { assert(i>=0 && i<4); return i<2 ? (i ? y : x) : (2==i ? z : w); } + double operator[](const int i) const { assert(i>=0 && i<4); return i<2 ? (i ? y : x) : (2==i ? z : w); } + vec<2> xy() const { return {x, y}; } + vec<3> xyz() const { return {x, y, z}; } +}; + +typedef vec<2> vec2; typedef vec<3> vec3; +typedef vec<4> vec4; + +template double norm(const vec& v) { + return std::sqrt(v*v); +} + +template vec normalized(const vec& v) { + return v / norm(v); +} + +inline vec3 cross(const vec3 &v1, const vec3 &v2) { + return {v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x}; +} + +template struct dt; + +template struct mat { + vec rows[nrows] = {{}}; + + vec& operator[] (const int idx) { assert(idx>=0 && idx& operator[] (const int idx) const { assert(idx>=0 && idx::det(*this); + } + + double cofactor(const int row, const int col) const { + mat submatrix; + for (int i=nrows-1; i--; ) + for (int j=ncols-1;j--; submatrix[i][j]=rows[i+int(i>=row)][j+int(j>=col)]); + return submatrix.det() * ((row+col)%2 ? -1 : 1); + } + + mat invert_transpose() const { + mat adjugate_transpose; // transpose to ease determinant computation, check the last line + for (int i=nrows; i--; ) + for (int j=ncols; j--; adjugate_transpose[i][j]=cofactor(i,j)); + return adjugate_transpose/(adjugate_transpose[0]*rows[0]); + } + + mat invert() const { + return invert_transpose().transpose(); + } + + mat transpose() const { + mat ret; + for (int i=ncols; i--; ) + for (int j=nrows; j--; ret[i][j]=rows[j][i]); + return ret; + } +}; + +template vec operator*(const vec& lhs, const mat& rhs) { + return (mat<1,nrows>{{lhs}}*rhs)[0]; +} + +template vec operator*(const mat& lhs, const vec& rhs) { + vec ret; + for (int i=nrows; i--; ret[i]=lhs[i]*rhs); + return ret; +} + +templatemat operator*(const mat& lhs, const mat& rhs) { + mat result; + for (int i=R1; i--; ) + for (int j=C2; j--; ) + for (int k=C1; k--; result[i][j]+=lhs[i][k]*rhs[k][j]); + return result; +} + +templatemat operator*(const mat& lhs, const double& val) { + mat result; + for (int i=nrows; i--; result[i] = lhs[i]*val); + return result; +} + +templatemat operator/(const mat& lhs, const double& val) { + mat result; + for (int i=nrows; i--; result[i] = lhs[i]/val); + return result; +} + +templatemat operator+(const mat& lhs, const mat& rhs) { + mat result; + for (int i=nrows; i--; ) + for (int j=ncols; j--; result[i][j]=lhs[i][j]+rhs[i][j]); + return result; +} + +templatemat operator-(const mat& lhs, const mat& rhs) { + mat result; + for (int i=nrows; i--; ) + for (int j=ncols; j--; result[i][j]=lhs[i][j]-rhs[i][j]); + return result; +} + +template std::ostream& operator<<(std::ostream& out, const mat& m) { + for (int i=0; i struct dt { // template metaprogramming to compute the determinant recursively + static double det(const mat& src) { + double ret = 0; + for (int i=n; i--; ret += src[0][i] * src.cofactor(0,i)); + return ret; + } +}; + +template<> struct dt<1> { // template specialization to stop the recursion + static double det(const mat<1,1>& src) { + return src[0][0]; + } +}; From 57760190f9f19005afe7031967c4e21dab3b9fd8 Mon Sep 17 00:00:00 2001 From: Dmitry Sokolov Date: Tue, 22 Apr 2025 13:18:54 +0200 Subject: [PATCH 26/33] rotate the camera --- main.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/main.cpp b/main.cpp index 9c0f3b0a..9e61baef 100644 --- a/main.cpp +++ b/main.cpp @@ -12,10 +12,10 @@ double signed_triangle_area(int ax, int ay, int bx, int by, int cx, int cy) { } void triangle(int ax, int ay, int az, int bx, int by, int bz, int cx, int cy, int cz, TGAImage &zbuffer, TGAImage &framebuffer, TGAColor color) { - int bbminx = std::min(std::min(ax, bx), cx); // bounding box for the triangle - int bbminy = std::min(std::min(ay, by), cy); // defined by its top left and bottom right corners - int bbmaxx = std::max(std::max(ax, bx), cx); - int bbmaxy = std::max(std::max(ay, by), cy); + int bbminx = std::max(0, std::min(std::min(ax, bx), cx)); // bounding box for the triangle clipped by the screen + int bbminy = std::max(0, std::min(std::min(ay, by), cy)); // defined by its top left and bottom right corners + int bbmaxx = std::min(framebuffer.width() -1, std::max(std::max(ax, bx), cx)); + int bbmaxy = std::min(framebuffer.height()-1, std::max(std::max(ay, by), cy)); double total_area = signed_triangle_area(ax, ay, bx, by, cx, cy); if (total_area<1) return; // backface culling + discarding triangles that cover less than a pixel @@ -34,6 +34,12 @@ void triangle(int ax, int ay, int az, int bx, int by, int bz, int cx, int cy, in } } +vec3 rot(vec3 v) { + constexpr double a = M_PI/6; + constexpr mat<3,3> Ry = {{{std::cos(a), 0, std::sin(a)}, {0,1,0}, {-std::sin(a), 0, std::cos(a)}}}; + return Ry*v; +} + std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, (v.y + 1.) * height/2, // we want to shift the vector (x,y) and then scale it to span the entire screen. @@ -51,9 +57,9 @@ int main(int argc, char** argv) { TGAImage zbuffer(width, height, TGAImage::GRAYSCALE); for (int i=0; i Date: Tue, 22 Apr 2025 13:19:15 +0200 Subject: [PATCH 27/33] central projection --- main.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 9e61baef..63cd32ac 100644 --- a/main.cpp +++ b/main.cpp @@ -40,6 +40,11 @@ vec3 rot(vec3 v) { return Ry*v; } +vec3 persp(vec3 v) { + constexpr double c = 3.; + return v / (1-v.z/c); +} + std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, (v.y + 1.) * height/2, // we want to shift the vector (x,y) and then scale it to span the entire screen. @@ -57,9 +62,9 @@ int main(int argc, char** argv) { TGAImage zbuffer(width, height, TGAImage::GRAYSCALE); for (int i=0; i Date: Tue, 22 Apr 2025 16:39:05 +0200 Subject: [PATCH 28/33] float point z-buffer --- main.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/main.cpp b/main.cpp index 63cd32ac..dee756fc 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,6 @@ #include #include +#include #include "geometry.h" #include "model.h" #include "tgaimage.h" @@ -11,7 +12,7 @@ double signed_triangle_area(int ax, int ay, int bx, int by, int cx, int cy) { return .5*((by-ay)*(bx+ax) + (cy-by)*(cx+bx) + (ay-cy)*(ax+cx)); } -void triangle(int ax, int ay, int az, int bx, int by, int bz, int cx, int cy, int cz, TGAImage &zbuffer, TGAImage &framebuffer, TGAColor color) { +void triangle(int ax, int ay, double az, int bx, int by, double bz, int cx, int cy, double cz, std::vector &zbuffer, TGAImage &framebuffer, TGAColor color) { int bbminx = std::max(0, std::min(std::min(ax, bx), cx)); // bounding box for the triangle clipped by the screen int bbminy = std::max(0, std::min(std::min(ay, by), cy)); // defined by its top left and bottom right corners int bbmaxx = std::min(framebuffer.width() -1, std::max(std::max(ax, bx), cx)); @@ -26,9 +27,9 @@ void triangle(int ax, int ay, int az, int bx, int by, int bz, int cx, int cy, in double beta = signed_triangle_area(x, y, cx, cy, ax, ay) / total_area; double gamma = signed_triangle_area(x, y, ax, ay, bx, by) / total_area; if (alpha<0 || beta<0 || gamma<0) continue; // negative barycentric coordinate => the pixel is outside the triangle - unsigned char z = static_cast(alpha * az + beta * bz + gamma * cz); - if (z <= zbuffer.get(x, y)[0]) continue; - zbuffer.set(x, y, {z}); + double z = alpha * az + beta * bz + gamma * cz; + if (z <= zbuffer[x+y*width]) continue; + zbuffer[x+y*width] = z; framebuffer.set(x, y, color); } } @@ -45,10 +46,10 @@ vec3 persp(vec3 v) { return v / (1-v.z/c); } -std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). - return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, - (v.y + 1.) * height/2, // we want to shift the vector (x,y) and then scale it to span the entire screen. - (v.z + 1.) * 255./2 }; +std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). + return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, + (v.y + 1.) * height/2, // we want to shift the vector (x,y) and then scale it to span the entire screen. + v.z }; } int main(int argc, char** argv) { @@ -59,7 +60,7 @@ int main(int argc, char** argv) { Model model(argv[1]); TGAImage framebuffer(width, height, TGAImage::RGB); - TGAImage zbuffer(width, height, TGAImage::GRAYSCALE); + std::vector zbuffer(width*height, -std::numeric_limits::max()); for (int i=0; i Date: Tue, 29 Apr 2025 14:31:26 +0200 Subject: [PATCH 29/33] camera handling --- main.cpp | 102 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/main.cpp b/main.cpp index dee756fc..e160e2d7 100644 --- a/main.cpp +++ b/main.cpp @@ -1,74 +1,80 @@ -#include -#include #include +#include #include "geometry.h" #include "model.h" #include "tgaimage.h" -constexpr int width = 800; -constexpr int height = 800; +mat<4,4> ModelView, Viewport, Projection; -double signed_triangle_area(int ax, int ay, int bx, int by, int cx, int cy) { - return .5*((by-ay)*(bx+ax) + (cy-by)*(cx+bx) + (ay-cy)*(ax+cx)); +void lookat(const vec3 eye, const vec3 center, const vec3 up) { + vec3 k = normalized(eye-center); + vec3 i = normalized(cross(up,k)); + vec3 j = normalized(cross(k, i)); + ModelView = mat<4,4>{{{i.x,i.y,i.z,0}, {j.x,j.y,j.z,0}, {k.x,k.y,k.z,0}, {0,0,0,1}}} * + mat<4,4>{{{1,0,0,-center.x}, {0,1,0,-center.y}, {0,0,1,-center.z}, {0,0,0,1}}}; } -void triangle(int ax, int ay, double az, int bx, int by, double bz, int cx, int cy, double cz, std::vector &zbuffer, TGAImage &framebuffer, TGAColor color) { - int bbminx = std::max(0, std::min(std::min(ax, bx), cx)); // bounding box for the triangle clipped by the screen - int bbminy = std::max(0, std::min(std::min(ay, by), cy)); // defined by its top left and bottom right corners - int bbmaxx = std::min(framebuffer.width() -1, std::max(std::max(ax, bx), cx)); - int bbmaxy = std::min(framebuffer.height()-1, std::max(std::max(ay, by), cy)); - double total_area = signed_triangle_area(ax, ay, bx, by, cx, cy); - if (total_area<1) return; // backface culling + discarding triangles that cover less than a pixel - -#pragma omp parallel for - for (int x=bbminx; x<=bbmaxx; x++) { - for (int y=bbminy; y<=bbmaxy; y++) { - double alpha = signed_triangle_area(x, y, bx, by, cx, cy) / total_area; - double beta = signed_triangle_area(x, y, cx, cy, ax, ay) / total_area; - double gamma = signed_triangle_area(x, y, ax, ay, bx, by) / total_area; - if (alpha<0 || beta<0 || gamma<0) continue; // negative barycentric coordinate => the pixel is outside the triangle - double z = alpha * az + beta * bz + gamma * cz; - if (z <= zbuffer[x+y*width]) continue; - zbuffer[x+y*width] = z; - framebuffer.set(x, y, color); - } - } +void projection(const double f) { + Projection = {{{1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0, -1/f,1}}}; } -vec3 rot(vec3 v) { - constexpr double a = M_PI/6; - constexpr mat<3,3> Ry = {{{std::cos(a), 0, std::sin(a)}, {0,1,0}, {-std::sin(a), 0, std::cos(a)}}}; - return Ry*v; +void viewport(const int x, const int y, const int w, const int h) { + Viewport = {{{w/2., 0, 0, x+w/2.}, {0, h/2., 0, y+h/2.}, {0,0,1,0}, {0,0,0,1}}}; } -vec3 persp(vec3 v) { - constexpr double c = 3.; - return v / (1-v.z/c); -} +void rasterize(const vec4 clip[3], std::vector &zbuffer, TGAImage &framebuffer, const TGAColor color) { + vec4 ndc[3] = { clip[0]/clip[0].w, clip[1]/clip[1].w, clip[2]/clip[2].w }; // normalized device coordinates + vec2 screen[3] = { (Viewport*ndc[0]).xy(), (Viewport*ndc[1]).xy(), (Viewport*ndc[2]).xy() }; // screen coordinates + + mat<3,3> ABC = {{ {screen[0].x, screen[0].y, 1.}, {screen[1].x, screen[1].y, 1.}, {screen[2].x, screen[2].y, 1.} }}; + if (ABC.det()<1) return; // backface culling + discarding triangles that cover less than a pixel -std::tuple project(vec3 v) { // First of all, (x,y) is an orthogonal projection of the vector (x,y,z). - return { (v.x + 1.) * width/2, // Second, since the input models are scaled to have fit in the [-1,1]^3 world coordinates, - (v.y + 1.) * height/2, // we want to shift the vector (x,y) and then scale it to span the entire screen. - v.z }; + auto [bbminx,bbmaxx] = std::minmax({screen[0].x, screen[1].x, screen[2].x}); // bounding box for the triangle + auto [bbminy,bbmaxy] = std::minmax({screen[0].y, screen[1].y, screen[2].y}); // defined by its top left and bottom right corners +#pragma omp parallel for + for (int x=std::max(bbminx, 0); x<=std::min(bbmaxx, framebuffer.width()-1); x++) { // clip the bounding box by the screen + for (int y=std::max(bbminy, 0); y<=std::min(bbmaxy, framebuffer.height()-1); y++) { + vec3 bc = ABC.invert_transpose() * vec3{static_cast(x), static_cast(y), 1.}; // barycentric coordinates of {x,y} w.r.t the triangle + if (bc.x<0 || bc.y<0 || bc.z<0) continue; // negative barycentric coordinate => the pixel is outside the triangle + double z = bc * vec3{ ndc[0].z, ndc[1].z, ndc[2].z }; + if (z <= zbuffer[x+y*framebuffer.width()]) continue; + zbuffer[x+y*framebuffer.width()] = z; + framebuffer.set(x, y, color); + } + } } int main(int argc, char** argv) { - if (argc != 2) { + if (argc < 2) { std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl; return 1; } - Model model(argv[1]); + constexpr int width = 800; // output image size + constexpr int height = 800; + constexpr vec3 eye{-1,0,2}; // camera position + constexpr vec3 center{0,0,0}; // camera direction + constexpr vec3 up{0,1,0}; // camera up vector + + lookat(eye, center, up); // build the ModelView matrix + projection(norm(eye-center)); // build the Projection matrix + viewport(width/16, height/16, width*7/8, height*7/8); // build the Viewport matrix + TGAImage framebuffer(width, height, TGAImage::RGB); std::vector zbuffer(width*height, -std::numeric_limits::max()); - for (int i=0; i Date: Wed, 28 May 2025 10:29:17 +0200 Subject: [PATCH 30/33] better camera handling --- main.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/main.cpp b/main.cpp index e160e2d7..8c2861db 100644 --- a/main.cpp +++ b/main.cpp @@ -4,18 +4,18 @@ #include "model.h" #include "tgaimage.h" -mat<4,4> ModelView, Viewport, Projection; +mat<4,4> ModelView, Viewport, Perspective; void lookat(const vec3 eye, const vec3 center, const vec3 up) { - vec3 k = normalized(eye-center); - vec3 i = normalized(cross(up,k)); - vec3 j = normalized(cross(k, i)); - ModelView = mat<4,4>{{{i.x,i.y,i.z,0}, {j.x,j.y,j.z,0}, {k.x,k.y,k.z,0}, {0,0,0,1}}} * + vec3 n = normalized(eye-center); + vec3 l = normalized(cross(up,n)); + vec3 m = normalized(cross(n, l)); + ModelView = mat<4,4>{{{l.x,l.y,l.z,0}, {m.x,m.y,m.z,0}, {n.x,n.y,n.z,0}, {0,0,0,1}}} * mat<4,4>{{{1,0,0,-center.x}, {0,1,0,-center.y}, {0,0,1,-center.z}, {0,0,0,1}}}; } -void projection(const double f) { - Projection = {{{1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0, -1/f,1}}}; +void perspective(const double f) { + Perspective = {{{1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0, -1/f,1}}}; } void viewport(const int x, const int y, const int w, const int h) { @@ -56,9 +56,9 @@ int main(int argc, char** argv) { constexpr vec3 center{0,0,0}; // camera direction constexpr vec3 up{0,1,0}; // camera up vector - lookat(eye, center, up); // build the ModelView matrix - projection(norm(eye-center)); // build the Projection matrix - viewport(width/16, height/16, width*7/8, height*7/8); // build the Viewport matrix + lookat(eye, center, up); // build the ModelView matrix + perspective(norm(eye-center)); // build the Perspective matrix + viewport(width/16, height/16, width*7/8, height*7/8); // build the Viewport matrix TGAImage framebuffer(width, height, TGAImage::RGB); std::vector zbuffer(width*height, -std::numeric_limits::max()); @@ -69,7 +69,7 @@ int main(int argc, char** argv) { vec4 clip[3]; for (int d : {0,1,2}) { // assemble the primitive vec3 v = model.vert(i, d); - clip[d] = Projection * ModelView * vec4{v.x, v.y, v.z, 1.}; + clip[d] = Perspective * ModelView * vec4{v.x, v.y, v.z, 1.}; } TGAColor rnd; for (int c=0; c<3; c++) rnd[c] = std::rand()%255; From 430e6b3adb41345c28f54204b47b383b31b2609b Mon Sep 17 00:00:00 2001 From: "Dmitry V. Sokolov" Date: Wed, 28 May 2025 13:10:55 +0200 Subject: [PATCH 31/33] first shader --- CMakeLists.txt | 2 +- graphics.cpp | 45 ++++++++++++++++++++++++++++ graphics.h | 16 ++++++++++ main.cpp | 81 ++++++++++++++++++++------------------------------ tgaimage.h | 1 + 5 files changed, 96 insertions(+), 49 deletions(-) create mode 100644 graphics.cpp create mode 100644 graphics.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ec1a8a26..f4a0d117 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ endif() find_package(OpenMP COMPONENTS CXX) -set(SOURCES main.cpp model.cpp tgaimage.cpp) +set(SOURCES main.cpp graphics.cpp model.cpp tgaimage.cpp) add_executable(${PROJECT_NAME} ${SOURCES}) target_link_libraries(${PROJECT_NAME} PRIVATE $<$:OpenMP::OpenMP_CXX>) diff --git a/graphics.cpp b/graphics.cpp new file mode 100644 index 00000000..7876810b --- /dev/null +++ b/graphics.cpp @@ -0,0 +1,45 @@ +#include +#include "graphics.h" + +mat<4,4> ModelView, Viewport, Perspective; + +void lookat(const vec3 eye, const vec3 center, const vec3 up) { + vec3 n = normalized(eye-center); + vec3 l = normalized(cross(up,n)); + vec3 m = normalized(cross(n, l)); + ModelView = mat<4,4>{{{l.x,l.y,l.z,0}, {m.x,m.y,m.z,0}, {n.x,n.y,n.z,0}, {0,0,0,1}}} * + mat<4,4>{{{1,0,0,-center.x}, {0,1,0,-center.y}, {0,0,1,-center.z}, {0,0,0,1}}}; +} + +void perspective(const double f) { + Perspective = {{{1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0, -1/f,1}}}; +} + +void viewport(const int x, const int y, const int w, const int h) { + Viewport = {{{w/2., 0, 0, x+w/2.}, {0, h/2., 0, y+h/2.}, {0,0,1,0}, {0,0,0,1}}}; +} + +void rasterize(const vec4 clip[3], const IShader &shader, std::vector &zbuffer, TGAImage &framebuffer) { + vec4 ndc[3] = { clip[0]/clip[0].w, clip[1]/clip[1].w, clip[2]/clip[2].w }; // normalized device coordinates + vec2 screen[3] = { (Viewport*ndc[0]).xy(), (Viewport*ndc[1]).xy(), (Viewport*ndc[2]).xy() }; // screen coordinates + + mat<3,3> ABC = {{ {screen[0].x, screen[0].y, 1.}, {screen[1].x, screen[1].y, 1.}, {screen[2].x, screen[2].y, 1.} }}; + if (ABC.det()<1) return; // backface culling + discarding triangles that cover less than a pixel + + auto [bbminx,bbmaxx] = std::minmax({screen[0].x, screen[1].x, screen[2].x}); // bounding box for the triangle + auto [bbminy,bbmaxy] = std::minmax({screen[0].y, screen[1].y, screen[2].y}); // defined by its top left and bottom right corners +#pragma omp parallel for + for (int x=std::max(bbminx, 0); x<=std::min(bbmaxx, framebuffer.width()-1); x++) { // clip the bounding box by the screen + for (int y=std::max(bbminy, 0); y<=std::min(bbmaxy, framebuffer.height()-1); y++) { + vec3 bc = ABC.invert_transpose() * vec3{static_cast(x), static_cast(y), 1.}; // barycentric coordinates of {x,y} w.r.t the triangle + if (bc.x<0 || bc.y<0 || bc.z<0) continue; // negative barycentric coordinate => the pixel is outside the triangle + double z = bc * vec3{ ndc[0].z, ndc[1].z, ndc[2].z }; + if (z <= zbuffer[x+y*framebuffer.width()]) continue; + auto [discard, color] = shader.fragment(bc); + if (discard) continue; // fragment shader can discard current fragment + zbuffer[x+y*framebuffer.width()] = z; + framebuffer.set(x, y, color); + } + } +} + diff --git a/graphics.h b/graphics.h new file mode 100644 index 00000000..d4251803 --- /dev/null +++ b/graphics.h @@ -0,0 +1,16 @@ +#include "tgaimage.h" +#include "geometry.h" + +void lookat(const vec3 eye, const vec3 center, const vec3 up); +void perspective(const double f); +void viewport(const int x, const int y, const int w, const int h); + +struct IShader { + static TGAColor sample2D(const TGAImage &img, const vec2 &uvf) { + return img.get(uvf[0] * img.width(), uvf[1] * img.height()); + } + virtual std::pair fragment(const vec3 bar) const = 0; +}; + +void rasterize(const vec4 clip[3], const IShader &shader, std::vector &zbuffer, TGAImage &framebuffer); + diff --git a/main.cpp b/main.cpp index 8c2861db..31fab8a8 100644 --- a/main.cpp +++ b/main.cpp @@ -1,48 +1,34 @@ #include -#include -#include "geometry.h" +#include "graphics.h" #include "model.h" -#include "tgaimage.h" -mat<4,4> ModelView, Viewport, Perspective; +extern mat<4,4> ModelView, Perspective; // "OpenGL" state matrices -void lookat(const vec3 eye, const vec3 center, const vec3 up) { - vec3 n = normalized(eye-center); - vec3 l = normalized(cross(up,n)); - vec3 m = normalized(cross(n, l)); - ModelView = mat<4,4>{{{l.x,l.y,l.z,0}, {m.x,m.y,m.z,0}, {n.x,n.y,n.z,0}, {0,0,0,1}}} * - mat<4,4>{{{1,0,0,-center.x}, {0,1,0,-center.y}, {0,0,1,-center.z}, {0,0,0,1}}}; -} - -void perspective(const double f) { - Perspective = {{{1,0,0,0}, {0,1,0,0}, {0,0,1,0}, {0,0, -1/f,1}}}; -} - -void viewport(const int x, const int y, const int w, const int h) { - Viewport = {{{w/2., 0, 0, x+w/2.}, {0, h/2., 0, y+h/2.}, {0,0,1,0}, {0,0,0,1}}}; -} +struct RandomShader : IShader { + const Model &model; + vec3 uniform_l; // light direction in clip coordinates + TGAColor varying_color[3]; -void rasterize(const vec4 clip[3], std::vector &zbuffer, TGAImage &framebuffer, const TGAColor color) { - vec4 ndc[3] = { clip[0]/clip[0].w, clip[1]/clip[1].w, clip[2]/clip[2].w }; // normalized device coordinates - vec2 screen[3] = { (Viewport*ndc[0]).xy(), (Viewport*ndc[1]).xy(), (Viewport*ndc[2]).xy() }; // screen coordinates + RandomShader(const vec3 l, const Model &m) : model(m) { + uniform_l = normalized((ModelView*vec4{l.x, l.y, l.z, 0.}).xyz()); // transform the light vector to view coordinates + } - mat<3,3> ABC = {{ {screen[0].x, screen[0].y, 1.}, {screen[1].x, screen[1].y, 1.}, {screen[2].x, screen[2].y, 1.} }}; - if (ABC.det()<1) return; // backface culling + discarding triangles that cover less than a pixel + virtual vec4 vertex(const int face, const int vert) { + for (int c=0; c<3; c++) + varying_color[vert][c] = std::rand()%255; + vec3 v = model.vert(face, vert); + vec4 gl_Position = ModelView * vec4{v.x, v.y, v.z, 1.}; + return Perspective * gl_Position; + } - auto [bbminx,bbmaxx] = std::minmax({screen[0].x, screen[1].x, screen[2].x}); // bounding box for the triangle - auto [bbminy,bbmaxy] = std::minmax({screen[0].y, screen[1].y, screen[2].y}); // defined by its top left and bottom right corners -#pragma omp parallel for - for (int x=std::max(bbminx, 0); x<=std::min(bbmaxx, framebuffer.width()-1); x++) { // clip the bounding box by the screen - for (int y=std::max(bbminy, 0); y<=std::min(bbmaxy, framebuffer.height()-1); y++) { - vec3 bc = ABC.invert_transpose() * vec3{static_cast(x), static_cast(y), 1.}; // barycentric coordinates of {x,y} w.r.t the triangle - if (bc.x<0 || bc.y<0 || bc.z<0) continue; // negative barycentric coordinate => the pixel is outside the triangle - double z = bc * vec3{ ndc[0].z, ndc[1].z, ndc[2].z }; - if (z <= zbuffer[x+y*framebuffer.width()]) continue; - zbuffer[x+y*framebuffer.width()] = z; - framebuffer.set(x, y, color); - } + virtual std::pair fragment(const vec3 bar) const { + TGAColor gl_FragColor; + for (int c : {0,1,2}) + for (int v : {0,1,2}) + gl_FragColor[c] += bar[v] * varying_color[v][c]; + return {false, gl_FragColor}; // do not discard the pixel } -} +}; int main(int argc, char** argv) { if (argc < 2) { @@ -50,11 +36,12 @@ int main(int argc, char** argv) { return 1; } - constexpr int width = 800; // output image size + constexpr int width = 800; // output image size constexpr int height = 800; - constexpr vec3 eye{-1,0,2}; // camera position - constexpr vec3 center{0,0,0}; // camera direction - constexpr vec3 up{0,1,0}; // camera up vector + constexpr vec3 light_dir{1,1,1}; // light source + constexpr vec3 eye{-1,0,2}; // camera position + constexpr vec3 center{0,0,0}; // camera direction + constexpr vec3 up{0,1,0}; // camera up vector lookat(eye, center, up); // build the ModelView matrix perspective(norm(eye-center)); // build the Perspective matrix @@ -65,15 +52,13 @@ int main(int argc, char** argv) { for (int m=1; m Date: Wed, 4 Jun 2025 09:53:48 +0200 Subject: [PATCH 32/33] flat shader --- main.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/main.cpp b/main.cpp index 31fab8a8..7b70a8b9 100644 --- a/main.cpp +++ b/main.cpp @@ -4,28 +4,30 @@ extern mat<4,4> ModelView, Perspective; // "OpenGL" state matrices -struct RandomShader : IShader { +struct FlatShader : IShader { const Model &model; vec3 uniform_l; // light direction in clip coordinates - TGAColor varying_color[3]; + vec3 tri_eye[3]; - RandomShader(const vec3 l, const Model &m) : model(m) { + FlatShader(const vec3 l, const Model &m) : model(m) { uniform_l = normalized((ModelView*vec4{l.x, l.y, l.z, 0.}).xyz()); // transform the light vector to view coordinates } virtual vec4 vertex(const int face, const int vert) { - for (int c=0; c<3; c++) - varying_color[vert][c] = std::rand()%255; - vec3 v = model.vert(face, vert); + vec3 v = model.vert(face, vert); // current vertex in object coordinates vec4 gl_Position = ModelView * vec4{v.x, v.y, v.z, 1.}; - return Perspective * gl_Position; + tri_eye[vert] = gl_Position.xyz(); // in eye coordinates + return Perspective * gl_Position; // in clip coordinates } virtual std::pair fragment(const vec3 bar) const { TGAColor gl_FragColor; - for (int c : {0,1,2}) - for (int v : {0,1,2}) - gl_FragColor[c] += bar[v] * varying_color[v][c]; + + vec3 n = normalized(cross(tri_eye[1]-tri_eye[0], tri_eye[2]-tri_eye[0])); // triangle normal in eye coordinates + double diff = std::max(0., n * uniform_l); // diffuse light intensity + + for (int i : {0,1,2}) + gl_FragColor[i] = std::min(30 + 255*diff, 255); // a bit of ambient light + diffuse light return {false, gl_FragColor}; // do not discard the pixel } }; @@ -53,7 +55,7 @@ int main(int argc, char** argv) { for (int m=1; m Date: Wed, 4 Jun 2025 13:50:49 +0200 Subject: [PATCH 33/33] flat shader --- main.cpp | 13 ++++++++----- model.cpp | 1 - 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/main.cpp b/main.cpp index 7b70a8b9..015b361a 100644 --- a/main.cpp +++ b/main.cpp @@ -21,13 +21,16 @@ struct FlatShader : IShader { } virtual std::pair fragment(const vec3 bar) const { - TGAColor gl_FragColor; + TGAColor gl_FragColor; - vec3 n = normalized(cross(tri_eye[1]-tri_eye[0], tri_eye[2]-tri_eye[0])); // triangle normal in eye coordinates - double diff = std::max(0., n * uniform_l); // diffuse light intensity + vec3 n = normalized(cross(tri_eye[1]-tri_eye[0], tri_eye[2]-tri_eye[0])); // triangle normal in eye coordinates + vec3 r = normalized(n * (n * uniform_l)*2 - uniform_l); // reflected light direction - for (int i : {0,1,2}) - gl_FragColor[i] = std::min(30 + 255*diff, 255); // a bit of ambient light + diffuse light + double diff = std::max(0., n * uniform_l); // diffuse light intensity + double spec = std::pow(std::max(r.z, 0.), 35); // specular intensity, note that the camera lies on the z-axis (in eye coordinates), therefore simple r.z + + for (int i : {0,1,2}) + gl_FragColor[i] = std::min(30 + 255*(diff + spec), 255); // a bit of ambient light + diffuse light return {false, gl_FragColor}; // do not discard the pixel } }; diff --git a/model.cpp b/model.cpp index 57da22fe..bb99e6d8 100644 --- a/model.cpp +++ b/model.cpp @@ -1,7 +1,6 @@ #include #include #include "model.h" -#include Model::Model(const std::string filename) { std::ifstream in;