add unmodified version of TinyRenderer, a 500 line software renderer with vertex and pixel shaders, texture mapping and Wavefront .obj support.

This commit is contained in:
erwin coumans
2016-04-26 20:47:10 -07:00
parent edba85bab3
commit 2cb39e358a
12 changed files with 1116 additions and 0 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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, well 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. Its one of the simplest formats that supports images in RGB/RGBA/black and white formats. So, as a starting point, well 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. Well 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 doesnt 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)

View File

@@ -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) {}

View File

@@ -0,0 +1,221 @@
#ifndef __GEOMETRY_H__
#define __GEOMETRY_H__
#include <cmath>
#include <vector>
#include <cassert>
#include <iostream>
template<size_t DimCols,size_t DimRows,typename T> class mat;
template <size_t DIM, typename T> struct vec {
vec() { for (size_t i=DIM; i--; data_[i] = T()); }
T& operator[](const size_t i) { assert(i<DIM); return data_[i]; }
const T& operator[](const size_t i) const { assert(i<DIM); return data_[i]; }
private:
T data_[DIM];
};
/////////////////////////////////////////////////////////////////////////////////
template <typename T> struct vec<2,T> {
vec() : x(T()), y(T()) {}
vec(T X, T Y) : x(X), y(Y) {}
template <class U> 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 <typename T> 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 <class U> 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<size_t DIM,typename T> T operator*(const vec<DIM,T>& lhs, const vec<DIM,T>& rhs) {
T ret = T();
for (size_t i=DIM; i--; ret+=lhs[i]*rhs[i]);
return ret;
}
template<size_t DIM,typename T>vec<DIM,T> operator+(vec<DIM,T> lhs, const vec<DIM,T>& rhs) {
for (size_t i=DIM; i--; lhs[i]+=rhs[i]);
return lhs;
}
template<size_t DIM,typename T>vec<DIM,T> operator-(vec<DIM,T> lhs, const vec<DIM,T>& rhs) {
for (size_t i=DIM; i--; lhs[i]-=rhs[i]);
return lhs;
}
template<size_t DIM,typename T,typename U> vec<DIM,T> operator*(vec<DIM,T> lhs, const U& rhs) {
for (size_t i=DIM; i--; lhs[i]*=rhs);
return lhs;
}
template<size_t DIM,typename T,typename U> vec<DIM,T> operator/(vec<DIM,T> lhs, const U& rhs) {
for (size_t i=DIM; i--; lhs[i]/=rhs);
return lhs;
}
template<size_t LEN,size_t DIM,typename T> vec<LEN,T> embed(const vec<DIM,T> &v, T fill=1) {
vec<LEN,T> ret;
for (size_t i=LEN; i--; ret[i]=(i<DIM?v[i]:fill));
return ret;
}
template<size_t LEN,size_t DIM, typename T> vec<LEN,T> proj(const vec<DIM,T> &v) {
vec<LEN,T> ret;
for (size_t i=LEN; i--; ret[i]=v[i]);
return ret;
}
template <typename T> 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 <size_t DIM, typename T> std::ostream& operator<<(std::ostream& out, vec<DIM,T>& v) {
for(unsigned int i=0; i<DIM; i++) {
out << v[i] << " " ;
}
return out ;
}
/////////////////////////////////////////////////////////////////////////////////
template<size_t DIM,typename T> struct dt {
static T det(const mat<DIM,DIM,T>& src) {
T ret=0;
for (size_t i=DIM; i--; ret += src[0][i]*src.cofactor(0,i));
return ret;
}
};
template<typename T> struct dt<1,T> {
static T det(const mat<1,1,T>& src) {
return src[0][0];
}
};
/////////////////////////////////////////////////////////////////////////////////
template<size_t DimRows,size_t DimCols,typename T> class mat {
vec<DimCols,T> rows[DimRows];
public:
mat() {}
vec<DimCols,T>& operator[] (const size_t idx) {
assert(idx<DimRows);
return rows[idx];
}
const vec<DimCols,T>& operator[] (const size_t idx) const {
assert(idx<DimRows);
return rows[idx];
}
vec<DimRows,T> col(const size_t idx) const {
assert(idx<DimCols);
vec<DimRows,T> ret;
for (size_t i=DimRows; i--; ret[i]=rows[i][idx]);
return ret;
}
void set_col(size_t idx, vec<DimRows,T> v) {
assert(idx<DimCols);
for (size_t i=DimRows; i--; rows[i][idx]=v[i]);
}
static mat<DimRows,DimCols,T> identity() {
mat<DimRows,DimCols,T> 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<DimCols,T>::det(*this);
}
mat<DimRows-1,DimCols-1,T> get_minor(size_t row, size_t col) const {
mat<DimRows-1,DimCols-1,T> ret;
for (size_t i=DimRows-1; i--; )
for (size_t j=DimCols-1;j--; ret[i][j]=rows[i<row?i:i+1][j<col?j:j+1]);
return ret;
}
T cofactor(size_t row, size_t col) const {
return get_minor(row,col).det()*((row+col)%2 ? -1 : 1);
}
mat<DimRows,DimCols,T> adjugate() const {
mat<DimRows,DimCols,T> ret;
for (size_t i=DimRows; i--; )
for (size_t j=DimCols; j--; ret[i][j]=cofactor(i,j));
return ret;
}
mat<DimRows,DimCols,T> invert_transpose() {
mat<DimRows,DimCols,T> ret = adjugate();
T tmp = ret[0]*rows[0];
return ret/tmp;
}
mat<DimRows,DimCols,T> invert() {
return invert_transpose().transpose();
}
mat<DimCols,DimRows,T> transpose() {
mat<DimCols,DimRows,T> ret;
for (size_t i=DimCols; i--; ret[i]=this->col(i));
return ret;
}
};
/////////////////////////////////////////////////////////////////////////////////
template<size_t DimRows,size_t DimCols,typename T> vec<DimRows,T> operator*(const mat<DimRows,DimCols,T>& lhs, const vec<DimCols,T>& rhs) {
vec<DimRows,T> ret;
for (size_t i=DimRows; i--; ret[i]=lhs[i]*rhs);
return ret;
}
template<size_t R1,size_t C1,size_t C2,typename T>mat<R1,C2,T> operator*(const mat<R1,C1,T>& lhs, const mat<C1,C2,T>& rhs) {
mat<R1,C2,T> result;
for (size_t i=R1; i--; )
for (size_t j=C2; j--; result[i][j]=lhs[i]*rhs.col(j));
return result;
}
template<size_t DimRows,size_t DimCols,typename T>mat<DimCols,DimRows,T> operator/(mat<DimRows,DimCols,T> lhs, const T& rhs) {
for (size_t i=DimRows; i--; lhs[i]=lhs[i]/rhs);
return lhs;
}
template <size_t DimRows,size_t DimCols,class T> std::ostream& operator<<(std::ostream& out, mat<DimRows,DimCols,T>& m) {
for (size_t i=0; i<DimRows; i++) out << m[i] << std::endl;
return out;
}
/////////////////////////////////////////////////////////////////////////////////
typedef vec<2, float> 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__

View File

@@ -0,0 +1,94 @@
#include <vector>
#include <limits>
#include <iostream>
#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<float>::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; m<argc; m++) {
model = new Model(argv[m]);
Shader shader;
for (int i=0; i<model->nfaces(); 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;
}

View File

@@ -0,0 +1,108 @@
#include <iostream>
#include <fstream>
#include <sstream>
#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<Vec3i> 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<int> Model::face(int idx) {
std::vector<int> 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();
}

View File

@@ -0,0 +1,33 @@
#ifndef __MODEL_H__
#define __MODEL_H__
#include <vector>
#include <string>
#include "geometry.h"
#include "tgaimage.h"
class Model {
private:
std::vector<Vec3f> verts_;
std::vector<std::vector<Vec3i> > faces_; // attention, this Vec3i means vertex/uv/normal
std::vector<Vec3f> norms_;
std::vector<Vec2f> 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<int> face(int idx);
};
#endif //__MODEL_H__

View File

@@ -0,0 +1,86 @@
#include <cmath>
#include <limits>
#include <cstdlib>
#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<float>::max(), std::numeric_limits<float>::max());
Vec2f bboxmax(-std::numeric_limits<float>::max(), -std::numeric_limits<float>::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);
}
}
}
}

View File

@@ -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__

View File

@@ -0,0 +1,356 @@
#include <iostream>
#include <fstream>
#include <string.h>
#include <time.h>
#include <math.h>
#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; i<chunkheader; i++) {
in.read((char *)colorbuffer.bgra, bytespp);
if (!in.good()) {
std::cerr << "an error occured while reading the header\n";
return false;
}
for (int t=0; t<bytespp; t++)
data[currentbyte++] = colorbuffer.bgra[t];
currentpixel++;
if (currentpixel>pixelcount) {
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; i<chunkheader; i++) {
for (int t=0; t<bytespp; t++)
data[currentbyte++] = colorbuffer.bgra[t];
currentpixel++;
if (currentpixel>pixelcount) {
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<npixels) {
unsigned long chunkstart = curpix*bytespp;
unsigned long curbyte = curpix*bytespp;
unsigned char run_length = 1;
bool raw = true;
while (curpix+run_length<npixels && run_length<max_chunk_length) {
bool succ_eq = true;
for (int t=0; succ_eq && t<bytespp; t++) {
succ_eq = (data[curbyte+t]==data[curbyte+t+bytespp]);
}
curbyte += bytespp;
if (1==run_length) {
raw = !succ_eq;
}
if (raw && succ_eq) {
run_length--;
break;
}
if (!raw && !succ_eq) {
break;
}
run_length++;
}
curpix += run_length;
out.put(raw?run_length-1:run_length+127);
if (!out.good()) {
std::cerr << "can't dump the tga file\n";
return false;
}
out.write((char *)(data+chunkstart), (raw?run_length*bytespp:bytespp));
if (!out.good()) {
std::cerr << "can't dump the tga file\n";
return false;
}
}
return true;
}
TGAColor TGAImage::get(int x, int y) {
if (!data || x<0 || y<0 || x>=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<half; i++) {
for (int j=0; j<height; j++) {
TGAColor c1 = get(i, j);
TGAColor c2 = get(width-1-i, j);
set(i, j, c2);
set(width-1-i, j, c1);
}
}
return true;
}
bool TGAImage::flip_vertically() {
if (!data) return false;
unsigned long bytes_per_line = width*bytespp;
unsigned char *line = new unsigned char[bytes_per_line];
int half = height>>1;
for (int j=0; j<half; j++) {
unsigned long l1 = j*bytes_per_line;
unsigned long l2 = (height-1-j)*bytes_per_line;
memmove((void *)line, (void *)(data+l1), bytes_per_line);
memmove((void *)(data+l1), (void *)(data+l2), bytes_per_line);
memmove((void *)(data+l2), (void *)line, bytes_per_line);
}
delete [] line;
return true;
}
unsigned char *TGAImage::buffer() {
return data;
}
void TGAImage::clear() {
memset((void *)data, 0, width*height*bytespp);
}
bool TGAImage::scale(int w, int h) {
if (w<=0 || h<=0 || !data) return false;
unsigned char *tdata = new unsigned char[w*h*bytespp];
int nscanline = 0;
int oscanline = 0;
int erry = 0;
unsigned long nlinebytes = w*bytespp;
unsigned long olinebytes = width*bytespp;
for (int j=0; j<height; j++) {
int errx = width-w;
int nx = -bytespp;
int ox = -bytespp;
for (int i=0; i<width; i++) {
ox += bytespp;
errx += w;
while (errx>=(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;
}

View File

@@ -0,0 +1,98 @@
#ifndef __IMAGE_H__
#define __IMAGE_H__
#include <fstream>
#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__