diff --git a/examples/TinyRenderer/LICENSE.txt b/examples/TinyRenderer/LICENSE.txt new file mode 100644 index 000000000..95dbe5b4e --- /dev/null +++ b/examples/TinyRenderer/LICENSE.txt @@ -0,0 +1,13 @@ +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/examples/TinyRenderer/Makefile b/examples/TinyRenderer/Makefile new file mode 100644 index 000000000..0b0a469b0 --- /dev/null +++ b/examples/TinyRenderer/Makefile @@ -0,0 +1,23 @@ +SYSCONF_LINK = g++ +CPPFLAGS = -Wall -Wextra -Weffc++ -pedantic -std=c++98 +LDFLAGS = -O3 +LIBS = -lm + +DESTDIR = ./ +TARGET = main + +OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) + +all: $(DESTDIR)$(TARGET) + +$(DESTDIR)$(TARGET): $(OBJECTS) + $(SYSCONF_LINK) -Wall $(LDFLAGS) -o $(DESTDIR)$(TARGET) $(OBJECTS) $(LIBS) + +$(OBJECTS): %.o: %.cpp + $(SYSCONF_LINK) -Wall $(CPPFLAGS) -c $(CFLAGS) $< -o $@ + +clean: + -rm -f $(OBJECTS) + -rm -f $(TARGET) + -rm -f *.tga + diff --git a/examples/TinyRenderer/README.md b/examples/TinyRenderer/README.md new file mode 100644 index 000000000..84a125234 --- /dev/null +++ b/examples/TinyRenderer/README.md @@ -0,0 +1,55 @@ +# Tiny Renderer or how OpenGL works: software renderer in 500 lines of code + +*** + +**Check [the wiki](https://github.com/ssloy/tinyrenderer/wiki/Lesson-1:-Bresenham%E2%80%99s-Line-Drawing-Algorithm) for the detailed lessons. 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.** + +**I do want to get emails for feedback (dmitry.sokolov@univ-lorraine.fr); do not hesitate to contact me if you have any questions.** + +**If you are a teacher and willing to adopt this material for teaching your class your are very welcome, no authorization is needed, simply inform me by mail, it will help me to improve the course.** + +*** + +In this series of articles, I want to show the way 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: + +![](http://haqr.eu/framebuffer.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, 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 capability to set the color of one pixel. + +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.flip_vertically(); // i want to have the origin at the left bottom corner of the image + image.write_tga_file("output.tga");` + return 0; +} +``` + +output.tga should look something like this: + +![](http://www.loria.fr/~sokolovd/cg-course/img/2d3b12170b.png) + + + +# Teaser: few examples made with the renderer + +![](https://hsto.org/getpro/habr/post_images/50d/e2a/be9/50de2abe990efa345664f98c9464a4c8.png) + +![](https://hsto.org/getpro/habr/post_images/e3c/d70/492/e3cd704925f52b5466ab3c4f9fbab899.png) + +![](http://www.loria.fr/~sokolovd/cg-course/06-shaders/img/boggie.png) + +![](http://hsto.org/files/1ba/93f/a5a/1ba93fa5a48646e2a9614271c943b4da.png) diff --git a/examples/TinyRenderer/geometry.cpp b/examples/TinyRenderer/geometry.cpp new file mode 100644 index 000000000..3b6b2f251 --- /dev/null +++ b/examples/TinyRenderer/geometry.cpp @@ -0,0 +1,7 @@ +#include "geometry.h" + +template <> template <> vec<3,int> ::vec(const vec<3,float> &v) : x(int(v.x+.5f)),y(int(v.y+.5f)),z(int(v.z+.5f)) {} +template <> template <> vec<3,float>::vec(const vec<3,int> &v) : x(v.x),y(v.y),z(v.z) {} +template <> template <> vec<2,int> ::vec(const vec<2,float> &v) : x(int(v.x+.5f)),y(int(v.y+.5f)) {} +template <> template <> vec<2,float>::vec(const vec<2,int> &v) : x(v.x),y(v.y) {} + diff --git a/examples/TinyRenderer/geometry.h b/examples/TinyRenderer/geometry.h new file mode 100644 index 000000000..46ee7b354 --- /dev/null +++ b/examples/TinyRenderer/geometry.h @@ -0,0 +1,221 @@ +#ifndef __GEOMETRY_H__ +#define __GEOMETRY_H__ +#include +#include +#include +#include + +template class mat; + +template struct vec { + vec() { for (size_t i=DIM; i--; data_[i] = T()); } + T& operator[](const size_t i) { assert(i struct vec<2,T> { + vec() : x(T()), y(T()) {} + vec(T X, T Y) : x(X), y(Y) {} + template vec<2,T>(const vec<2,U> &v); + T& operator[](const size_t i) { assert(i<2); return i<=0 ? x : y; } + const T& operator[](const size_t i) const { assert(i<2); return i<=0 ? x : y; } + + T x,y; +}; + +///////////////////////////////////////////////////////////////////////////////// + +template struct vec<3,T> { + vec() : x(T()), y(T()), z(T()) {} + vec(T X, T Y, T Z) : x(X), y(Y), z(Z) {} + template vec<3,T>(const vec<3,U> &v); + T& operator[](const size_t i) { assert(i<3); return i<=0 ? x : (1==i ? y : z); } + const T& operator[](const size_t i) const { assert(i<3); return i<=0 ? x : (1==i ? y : z); } + float norm() { return std::sqrt(x*x+y*y+z*z); } + vec<3,T> & normalize(T l=1) { *this = (*this)*(l/norm()); return *this; } + + T x,y,z; +}; + +///////////////////////////////////////////////////////////////////////////////// + +template T operator*(const vec& lhs, const vec& rhs) { + T ret = T(); + for (size_t i=DIM; i--; ret+=lhs[i]*rhs[i]); + return ret; +} + + +templatevec operator+(vec lhs, const vec& rhs) { + for (size_t i=DIM; i--; lhs[i]+=rhs[i]); + return lhs; +} + +templatevec operator-(vec lhs, const vec& rhs) { + for (size_t i=DIM; i--; lhs[i]-=rhs[i]); + return lhs; +} + +template vec operator*(vec lhs, const U& rhs) { + for (size_t i=DIM; i--; lhs[i]*=rhs); + return lhs; +} + +template vec operator/(vec lhs, const U& rhs) { + for (size_t i=DIM; i--; lhs[i]/=rhs); + return lhs; +} + +template vec embed(const vec &v, T fill=1) { + vec ret; + for (size_t i=LEN; i--; ret[i]=(i vec proj(const vec &v) { + vec ret; + for (size_t i=LEN; i--; ret[i]=v[i]); + return ret; +} + +template vec<3,T> cross(vec<3,T> v1, vec<3,T> v2) { + return vec<3,T>(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 std::ostream& operator<<(std::ostream& out, vec& v) { + for(unsigned int i=0; i struct dt { + static T det(const mat& src) { + T ret=0; + for (size_t i=DIM; i--; ret += src[0][i]*src.cofactor(0,i)); + return ret; + } +}; + +template struct dt<1,T> { + static T det(const mat<1,1,T>& src) { + return src[0][0]; + } +}; + +///////////////////////////////////////////////////////////////////////////////// + +template class mat { + vec rows[DimRows]; +public: + mat() {} + + vec& operator[] (const size_t idx) { + assert(idx& operator[] (const size_t idx) const { + assert(idx col(const size_t idx) const { + assert(idx ret; + for (size_t i=DimRows; i--; ret[i]=rows[i][idx]); + return ret; + } + + void set_col(size_t idx, vec v) { + assert(idx identity() { + mat ret; + for (size_t i=DimRows; i--; ) + for (size_t j=DimCols;j--; ret[i][j]=(i==j)); + return ret; + } + + T det() const { + return dt::det(*this); + } + + mat get_minor(size_t row, size_t col) const { + mat ret; + for (size_t i=DimRows-1; i--; ) + for (size_t j=DimCols-1;j--; ret[i][j]=rows[i adjugate() const { + mat ret; + for (size_t i=DimRows; i--; ) + for (size_t j=DimCols; j--; ret[i][j]=cofactor(i,j)); + return ret; + } + + mat invert_transpose() { + mat ret = adjugate(); + T tmp = ret[0]*rows[0]; + return ret/tmp; + } + + mat invert() { + return invert_transpose().transpose(); + } + + mat transpose() { + mat ret; + for (size_t i=DimCols; i--; ret[i]=this->col(i)); + return ret; + } +}; + +///////////////////////////////////////////////////////////////////////////////// + +template vec operator*(const mat& lhs, const vec& rhs) { + vec ret; + for (size_t i=DimRows; i--; ret[i]=lhs[i]*rhs); + return ret; +} + +templatemat operator*(const mat& lhs, const mat& rhs) { + mat result; + for (size_t i=R1; i--; ) + for (size_t j=C2; j--; result[i][j]=lhs[i]*rhs.col(j)); + return result; +} + +templatemat operator/(mat lhs, const T& rhs) { + for (size_t i=DimRows; i--; lhs[i]=lhs[i]/rhs); + return lhs; +} + +template std::ostream& operator<<(std::ostream& out, mat& m) { + for (size_t i=0; i Vec2f; +typedef vec<2, int> Vec2i; +typedef vec<3, float> Vec3f; +typedef vec<3, int> Vec3i; +typedef vec<4, float> Vec4f; +typedef mat<4,4,float> Matrix; +#endif //__GEOMETRY_H__ + diff --git a/examples/TinyRenderer/main.cpp b/examples/TinyRenderer/main.cpp new file mode 100644 index 000000000..8ce61dda2 --- /dev/null +++ b/examples/TinyRenderer/main.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include "tgaimage.h" +#include "model.h" +#include "geometry.h" +#include "our_gl.h" + +Model *model = NULL; + +const int width = 800; +const int height = 800; + +Vec3f light_dir(1,1,1); +Vec3f eye(1,1,3); +Vec3f center(0,0,0); +Vec3f up(0,1,0); + +struct Shader : public IShader { + mat<2,3,float> varying_uv; // triangle uv coordinates, written by the vertex shader, read by the fragment shader + mat<4,3,float> varying_tri; // triangle coordinates (clip coordinates), written by VS, read by FS + mat<3,3,float> varying_nrm; // normal per vertex to be interpolated by FS + mat<3,3,float> ndc_tri; // triangle in normalized device coordinates + + virtual Vec4f vertex(int iface, int nthvert) { + varying_uv.set_col(nthvert, model->uv(iface, nthvert)); + varying_nrm.set_col(nthvert, proj<3>((Projection*ModelView).invert_transpose()*embed<4>(model->normal(iface, nthvert), 0.f))); + Vec4f gl_Vertex = Projection*ModelView*embed<4>(model->vert(iface, nthvert)); + varying_tri.set_col(nthvert, gl_Vertex); + ndc_tri.set_col(nthvert, proj<3>(gl_Vertex/gl_Vertex[3])); + return gl_Vertex; + } + + virtual bool fragment(Vec3f bar, TGAColor &color) { + Vec3f bn = (varying_nrm*bar).normalize(); + Vec2f uv = varying_uv*bar; + + mat<3,3,float> A; + A[0] = ndc_tri.col(1) - ndc_tri.col(0); + A[1] = ndc_tri.col(2) - ndc_tri.col(0); + A[2] = bn; + + mat<3,3,float> AI = A.invert(); + + Vec3f i = AI * Vec3f(varying_uv[0][1] - varying_uv[0][0], varying_uv[0][2] - varying_uv[0][0], 0); + Vec3f j = AI * Vec3f(varying_uv[1][1] - varying_uv[1][0], varying_uv[1][2] - varying_uv[1][0], 0); + + mat<3,3,float> B; + B.set_col(0, i.normalize()); + B.set_col(1, j.normalize()); + B.set_col(2, bn); + + Vec3f n = (B*model->normal(uv)).normalize(); + + float diff = std::max(0.f, n*light_dir); + color = model->diffuse(uv)*diff; + + return false; + } +}; + +int main(int argc, char** argv) { + if (2>argc) { + std::cerr << "Usage: " << argv[0] << " obj/model.obj" << std::endl; + return 1; + } + + float *zbuffer = new float[width*height]; + for (int i=width*height; i--; zbuffer[i] = -std::numeric_limits::max()); + + TGAImage frame(width, height, TGAImage::RGB); + lookat(eye, center, up); + viewport(width/8, height/8, width*3/4, height*3/4); + projection(-1.f/(eye-center).norm()); + light_dir = proj<3>((Projection*ModelView*embed<4>(light_dir, 0.f))).normalize(); + + for (int m=1; mnfaces(); i++) { + for (int j=0; j<3; j++) { + shader.vertex(i, j); + } + triangle(shader.varying_tri, shader, frame, zbuffer); + } + delete model; + } + frame.flip_vertically(); // to place the origin in the bottom left corner of the image + frame.write_tga_file("framebuffer.tga"); + + delete [] zbuffer; + return 0; +} + diff --git a/examples/TinyRenderer/model.cpp b/examples/TinyRenderer/model.cpp new file mode 100644 index 000000000..bc7983dd5 --- /dev/null +++ b/examples/TinyRenderer/model.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include "model.h" + +Model::Model(const char *filename) : verts_(), faces_(), norms_(), uv_(), diffusemap_(), normalmap_(), specularmap_() { + 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; + Vec3f v; + for (int i=0;i<3;i++) iss >> v[i]; + verts_.push_back(v); + } else if (!line.compare(0, 3, "vn ")) { + iss >> trash >> trash; + Vec3f n; + for (int i=0;i<3;i++) iss >> n[i]; + norms_.push_back(n); + } else if (!line.compare(0, 3, "vt ")) { + iss >> trash >> trash; + Vec2f uv; + for (int i=0;i<2;i++) iss >> uv[i]; + uv_.push_back(uv); + } else if (!line.compare(0, 2, "f ")) { + std::vector f; + Vec3i tmp; + iss >> trash; + while (iss >> tmp[0] >> trash >> tmp[1] >> trash >> tmp[2]) { + for (int i=0; i<3; i++) tmp[i]--; // in wavefront obj all indices start at 1, not zero + f.push_back(tmp); + } + faces_.push_back(f); + } + } + std::cerr << "# v# " << verts_.size() << " f# " << faces_.size() << " vt# " << uv_.size() << " vn# " << norms_.size() << std::endl; + load_texture(filename, "_diffuse.tga", diffusemap_); + load_texture(filename, "_nm_tangent.tga", normalmap_); + load_texture(filename, "_spec.tga", specularmap_); +} + +Model::~Model() {} + +int Model::nverts() { + return (int)verts_.size(); +} + +int Model::nfaces() { + return (int)faces_.size(); +} + +std::vector Model::face(int idx) { + std::vector face; + for (int i=0; i<(int)faces_[idx].size(); i++) face.push_back(faces_[idx][i][0]); + return face; +} + +Vec3f Model::vert(int i) { + return verts_[i]; +} + +Vec3f Model::vert(int iface, int nthvert) { + return verts_[faces_[iface][nthvert][0]]; +} + +void Model::load_texture(std::string filename, const char *suffix, TGAImage &img) { + std::string texfile(filename); + size_t dot = texfile.find_last_of("."); + if (dot!=std::string::npos) { + texfile = texfile.substr(0,dot) + std::string(suffix); + std::cerr << "texture file " << texfile << " loading " << (img.read_tga_file(texfile.c_str()) ? "ok" : "failed") << std::endl; + img.flip_vertically(); + } +} + +TGAColor Model::diffuse(Vec2f uvf) { + Vec2i uv(uvf[0]*diffusemap_.get_width(), uvf[1]*diffusemap_.get_height()); + return diffusemap_.get(uv[0], uv[1]); +} + +Vec3f Model::normal(Vec2f uvf) { + Vec2i uv(uvf[0]*normalmap_.get_width(), uvf[1]*normalmap_.get_height()); + TGAColor c = normalmap_.get(uv[0], uv[1]); + Vec3f res; + for (int i=0; i<3; i++) + res[2-i] = (float)c[i]/255.f*2.f - 1.f; + return res; +} + +Vec2f Model::uv(int iface, int nthvert) { + return uv_[faces_[iface][nthvert][1]]; +} + +float Model::specular(Vec2f uvf) { + Vec2i uv(uvf[0]*specularmap_.get_width(), uvf[1]*specularmap_.get_height()); + return specularmap_.get(uv[0], uv[1])[0]/1.f; +} + +Vec3f Model::normal(int iface, int nthvert) { + int idx = faces_[iface][nthvert][2]; + return norms_[idx].normalize(); +} + diff --git a/examples/TinyRenderer/model.h b/examples/TinyRenderer/model.h new file mode 100644 index 000000000..035cc119a --- /dev/null +++ b/examples/TinyRenderer/model.h @@ -0,0 +1,33 @@ +#ifndef __MODEL_H__ +#define __MODEL_H__ +#include +#include +#include "geometry.h" +#include "tgaimage.h" + +class Model { +private: + std::vector verts_; + std::vector > faces_; // attention, this Vec3i means vertex/uv/normal + std::vector norms_; + std::vector uv_; + TGAImage diffusemap_; + TGAImage normalmap_; + TGAImage specularmap_; + void load_texture(std::string filename, const char *suffix, TGAImage &img); +public: + Model(const char *filename); + ~Model(); + int nverts(); + int nfaces(); + Vec3f normal(int iface, int nthvert); + Vec3f normal(Vec2f uv); + Vec3f vert(int i); + Vec3f vert(int iface, int nthvert); + Vec2f uv(int iface, int nthvert); + TGAColor diffuse(Vec2f uv); + float specular(Vec2f uv); + std::vector face(int idx); +}; +#endif //__MODEL_H__ + diff --git a/examples/TinyRenderer/our_gl.cpp b/examples/TinyRenderer/our_gl.cpp new file mode 100644 index 000000000..3772c984a --- /dev/null +++ b/examples/TinyRenderer/our_gl.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include "our_gl.h" + +Matrix ModelView; +Matrix Viewport; +Matrix Projection; + +IShader::~IShader() {} + +void viewport(int x, int y, int w, int h) { + Viewport = Matrix::identity(); + Viewport[0][3] = x+w/2.f; + Viewport[1][3] = y+h/2.f; + Viewport[2][3] = 1.f; + Viewport[0][0] = w/2.f; + Viewport[1][1] = h/2.f; + Viewport[2][2] = 0; +} + +void projection(float coeff) { + Projection = Matrix::identity(); + Projection[3][2] = coeff; +} + +void lookat(Vec3f eye, Vec3f center, Vec3f up) { + Vec3f z = (eye-center).normalize(); + Vec3f x = cross(up,z).normalize(); + Vec3f y = cross(z,x).normalize(); + Matrix Minv = Matrix::identity(); + Matrix Tr = Matrix::identity(); + for (int i=0; i<3; i++) { + Minv[0][i] = x[i]; + Minv[1][i] = y[i]; + Minv[2][i] = z[i]; + Tr[i][3] = -center[i]; + } + ModelView = Minv*Tr; +} + +Vec3f barycentric(Vec2f A, Vec2f B, Vec2f C, Vec2f P) { + Vec3f s[2]; + for (int i=2; i--; ) { + s[i][0] = C[i]-A[i]; + s[i][1] = B[i]-A[i]; + s[i][2] = A[i]-P[i]; + } + Vec3f u = cross(s[0], s[1]); + if (std::abs(u[2])>1e-2) // dont forget that u[2] is integer. If it is zero then triangle ABC is degenerate + return Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z); + return Vec3f(-1,1,1); // in this case generate negative coordinates, it will be thrown away by the rasterizator +} + +void triangle(mat<4,3,float> &clipc, IShader &shader, TGAImage &image, float *zbuffer) { + mat<3,4,float> pts = (Viewport*clipc).transpose(); // transposed to ease access to each of the points + mat<3,2,float> pts2; + for (int i=0; i<3; i++) pts2[i] = proj<2>(pts[i]/pts[i][3]); + + Vec2f bboxmin( std::numeric_limits::max(), std::numeric_limits::max()); + Vec2f bboxmax(-std::numeric_limits::max(), -std::numeric_limits::max()); + Vec2f clamp(image.get_width()-1, image.get_height()-1); + for (int i=0; i<3; i++) { + for (int j=0; j<2; j++) { + bboxmin[j] = std::max(0.f, std::min(bboxmin[j], pts2[i][j])); + bboxmax[j] = std::min(clamp[j], std::max(bboxmax[j], pts2[i][j])); + } + } + Vec2i P; + TGAColor color; + for (P.x=bboxmin.x; P.x<=bboxmax.x; P.x++) { + for (P.y=bboxmin.y; P.y<=bboxmax.y; P.y++) { + Vec3f bc_screen = barycentric(pts2[0], pts2[1], pts2[2], P); + Vec3f bc_clip = Vec3f(bc_screen.x/pts[0][3], bc_screen.y/pts[1][3], bc_screen.z/pts[2][3]); + bc_clip = bc_clip/(bc_clip.x+bc_clip.y+bc_clip.z); + float frag_depth = clipc[2]*bc_clip; + if (bc_screen.x<0 || bc_screen.y<0 || bc_screen.z<0 || zbuffer[P.x+P.y*image.get_width()]>frag_depth) continue; + bool discard = shader.fragment(bc_clip, color); + if (!discard) { + zbuffer[P.x+P.y*image.get_width()] = frag_depth; + image.set(P.x, P.y, color); + } + } + } +} + diff --git a/examples/TinyRenderer/our_gl.h b/examples/TinyRenderer/our_gl.h new file mode 100644 index 000000000..bc8ed7312 --- /dev/null +++ b/examples/TinyRenderer/our_gl.h @@ -0,0 +1,22 @@ +#ifndef __OUR_GL_H__ +#define __OUR_GL_H__ +#include "tgaimage.h" +#include "geometry.h" + +extern Matrix ModelView; +extern Matrix Projection; + +void viewport(int x, int y, int w, int h); +void projection(float coeff=0.f); // coeff = -1/c +void lookat(Vec3f eye, Vec3f center, Vec3f up); + +struct IShader { + virtual ~IShader(); + virtual Vec4f vertex(int iface, int nthvert) = 0; + virtual bool fragment(Vec3f bar, TGAColor &color) = 0; +}; + +//void triangle(Vec4f *pts, IShader &shader, TGAImage &image, float *zbuffer); +void triangle(mat<4,3,float> &pts, IShader &shader, TGAImage &image, float *zbuffer); +#endif //__OUR_GL_H__ + diff --git a/examples/TinyRenderer/tgaimage.cpp b/examples/TinyRenderer/tgaimage.cpp new file mode 100644 index 000000000..47e6ff833 --- /dev/null +++ b/examples/TinyRenderer/tgaimage.cpp @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include "tgaimage.h" + +TGAImage::TGAImage() : data(NULL), width(0), height(0), bytespp(0) {} + +TGAImage::TGAImage(int w, int h, int bpp) : data(NULL), width(w), height(h), bytespp(bpp) { + unsigned long nbytes = width*height*bytespp; + data = new unsigned char[nbytes]; + memset(data, 0, nbytes); +} + +TGAImage::TGAImage(const TGAImage &img) : data(NULL), width(img.width), height(img.height), bytespp(img.bytespp) { + unsigned long nbytes = width*height*bytespp; + data = new unsigned char[nbytes]; + memcpy(data, img.data, nbytes); +} + +TGAImage::~TGAImage() { + if (data) delete [] data; +} + +TGAImage & TGAImage::operator =(const TGAImage &img) { + if (this != &img) { + if (data) delete [] data; + width = img.width; + height = img.height; + bytespp = img.bytespp; + unsigned long nbytes = width*height*bytespp; + data = new unsigned char[nbytes]; + memcpy(data, img.data, nbytes); + } + return *this; +} + +bool TGAImage::read_tga_file(const char *filename) { + if (data) delete [] data; + data = NULL; + std::ifstream in; + in.open (filename, std::ios::binary); + if (!in.is_open()) { + std::cerr << "can't open file " << filename << "\n"; + in.close(); + return false; + } + TGA_Header header; + in.read((char *)&header, sizeof(header)); + if (!in.good()) { + in.close(); + std::cerr << "an error occured while reading the header\n"; + return false; + } + width = header.width; + height = header.height; + bytespp = header.bitsperpixel>>3; + if (width<=0 || height<=0 || (bytespp!=GRAYSCALE && bytespp!=RGB && bytespp!=RGBA)) { + in.close(); + std::cerr << "bad bpp (or width/height) value\n"; + return false; + } + unsigned long nbytes = bytespp*width*height; + data = new unsigned char[nbytes]; + if (3==header.datatypecode || 2==header.datatypecode) { + in.read((char *)data, nbytes); + if (!in.good()) { + in.close(); + std::cerr << "an error occured while reading the data\n"; + return false; + } + } else if (10==header.datatypecode||11==header.datatypecode) { + if (!load_rle_data(in)) { + in.close(); + std::cerr << "an error occured while reading the data\n"; + return false; + } + } else { + in.close(); + std::cerr << "unknown file format " << (int)header.datatypecode << "\n"; + return false; + } + if (!(header.imagedescriptor & 0x20)) { + flip_vertically(); + } + if (header.imagedescriptor & 0x10) { + flip_horizontally(); + } + std::cerr << width << "x" << height << "/" << bytespp*8 << "\n"; + in.close(); + return true; +} + +bool TGAImage::load_rle_data(std::ifstream &in) { + unsigned long pixelcount = width*height; + unsigned long currentpixel = 0; + unsigned long currentbyte = 0; + TGAColor colorbuffer; + do { + unsigned char chunkheader = 0; + chunkheader = in.get(); + if (!in.good()) { + std::cerr << "an error occured while reading the data\n"; + return false; + } + if (chunkheader<128) { + chunkheader++; + for (int i=0; ipixelcount) { + std::cerr << "Too many pixels read\n"; + return false; + } + } + } else { + chunkheader -= 127; + in.read((char *)colorbuffer.bgra, bytespp); + if (!in.good()) { + std::cerr << "an error occured while reading the header\n"; + return false; + } + for (int i=0; ipixelcount) { + std::cerr << "Too many pixels read\n"; + return false; + } + } + } + } while (currentpixel < pixelcount); + return true; +} + +bool TGAImage::write_tga_file(const char *filename, bool rle) { + unsigned char developer_area_ref[4] = {0, 0, 0, 0}; + unsigned char extension_area_ref[4] = {0, 0, 0, 0}; + unsigned char footer[18] = {'T','R','U','E','V','I','S','I','O','N','-','X','F','I','L','E','.','\0'}; + std::ofstream out; + out.open (filename, std::ios::binary); + if (!out.is_open()) { + std::cerr << "can't open file " << filename << "\n"; + out.close(); + return false; + } + TGA_Header header; + memset((void *)&header, 0, sizeof(header)); + header.bitsperpixel = bytespp<<3; + header.width = width; + header.height = height; + header.datatypecode = (bytespp==GRAYSCALE?(rle?11:3):(rle?10:2)); + header.imagedescriptor = 0x20; // top-left origin + out.write((char *)&header, sizeof(header)); + if (!out.good()) { + out.close(); + std::cerr << "can't dump the tga file\n"; + return false; + } + if (!rle) { + out.write((char *)data, width*height*bytespp); + if (!out.good()) { + std::cerr << "can't unload raw data\n"; + out.close(); + return false; + } + } else { + if (!unload_rle_data(out)) { + out.close(); + std::cerr << "can't unload rle data\n"; + return false; + } + } + out.write((char *)developer_area_ref, sizeof(developer_area_ref)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + out.close(); + return false; + } + out.write((char *)extension_area_ref, sizeof(extension_area_ref)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + out.close(); + return false; + } + out.write((char *)footer, sizeof(footer)); + if (!out.good()) { + std::cerr << "can't dump the tga file\n"; + out.close(); + return false; + } + out.close(); + return true; +} + +// TODO: it is not necessary to break a raw chunk for two equal pixels (for the matter of the resulting size) +bool TGAImage::unload_rle_data(std::ofstream &out) { + const unsigned char max_chunk_length = 128; + unsigned long npixels = width*height; + unsigned long curpix = 0; + while (curpix=width || y>=height) { + return TGAColor(); + } + return TGAColor(data+(x+y*width)*bytespp, bytespp); +} + +bool TGAImage::set(int x, int y, TGAColor &c) { + if (!data || x<0 || y<0 || x>=width || y>=height) { + return false; + } + memcpy(data+(x+y*width)*bytespp, c.bgra, bytespp); + return true; +} + +bool TGAImage::set(int x, int y, const TGAColor &c) { + if (!data || x<0 || y<0 || x>=width || y>=height) { + return false; + } + memcpy(data+(x+y*width)*bytespp, c.bgra, bytespp); + return true; +} + +int TGAImage::get_bytespp() { + return bytespp; +} + +int TGAImage::get_width() { + return width; +} + +int TGAImage::get_height() { + return height; +} + +bool TGAImage::flip_horizontally() { + if (!data) return false; + int half = width>>1; + for (int i=0; i>1; + for (int j=0; j=(int)width) { + errx -= width; + nx += bytespp; + memcpy(tdata+nscanline+nx, data+oscanline+ox, bytespp); + } + } + erry += h; + oscanline += olinebytes; + while (erry>=(int)height) { + if (erry>=(int)height<<1) // it means we jump over a scanline + memcpy(tdata+nscanline+nlinebytes, tdata+nscanline, nlinebytes); + erry -= height; + nscanline += nlinebytes; + } + } + delete [] data; + data = tdata; + width = w; + height = h; + return true; +} + diff --git a/examples/TinyRenderer/tgaimage.h b/examples/TinyRenderer/tgaimage.h new file mode 100644 index 000000000..63a944bca --- /dev/null +++ b/examples/TinyRenderer/tgaimage.h @@ -0,0 +1,98 @@ +#ifndef __IMAGE_H__ +#define __IMAGE_H__ + +#include + +#pragma pack(push,1) +struct TGA_Header { + char idlength; + char colormaptype; + char datatypecode; + short colormaporigin; + short colormaplength; + char colormapdepth; + short x_origin; + short y_origin; + short width; + short height; + char bitsperpixel; + char imagedescriptor; +}; +#pragma pack(pop) + +struct TGAColor { + unsigned char bgra[4]; + unsigned char bytespp; + + TGAColor() : bgra(), bytespp(1) { + for (int i=0; i<4; i++) bgra[i] = 0; + } + + TGAColor(unsigned char R, unsigned char G, unsigned char B, unsigned char A=255) : bgra(), bytespp(4) { + bgra[0] = B; + bgra[1] = G; + bgra[2] = R; + bgra[3] = A; + } + + TGAColor(unsigned char v) : bgra(), bytespp(1) { + for (int i=0; i<4; i++) bgra[i] = 0; + bgra[0] = v; + } + + + TGAColor(const unsigned char *p, unsigned char bpp) : bgra(), bytespp(bpp) { + for (int i=0; i<(int)bpp; i++) { + bgra[i] = p[i]; + } + for (int i=bpp; i<4; i++) { + bgra[i] = 0; + } + } + + unsigned char& operator[](const int i) { return bgra[i]; } + + TGAColor operator *(float intensity) const { + TGAColor res = *this; + intensity = (intensity>1.f?1.f:(intensity<0.f?0.f:intensity)); + for (int i=0; i<4; i++) res.bgra[i] = bgra[i]*intensity; + return res; + } +}; + +class TGAImage { +protected: + unsigned char* data; + int width; + int height; + int bytespp; + + bool load_rle_data(std::ifstream &in); + bool unload_rle_data(std::ofstream &out); +public: + enum Format { + GRAYSCALE=1, RGB=3, RGBA=4 + }; + + TGAImage(); + TGAImage(int w, int h, int bpp); + TGAImage(const TGAImage &img); + bool read_tga_file(const char *filename); + bool write_tga_file(const char *filename, bool rle=true); + bool flip_horizontally(); + bool flip_vertically(); + bool scale(int w, int h); + TGAColor get(int x, int y); + bool set(int x, int y, TGAColor &c); + bool set(int x, int y, const TGAColor &c); + ~TGAImage(); + TGAImage & operator =(const TGAImage &img); + int get_width(); + int get_height(); + int get_bytespp(); + unsigned char *buffer(); + void clear(); +}; + +#endif //__IMAGE_H__ +