Code Monkey home page Code Monkey logo

tinyrenderer's Introduction

Tiny Renderer or how OpenGL works: software rendering in 500 lines of code

Check the wiki for the detailed lessons.

compilation

git clone https://github.com/ssloy/tinyrenderer.git &&
cd tinyrenderer &&
mkdir build &&
cd build &&
cmake .. &&
cmake --build . -j &&
./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 evironment for GitHub: Open in Gitpod

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, 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:

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 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 you will find the source code I give to my students.

#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:

Teaser: few examples made with the renderer

tinyrenderer's People

Contributors

bg4444 avatar cirosantilli avatar dmedov avatar paroj avatar ssloy avatar theosotr avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tinyrenderer's Issues

confusing description in troubleshooting article

Frequent bug: while sorting by y-coordinate, the vertices are sorted, but the data coming with the vertices is not.
Gouraud shading, forgot to sort intensities

In my case I was calculating a barycentric coordinate of one vertex and assigning it to a different vertex by mistake.
The artifacts looked exactly the same as you presented, if it is the same problem then the description could be more informative.

geometry.cpp проблемы

При прохождении вашего великолепного урока споткнулся об проблему
Vec3 (to few arguments)
компилирую Visual Studio 2013 но компилировал также на Dev C++, и получаю ошибку
wrong number of template arguments

прикрепляю код программы который у меня не компилируется
3dtofile.zip

questing about shadowbuffer in lesson 7

In lesson 7, we use a shadow buffer to determine whether a point is lit or not.

In my opinion, if a point is lit, then the depth should equal to than in the shadow buffer. However, this shadow buffer is an array, we need to round the point into an integer. As a result, there is some errors between the shadow buffer(from DepthShader) and the depth recovered by the Shader.

In your wiki, you use:
float shadow = .3+.7*(shadowbuffer[idx]<sb_p[2]+43.34);

Then, both cases will be ok.

However, I compare those two value in my code:

struct Shader : public IShader {
    mat<4,4,float> uniform_M;   //  Projection*ModelView
    mat<4,4,float> uniform_MIT; // (Projection*ModelView).invert_transpose()
    mat<4,4,float> uniform_Mshadow; // transform framebuffer screen coordinates to shadowbuffer screen coordinates
    mat<2,3,float> varying_uv;  // triangle uv coordinates, written by the vertex shader, read by the fragment shader
    mat<3,3,float> varying_tri; // triangle coordinates before Viewport transform, written by VS, read by FS
    mat<3,3,float> vert; // triangle coordinates before Viewport transform, written by VS, read by FS

    mat<4,4,float> uniform_M0shadow; // transform framebuffer screen coordinates to shadowbuffer screen coordinates

    Shader(Matrix M, Matrix MIT, Matrix MS, Matrix MS0) :
        uniform_M(M), uniform_MIT(MIT), uniform_Mshadow(MS), varying_uv(), varying_tri(), vert(), uniform_M0shadow(MS0) {}

    virtual Vec4f vertex(int iface, int nthvert) {
        varying_uv.set_col(nthvert, model->uv(iface, nthvert));
        Vec4f gl_Vertex = Viewport*Projection*ModelView*embed<4>(model->vert(iface, nthvert));
        varying_tri.set_col(nthvert, proj<3>(gl_Vertex/gl_Vertex[3]));
        vert[nthvert] =  model->vert(iface, nthvert);
        return gl_Vertex;
    }

    virtual bool fragment(Vec3f bar, TGAColor &color) {
        Vec4f sb_p = uniform_Mshadow*embed<4>(varying_tri*bar); // corresponding point in the shadow buffer
        sb_p = sb_p/sb_p[3];
        int idx = int(sb_p[0]) + int(sb_p[1])*width; // index in the shadowbuffer array
      
        // ==========================================
        static int x1, x2;
        if(std::abs(shadowbuffer[idx]-sb_p[2]) < 10){
            if (shadowbuffer[idx] > sb_p[2])
                x1 ++;
            else
                x2 ++;
            std::cout << x1 << " " << x2 << std::endl;
        }
        // ==========================================

        float shadow = .3+.7*(shadowbuffer[idx] < sb_p[2]); // magic coeff to avoid z-fighting
        Vec2f uv = varying_uv*bar;                 // interpolate uv for the current pixel
        Vec3f n = proj<3>(uniform_MIT*embed<4>(model->normal(uv))).normalize(); // normal
        Vec3f l = proj<3>(uniform_M  *embed<4>(light_dir        )).normalize(); // light vector
        Vec3f r = (n*(n*l*2.f) - l).normalize();   // reflected light
        float spec = pow(std::max(r.z, 0.0f), model->specular(uv));
        float diff = std::max(0.f, n*l);
        TGAColor c = model->diffuse(uv);
        for (int i=0; i<3; i++) color[i] = std::min<float>(20 + c[i]*shadow*(1.2*diff + .6*spec), 255);
        return false;
    }
};


results:

...
4644 35330
4644 35331
4644 35332
4645 35332
4645 35333
4645 35334
4645 35335
4645 35336
4645 35337
4645 35338
4645 35339
4645 35340
4645 35341
4645 35342
4645 35343
4645 35344
4645 35345
4645 35346
4645 35347
4645 35348
4645 35349
4645 35350
4645 35351
4645 35352
4645 35353

And this turns out that most of the time shadowbuffer[idx] < sb_p[2]

I can not understand it. I think, if a point is lit by light, the depth from DepthShader and Shader should be much close. And this result indicates there are some systematic errors between them.

Probably mistake in lookat

Hello

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;
}

I am wondering about this line: Tr[i][3] = -center[i];. We translate our camera in world coordinates, so maybe we should give an eye coordinates to translation matrix? Like this: Tr[i][3] = -eye[i];

Big black holes.

Здравствуйте, понимаю, что опоздал на несколько лет, но все таки столкнулся с очень интересной проблемой. Вы в комментариях под статьёй рассказывали, о хаке, который помогает избавиться от дыр на рендере. Они возникают из-за неправильного округления.
Моя проблема в том, что на моих рендерах эти дыры никуда не пропадают и когда я кастую (int(.999) = 0) и когда я округляю математически правильно: (int(.999+.5)). Вы знаете какие-нибудь ещё красивые решения, чтобы выкрутиться?
К сожалению, мой код вам не поможет, я решил устроить себе челендж, и написать такой рендер на python, который хорошо знал. Можете помочь, или нужна ещё информация?

Изображение с дырками:

render

ZBuffer с дырками:

zbuffer

SEGV in function Model::uv

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause SEGV in function Model::uv in model.cpp when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==5610==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000004 (pc 0x000000530bbd bp 0x7ffc0d6ba490 sp 0x7ffc0d6ba380 T0)
==5610==The signal is caused by a READ memory access.
==5610==Hint: address points to the zero page.
    #0 0x530bbc in Model::uv(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:96:16
    #1 0x542798 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:26:44
    #2 0x54146b in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:82:24
    #3 0x7f6109abe82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #4 0x41c098 in _start (/home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main+0x41c098)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:96:16 in Model::uv(int, int)
==5610==ABORTING

very strange phenomenon

int main(int argc, char** argv) {
TGAImage image(image_width, image_height, TGAImage::RGB);

for (int i = 0; i < image_width; ++i)
	image.set(i, 10, 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;

}

//when image_width = image_height = 800,the line is out of bound.
But when width and height less than 700, the line is visible

Why reverse the element order when sampling from normal map

Here is the code of sampling normal from normal map:

tinyrenderer/model.cpp

Lines 86 to 93 in 907bb56

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;
}

I can't understand why the element order of normal is reversed, that is, why not write the code like this:

for (int i=0; i<3; i++)
    res[i] = (float)c[i]/255.f*2.f - 1.f;

Useful but confusing at times

This tutorial is very good, but there are often quite difficult sentences that introduce new terminology without any definition. Perhaps there should be a note that some amount of geometry or other kind of background knowledge is a prerequisite. I keep getting stuck trying to figure out the point of an example because the overall concept and the terminology used is not explained clearly.

Bug on face drawing code in lesson 1

Please correct if this is wrong here but I think there's a bug in the lesson one code when we draw the obj mesh.

Specifically here:

int x0 = (v0.x+1.)*width/2.;

If a vertex has a x/y value equal to 1.0 or -1.0, then x or y becomes equal to width/height.

The problem is the data variable in TGAImage has a max width/height of (width - 1)/(height - 1) because its zero indexed.

What is hiding this bug is the fact that we just exit TGAImage::set if the x/y is out of bounds:

if (!data || x<0 || y<0 || x>=width || y>=height) {

I think we're effectively cutting off the top/right edge of the image in the current lesson 1 code.

I think the solution here would be to do something like this?

int x0 = (v0.x+1.)*(width - 1)/2.;
int y0 = (v0.y+1.)*(height - 1)/2.;
int x1 = (v1.x+1.)*(width - 1)/2.;
int y1 = (v1.y+1.)*(height - 1)/2.;

Happened to notice this because I implemented a difference image drawing class (for BMP instead of TGA), and I didn't have the bounds check in the set method.

As an easier way to confirm this, just comment out the bounds check code in TGAImage::set and you will get an out of bounds exception.

Your way

Vec3f barycentric(Vec3f A, Vec3f B, Vec3f C, Vec3f P) {

I do not understand your barycentric method's way. When i implement lesson 2 with my own barycentric method (finding cross products of edges and check point is inside with dot product, x and y are in screen coordinates format, but z is world coordinate) the final image is not same as yours. this is my method, if you can give me the point it would be glad.

Vec3f barycentric(Vec3f A, Vec3f B, Vec3f C, Vec3f P)
{
	float u;
	float v;
	float w;

	Vec3f AB(B - A);
	Vec3f AP(P - A);

	Vec3f BC(C - B);
	Vec3f BP(P - B);

	Vec3f CA(A - C);
	Vec3f CP(P - C);

	//for plane normal calculation
	Vec3f AC(C - A);

	Vec3f normal = cross(AB, AC);

	float denom = normal.dot(normal);

	Vec3f crossABAP = cross(AB, AP);

	if (normal.dot(crossABAP) < 0)
	{
		return Vec3f(-1, -1, -1);
	}

	Vec3f crossBCBP = cross(BC, BP);

	if ((u = normal.dot(crossBCBP)) < 0)
	{
		return Vec3f(-1, -1, -1);
	}

	Vec3f crossCACP = cross(CA, CP);

	if ((v = normal.dot(crossCACP) < 0))
	{
		return Vec3f(-1, -1, -1);
	}

	u /= denom;
	v /= denom;
	w = 1 - u - v;

	return Vec3f(w, u, v);
}

Heap buffer overflow in function Model::vert

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause heap buffer overflow in function Model::vert in model.cpp when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==31108==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6230000000f4 at pc 0x0000004df14d bp 0x7ffceaa92750 sp 0x7ffceaa91f00
READ of size 12 at 0x6230000000f4 thread T0
    #0 0x4df14c in __asan_memcpy /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23
    #1 0x52faed in Model::vert(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:68:12
    #2 0x543047 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:28:64
    #3 0x54146b in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:82:24
    #4 0x7f0574f4582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #5 0x41c098 in _start (/home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main+0x41c098)

0x6230000000f4 is located 12 bytes to the left of 6144-byte region [0x623000000100,0x623000001900)
allocated by thread T0 here:
    #0 0x517d58 in operator new(unsigned long) /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:92
    #1 0x535305 in __gnu_cxx::new_allocator<vec<3ul, float> >::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/new_allocator.h:104:27
    #2 0x5352ab in __gnu_cxx::__alloc_traits<std::allocator<vec<3ul, float> > >::allocate(std::allocator<vec<3ul, float> >&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/alloc_traits.h:182:18
    #3 0x534ed2 in std::_Vector_base<vec<3ul, float>, std::allocator<vec<3ul, float> > >::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:170:20
    #4 0x5340c6 in std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > >::_M_insert_aux(__gnu_cxx::__normal_iterator<vec<3ul, float>*, std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > > >, vec<3ul, float> const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/vector.tcc:353:30
    #5 0x5318a8 in std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > >::push_back(vec<3ul, float> const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:925:4
    #6 0x52ded8 in Model::Model(char const*) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:19:20
    #7 0x54133c in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:78:21
    #8 0x7f0574f4582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23 in __asan_memcpy
Shadow bytes around the buggy address:
  0x0c467fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c467fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa[fa]fa
  0x0c467fff8020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==31108==ABORTING

What if I load a obj_file without any tga_image?

I`m using AFL to fuzz this program these days, and I puts obj_file and tga_file in different files. Now AFL found some crashes, if I pass these file to main.cpp, then it will cause a SIGSEGV error at model.cpp:Model::uv(int,int), the std::vector<std::vector > faces_ will be all NULL, then the program will crash. But if I load obj_file you give me myself, the program will exit normally.

0.zip

Lession 2: question about the intensity math

float intensity = n*light_dir;
n is a vec3f, light_dir is also a vec3f.
Does it mean float intensity = n mul light_dir? but vec3f mul vec3f is a vec3f, how did it cast to a float?
thanks.

Cannot open output tga file

What I've done is just clone and compile using WSL since I dont have a linux environment at present, however the output tga file seems to be corrupted like this:

output

Is there anything to do with WSL like compatibility?

Lesson 2: What type is light_dir and where does it come from?

In order to calculate the light intensity, you need to know the light direction. The tutorial doesn't seem to mention at all how to obtain this light direction though.

In your example code, the light_dir variable gets used, but we don't see it getting defined anywhere.

Heap buffer overflow in function Model::vert

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause heap buffer overflow in function Model::vert when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==31108==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6230000000f4 at pc 0x0000004df14d bp 0x7ffceaa92750 sp 0x7ffceaa91f00
READ of size 12 at 0x6230000000f4 thread T0
    #0 0x4df14c in __asan_memcpy /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23
    #1 0x52faed in Model::vert(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:68:12
    #2 0x543047 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:28:64
    #3 0x54146b in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:82:24
    #4 0x7f0574f4582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #5 0x41c098 in _start (/home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main+0x41c098)

0x6230000000f4 is located 12 bytes to the left of 6144-byte region [0x623000000100,0x623000001900)
allocated by thread T0 here:
    #0 0x517d58 in operator new(unsigned long) /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:92
    #1 0x535305 in __gnu_cxx::new_allocator<vec<3ul, float> >::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/new_allocator.h:104:27
    #2 0x5352ab in __gnu_cxx::__alloc_traits<std::allocator<vec<3ul, float> > >::allocate(std::allocator<vec<3ul, float> >&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/alloc_traits.h:182:18
    #3 0x534ed2 in std::_Vector_base<vec<3ul, float>, std::allocator<vec<3ul, float> > >::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:170:20
    #4 0x5340c6 in std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > >::_M_insert_aux(__gnu_cxx::__normal_iterator<vec<3ul, float>*, std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > > >, vec<3ul, float> const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/vector.tcc:353:30
    #5 0x5318a8 in std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > >::push_back(vec<3ul, float> const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:925:4
    #6 0x52ded8 in Model::Model(char const*) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:19:20
    #7 0x54133c in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:78:21
    #8 0x7f0574f4582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23 in __asan_memcpy
Shadow bytes around the buggy address:
  0x0c467fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c467fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa[fa]fa
  0x0c467fff8020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==31108==ABORTING

Not an issue: asking for guidance

Thank you very much for the wonderful content:
In lesson 1 : at the end of the lesson there is a mention of
The model.cpp file contains a simple parser.
Should I use the file with the accompanying dependencies geometry.h and model.h, or should I implement them on my own?

Darboux basis

https://github.com/ssloy/tinyrenderer/wiki/Lesson-6bis:-tangent-space-normal-mapping#let-us-compute-darboux-basis-and-apply-the-perturbation-of-normals

Working my way through your lesson and got tripped up at this section. When finding the Darboux frame, you are using the interpolated normal vector(Vec3f bn = (varying_nrm*bar).normalize(); and B.set_col(2, bn);). Since the interpolated normal isn't usually normal to the triangle face, and the i, j vectors are on the triangle face, this means that the i, j, n vectors aren't actually orthogonal, no?

Reading around online it seems that many implementations will calculate the tangent vectors on a per-vertex basis, by averaging the tangent vectors of each face that is touching the vertex, similar to how the normals are calculated. Then the tangent can be interpolated like the normals within the triangle.

I'm guessing you're just using a bit of an approximation? The results look fine so it seems to be working alright.

Hum... the source code is correct I think. It says the following: Vec3f u = cross(Vec3f(AC.x, AB.x, PA.x), Vec3f(AC.y, AB.y, PA.y));

Hum... the source code is correct I think. It says the following: Vec3f u = cross(Vec3f(AC.x, AB.x, PA.x), Vec3f(AC.y, AB.y, PA.y));
therefore the confusion you have. Anyways, I found that this piece of code causes many troubles for the readers, and it is replaced by an explicit linear system solving in the most recent version of the code:

vec3 barycentric(const vec2 tri[3], const vec2 P) {

Originally posted by @ssloy in #57 (comment)

according to the article, what we get from cross(Vec3f(AC.x, AB.x, PA.x), Vec3f(AC.y, AB.y, PA.y)) is [u *z, v * z, z], and divided by z we can get [u, v, 1]
and what we want from the barycentric function is [1-u-v, u, v]
therefore, we should return Vec3f(1.f-(u.x+u.y)/u.z, u.x/u.z, u.y/u.z) instead of Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z);

Не нашел как еще связатся

На хабре указан какой то не понятный не существующий эмэйл поэтому пишу тут.

Возможно была бы не плохая идея создать конференцию в скайпе или чем то подобном где люди могли бы общаться на тему и помогать решать друг другу вопросы. Сам давно хотел статьи глянуть но вот только вчера первую рассмотрел :)

Я вижу что на хабре идет активное общение по теме но к примеру я там не могу что то писать. А даже еслиб и мог то глупо там задавать вопросы какие то. Поэтому возможно создать конференцию была бы не плохая идея. Люди бы помогали друг другу развиваться

why the exclusive or ?

Sorry if this is the wrong place to ask this. I'm on lesson 2. I'm porting the lessons to C# to try to understand the theory better. The following line of code confuses me. I understand it's using an exclusive or but why is it doing this? What is the logic behind this so I can port this to C#. Thank you.

Vec3f n = (world_coords[2]-world_coords[0])^(world_coords[1]-world_coords[0]);

Add Anti-Aliasing Section

Hey there, I was wondering if you have any plans to add anti-aliasing to this course? Would be pretty cool to have and it's not much more complex than a lot of other topics treated in here.

Thanks.

Shading triangles with clang++-8

So I am following your course here, and I struggled a bit with the shading lecture. This might be a compiler issue, meaning, the compiler stops me from doing things that I do not want.

My geometry.h basically looks the same as here, and multiplying a vector with a scalar value looks like this:

template <typename T>
struct Vector2D {
...
    inline Vector2D operator*(float scalar) const {
        return Vector2D{x*scalar, y*scalar};
    }
...

However I found that, since I am following the GSL (core guidelines for c++), and using initializer list constructors ( using the {}, instead of () ), the compiler will either complain, or result in faulty code. This might be due to clang in this case, but I suspect MSVC++ and g++ will result in the same compiler error:

error: type 'float' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]

So, changing that to:

    inline Vector2D operator*(float scalar) const {
        return Vector2D{static_cast<T>(x*scalar), static_cast<T>(y*scalar)};
    }

Fixes the issue. These are not issues related to the course in itself, but perhaps might be worth noting. If it's irrelevant, just close and remove this issue. Thanks again for awesome content.

Simple speed up of Lesson 1

First off, amazing content. But changing the line drawing function, so that it does not check if it is steep, within the for loop, almost speeds it up by 2x, with -O3 on. So, with sacrificing for a little extra code bloat, if one changes the for loop, from

    for (int x=x0; x<=x1; x++) { 
        if (steep) { 
            image.set(y, x, color); 
        } else { 
            image.set(x, y, color); 
        } 
        error2 += derror2; 
        if (error2 > dx) { 
            y += (y1>y0?1:-1); 
            error2 -= dx*2; 
        } 
    } 

to instead

    if(steep) {
        for(int x = x0; x<=x1; ++x) {
            img.set_pixel_color(y, x, color);
            error2 += derror2;
            if(error2 > dx) {
                y += (y1>y0? 1 : -1);
                error2 -= dx*2;
            }
        }
    } else {
        for(int x = x0; x<=x1; ++x) {
            img.set_pixel_color(x, y, color);
            error2 += derror2;
            if(error2 > dx) {
                y += (y1>y0? 1 : -1);
                error2 -= dx*2;
            }
        }
    }

We suddenly eliminate one of the biggest bad guys in programming, branching, and especially branching inside for loops. This also leaves even more room for the optimizer to optimize better. Granted, some optimizer (perhaps) are smart enough to see this on it's own, but it really isn't good practice to count on that in this case. Using rudimentary system_clock and measuring in microseconds, drawing the wire frame image, goes from around ~44000us to ~22000 us, on my system. So it's a pretty impressive speedup to eliminate the branch instruction inside a loop.

An error in triangle rasterisation

Hi. I used your triangle rasterisation code and found a problem when drawing two adjacent triangles. Sometimes there are missing pixels between them.
image
I found that the problem is how you calculate
int segment_height = t1.y - t0.y + 1
the "+1" leads to the error. I assume you made it to avoid division by zero, but it's wrong.
Here is how a did it:

	auto addTriangle = [ & ]( Vec2i t0, Vec2i t1, Vec2i t2 )
	{
		if ( t0.y > t1.y ) std::swap( t0, t1 );
		if ( t0.y > t2.y ) std::swap( t0, t2 );
		if ( t1.y > t2.y ) std::swap( t1, t2 );
		int total_height = t2.y - t0.y;

		if ( t0.y != t1.y )
		{
			int segment_height = t1.y - t0.y;
			for ( int y = t0.y; y <= t1.y; y++ )
			{
				int A = t0.x + ( t2.x - t0.x ) * ( y - t0.y ) / total_height;
				int B = t0.x + ( t1.x - t0.x ) * ( y - t0.y ) / segment_height;
				drawLine( y, A, B ); // draws a horisontal line
			}
		}

		if ( t1.y != t2.y )
		{
			int segment_height = t2.y - t1.y;
			for ( int y = t1.y; y <= t2.y; y++ )
			{
				int A = t0.x + ( t2.x - t0.x ) * ( y - t0.y ) / total_height;
				int B = t1.x + ( t2.x - t1.x ) * ( y - t1.y ) / segment_height;
				drawLine( y, A, B );
			}
		}
	};

z-buffer

Доброго времени суток!

Хочу сказать спасибо за ваши статьи на хабре о компьютерной графике - это то, что нужно таким новичкам, как я.
И, как полагается, у меня возникли некоторые вопросы по статье №3, и, когда у вас появится свободное время, прошу ответить.

В комментариях вы привели пример картинки с "дырками", когда mr. guyfawkes спросил у вас насчет хака

    P.x = j; P.y = t0.y+i; // a hack to fill holes (due to int cast precision problems)

Вы указали, что это ошибка с округлениями, и добавили на репозиторий конструктор копирования для Vec3i с округлением.

В чем моя проблема: 1.png в прикрепленных
Вроде бы очень похоже на вашу картинку с "дырками". НО, я использую double представление точки, т.е. все коодинаты:

      struct vertice {
        double x;
        double y;
        double z;

т.е. как видите, округлений особо нет(и да, я понимаю, что это медленне по сравнению с int)

Поскольку у меня координаты типа [-1.0, 1.0], то и прохожусь я по по координатам с помощью такого костыля:

    for (double y = f.y; y < t.y; y += m_delta)

где m_delta - некое магическое число(шаг), которое для 1.png установлено в 0.001

если уменьшить шаг, то количество дырок уменьшается, но не пропадает(2.png - шаг установлен в 0.00035). Сами понимаете, что производительность падает при таком решении(у меня, для сравнения, 0.26105 сек для первого изображения, и 1.71324 сек для второго)

Нужна ваша конструктивная критика моих решений.
вот код метода рисования треугольника: github

1.png
2.png

Interesting Conclusion

Here is a quote from lesson 1:

We should note that each division has the same divisor. Let’s take it out of the loop. [...]

I can't understand how noting that each division has the same divisor helps us for making next improvements.

Heap buffer overflow in function vec<3ul, float>::normalize(float)

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause heap buffer overflow in function vec<3ul, float>::normalize(float) in geometry.h when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==18198==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x623000008d8c at pc 0x000000538fa2 bp 0x7ffd3d4292c0 sp 0x7ffd3d4292b8
READ of size 8 at 0x623000008d8c thread T0
    #0 0x538fa1 in vec<3ul, float>::normalize(float) /home/fouzhe/my_fuzz/tinyrenderer/./geometry.h:39:43
    #1 0x538fa1 in Model::normal(int, int) /home/fouzhe/my_fuzz/tinyrenderer/model.cpp:106
    #2 0x5480d4 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer/main.cpp:27:104
    #3 0x546295 in main /home/fouzhe/my_fuzz/tinyrenderer/main.cpp:82:24
    #4 0x7f93897a582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #5 0x41c188 in _start (/home/fouzhe/my_fuzz/tinyrenderer/main+0x41c188)

Address 0x623000008d8c is a wild pointer.
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fouzhe/my_fuzz/tinyrenderer/./geometry.h:39:43 in vec<3ul, float>::normalize(float)
Shadow bytes around the buggy address:
  0x0c467fff9160: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff9170: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff9180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff9190: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff91a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c467fff91b0: fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff91c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff91d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff91e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff91f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff9200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==18198==ABORTING

Вопрос

Здравствуйте.
http://habrahabr.ru/post/248723/#comment_8241713
1 Если у нас по 1 нормали к каждой точке, то мы неверно выразим негладкую поверхность (у которой нормали в особых точках вообще не существует). Вывод - к стыкам должно быть столько нормалей, сколько полигонов там стыкуются. То есть односторонние пределы. В вашем примере должно быть по 2 нормали: одна - на боковую сторону, другая - на верхнее основание. Тогда тонировки Фонга и Гуро должны дать одинаковые результаты в силу линейности умножения.
Как вообще получают нормали к особым точкам, если их не существует, а они нужны?

2 Если же возьмём по 1й нормали, то либо верхнее основание будет затонированно неправильно (если возьмём нормали к боковым сторонам), либо боковые стороны (если возьмём нормали к верхнему основанию), либо всё будет затонированно неправильно.

3 И неплохо бы заполнить хабрапрофиль, в частности контактные данные, так как не все могут на Хабре писать.

Rounding problem

Vec2i uvP = uvA + (uvB-uvA)*phi;

Hi,

i think we should make uvP is Vec3f. When i tried it with Vec3i, texture coordinates were rounded to int and they broken. it works with vec3f.

Heap buffer overflow found in function Model::uv

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause heap buffer overflow in function Model::uv when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==26495==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000004140 at pc 0x000000530bb9 bp 0x7ffc8eedd770 sp 0x7ffc8eedd768
READ of size 4 at 0x602000004140 thread T0
    #0 0x530bb8 in Model::uv(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:96:16
    #1 0x542798 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:26:44
    #2 0x54146b in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:82:24
    #3 0x7f1ec386e82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #4 0x41c098 in _start (/home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main+0x41c098)

0x602000004140 is located 4 bytes to the right of 12-byte region [0x602000004130,0x60200000413c)
allocated by thread T0 here:
    #0 0x517d58 in operator new(unsigned long) /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:92
    #1 0x5387e5 in __gnu_cxx::new_allocator<vec<3ul, int> >::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/new_allocator.h:104:27
    #2 0x53878b in __gnu_cxx::__alloc_traits<std::allocator<vec<3ul, int> > >::allocate(std::allocator<vec<3ul, int> >&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/alloc_traits.h:182:18
    #3 0x538452 in std::_Vector_base<vec<3ul, int>, std::allocator<vec<3ul, int> > >::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:170:20
    #4 0x53a36e in std::_Vector_base<vec<3ul, int>, std::allocator<vec<3ul, int> > >::_M_create_storage(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:185:33
    #5 0x539bb4 in std::_Vector_base<vec<3ul, int>, std::allocator<vec<3ul, int> > >::_Vector_base(unsigned long, std::allocator<vec<3ul, int> > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:136:9
    #6 0x5398df in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >::vector(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:319:9
    #7 0x53e337 in void std::_Construct<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_construct.h:83:38
    #8 0x53e268 in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::__uninitialized_copy<false>::__uninit_copy<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*>(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:75:3
    #9 0x53e218 in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::uninitialized_copy<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*>(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:123:14
    #10 0x53e1e8 in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::__uninitialized_copy_a<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:281:14
    #11 0x53bdcc in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::__uninitialized_move_if_noexcept_a<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > >(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:302:14
    #12 0x53906b in std::vector<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > >::_M_insert_aux(__gnu_cxx::__normal_iterator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > > >, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/vector.tcc:371:5
    #13 0x5322b8 in std::vector<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > >::push_back(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:925:4
    #14 0x52e590 in Model::Model(char const*) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:38:20
    #15 0x54133c in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:78:21
    #16 0x7f1ec386e82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:96:16 in Model::uv(int, int)
Shadow bytes around the buggy address:
  0x0c047fff87d0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff87e0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff87f0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8800: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8810: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
=>0x0c047fff8820: fa fa fd fd fa fa 00 04[fa]fa fd fd fa fa fd fd
  0x0c047fff8830: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8840: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8850: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8860: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8870: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==26495==ABORTING

Lesson 3 Extension: Source code for solution?

I'm trying to do the texture mapping extension from part 3 but I can't seem to get over the mental hurdle of interpolating the texture triangle to the 3D one. I'd appreciate having the source code to reference and fill in the gaps in my knowledge.

blank output.tga

I've made it to the end of lesson 1 and I don't know what I'm doing wrong. I've copied all the files, they're in the right places, but when I run main.cpp I don't get the wire frame of the head, it's just blank. What am I doing wrong? I suspect it's something to do with the arguments in main() but I don't know much about them or what to do with them.

Heap buffer overflow in function Model::uv

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause heap buffer overflow in function Model::uv in model.cpp when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==26495==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000004140 at pc 0x000000530bb9 bp 0x7ffc8eedd770 sp 0x7ffc8eedd768
READ of size 4 at 0x602000004140 thread T0
    #0 0x530bb8 in Model::uv(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:96:16
    #1 0x542798 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:26:44
    #2 0x54146b in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:82:24
    #3 0x7f1ec386e82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #4 0x41c098 in _start (/home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main+0x41c098)

0x602000004140 is located 4 bytes to the right of 12-byte region [0x602000004130,0x60200000413c)
allocated by thread T0 here:
    #0 0x517d58 in operator new(unsigned long) /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:92
    #1 0x5387e5 in __gnu_cxx::new_allocator<vec<3ul, int> >::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/new_allocator.h:104:27
    #2 0x53878b in __gnu_cxx::__alloc_traits<std::allocator<vec<3ul, int> > >::allocate(std::allocator<vec<3ul, int> >&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/alloc_traits.h:182:18
    #3 0x538452 in std::_Vector_base<vec<3ul, int>, std::allocator<vec<3ul, int> > >::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:170:20
    #4 0x53a36e in std::_Vector_base<vec<3ul, int>, std::allocator<vec<3ul, int> > >::_M_create_storage(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:185:33
    #5 0x539bb4 in std::_Vector_base<vec<3ul, int>, std::allocator<vec<3ul, int> > >::_Vector_base(unsigned long, std::allocator<vec<3ul, int> > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:136:9
    #6 0x5398df in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >::vector(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:319:9
    #7 0x53e337 in void std::_Construct<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_construct.h:83:38
    #8 0x53e268 in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::__uninitialized_copy<false>::__uninit_copy<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*>(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:75:3
    #9 0x53e218 in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::uninitialized_copy<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*>(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:123:14
    #10 0x53e1e8 in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::__uninitialized_copy_a<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:281:14
    #11 0x53bdcc in std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >* std::__uninitialized_move_if_noexcept_a<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > >(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > >&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_uninitialized.h:302:14
    #12 0x53906b in std::vector<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > >::_M_insert_aux(__gnu_cxx::__normal_iterator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >*, std::vector<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > > >, std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/vector.tcc:371:5
    #13 0x5322b8 in std::vector<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > >, std::allocator<std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > > >::push_back(std::vector<vec<3ul, int>, std::allocator<vec<3ul, int> > > const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:925:4
    #14 0x52e590 in Model::Model(char const*) /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:38:20
    #15 0x54133c in main /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/main.cpp:78:21
    #16 0x7f1ec386e82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fouzhe/my_fuzz/tinyrenderer_test/tinyrenderer/model.cpp:96:16 in Model::uv(int, int)
Shadow bytes around the buggy address:
  0x0c047fff87d0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff87e0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff87f0: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8800: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8810: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
=>0x0c047fff8820: fa fa fd fd fa fa 00 04[fa]fa fd fd fa fa fd fd
  0x0c047fff8830: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8840: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8850: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8860: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
  0x0c047fff8870: fa fa fd fd fa fa fd fd fa fa fd fd fa fa fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==26495==ABORTING

Heap buffer overflow in function vec<3ul, float>::norm

I used Clang 6.0 and AddressSanitizer to build tinyrenderer, this obj file with those tga file tga1,tga2,tga3 can cause heap buffer overflow in function vec<3ul, float>::norm in geometry.h when executing this command:

./main $POC(obj file only)

This is the ASAN information:

=================================================================
==32048==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x623000001903 at pc 0x00000053905d bp 0x7ffcd16f47e0 sp 0x7ffcd16f47d8
READ of size 8 at 0x623000001903 thread T0
    #0 0x53905c in vec<3ul, float>::norm() /home/fouzhe/my_fuzz/tinyrenderer/./geometry.h:38:41
    #1 0x53905c in vec<3ul, float>::normalize(float) /home/fouzhe/my_fuzz/tinyrenderer/./geometry.h:39
    #2 0x53905c in Model::normal(int, int) /home/fouzhe/my_fuzz/tinyrenderer/model.cpp:106
    #3 0x5480d4 in Shader::vertex(int, int) /home/fouzhe/my_fuzz/tinyrenderer/main.cpp:27:104
    #4 0x546295 in main /home/fouzhe/my_fuzz/tinyrenderer/main.cpp:82:24
    #5 0x7f9b5f1e782f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #6 0x41c188 in _start (/home/fouzhe/my_fuzz/tinyrenderer/main+0x41c188)

0x623000001903 is located 3 bytes to the right of 6144-byte region [0x623000000100,0x623000001900)
allocated by thread T0 here:
    #0 0x517e48 in operator new(unsigned long) /home/fouzhe/llvm/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:92
    #1 0x539a6d in __gnu_cxx::new_allocator<vec<3ul, float> >::allocate(unsigned long, void const*) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/new_allocator.h:104:27
    #2 0x539a6d in __gnu_cxx::__alloc_traits<std::allocator<vec<3ul, float> > >::allocate(std::allocator<vec<3ul, float> >&, unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/ext/alloc_traits.h:182
    #3 0x539a6d in std::_Vector_base<vec<3ul, float>, std::allocator<vec<3ul, float> > >::_M_allocate(unsigned long) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:170
    #4 0x539a6d in std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > >::_M_insert_aux(__gnu_cxx::__normal_iterator<vec<3ul, float>*, std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > > >, vec<3ul, float> const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/vector.tcc:353
    #5 0x53148d in std::vector<vec<3ul, float>, std::allocator<vec<3ul, float> > >::push_back(vec<3ul, float> const&) /usr/lib/gcc/x86_64-linux-gnu/5.4.0/../../../../include/c++/5.4.0/bits/stl_vector.h:925:4
    #6 0x53148d in Model::Model(char const*) /home/fouzhe/my_fuzz/tinyrenderer/model.cpp:19
    #7 0x5461f7 in main /home/fouzhe/my_fuzz/tinyrenderer/main.cpp:78:21
    #8 0x7f9b5f1e782f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/fouzhe/my_fuzz/tinyrenderer/./geometry.h:38:41 in vec<3ul, float>::norm()
Shadow bytes around the buggy address:
  0x0c467fff82d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff82e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff82f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c467fff8310: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c467fff8320:[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff8330: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff8340: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff8350: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff8360: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c467fff8370: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==32048==ABORTING

Lesson 2 return value of barycentric

Vec3f barycentric(Vec2i pts, Vec2i P) {
Vec3f u = cross(Vec3f(pts[2][0]-pts[0][0], pts[1][0]-pts[0][0], pts[0][0]-P[0]), Vec3f(pts[2][1]-pts[0][1], pts[1][1]-pts[0][1], pts[0][1]-P[1]));
/
pts and P has integer value as coordinates
so abs(u[2]) < 1 means u[2] is 0, that means
triangle is degenerate, in this case return something with negative coordinates */
if (std::abs(u[2])<1) return Vec3f(-1,1,1);
return Vec3f(1.f-(u.x+u.y)/u.z, u.y/u.z, u.x/u.z);
}

According to the derivation of the lesson 2,i supposed the return value should be Vec3f(1.f-(u.x+u.y)/u.z, u.x/u.z , u.y/u.z);
Am i right? or have i missed something here?

Question about lesson 3

I found the output wrong when using the following code to render:
image
The outcome turns out to be like this:
image
After reviewing the code in lesson 2 I got code as follow:
image
And this works fine, but I'm still confused, why is that?

Shouldn't suggest using -O3

-O3 for GCC is explicitly unsafe, enabling - among other things - non-standards compliant floating point maths.

Given what's being discussed here, why is -O3 being suggested as a good idea? -O2 or -Os are usually much safer.

Compiling with -O3 is not a guaranteed way to improve performance, and in fact in many cases can slow down a system due to larger binaries and increased memory usage. -O3 is also known to break several packages. Therefore, using -O3 is not recommended.

A better, safer option for local builds is -O2 -march=native -mtune=native, possibly also with -flto.

some question about the Perspective projection

In the chapter four:

So, if we want to compute a central projection with a camera (important!) camera located on the z-axis with distance c from the origin, then we embed the point into 4D by augmenting it with 1, then we multiply it with the following matrix, and retro-project it into 3D.

When it comes to chapters six,our eyes are based on (1, 1, 3) which is not the z-axis.And I wonder why this projection matrix is so simple.So I looked for some other materials,then found a more complicated matrix:
projection

After scanning articels,I replace ur matrix with the new ones(include changing the lookat matrix's fourth column).But I get a strange pic.Could u explain the difference between the matrix above and yours?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.