Recently I have been working on a little game project to partake in a competition in my college, then I decided to develop a 3D game from scratch without any refercence to external libraries.
I finally Name it 'Shoot the Chicken 3D'
And I use C++ console to output (not with std::cout, but with Windows API WriteConsoleOutputCharacterA() and WriteConsoleOutputAttribute()
which means the project will contain 3 layer:
1, soft renderer (3D render pipieline)
2, game engine layer (Scene management, interfaces of renderer and pictures, etc.)
3,game logic
'Cos I have prior expericence on developing Game Engine, So I only have to develop layer 1 and 3 (which are still not easy= =).
I think the most challenging part of the my project is the soft renderer, a new module that I have never written. So It eventually took me 2 weeks to develop a soft renderer based on Rasterization, another 2 weeks for game logic.
Here are some of its screenshots:
[attachment=31842:19.JPG]
[attachment=31841:21.jpg]
[attachment=31847:23.jpg]
[attachment=31848:26.jpg]
[attachment=31843:26.jpg]
[attachment=31844:31.JPG]
[attachment=31845:32.JPG]
[attachment=31846:33.JPG]
Actually the Menus are presented with .PPM format bitmap picture which can be easily modified by PhotoShop.
here I'm gonna share some developing experience.
--Extension of Color Space
[attachment=31854:color.jpg]
Output characters to "draw" picture will definitely cause inferiority of graphic quality, especially when I knew that console only support 15 colors.
No, It would be really a disaster that a colorful scene would be rendered into an awful-looking image. So I took pains to come up with an algorithm that generate a palette with hundreds of colors. I abstract a console character as a PIXEL, thus I am capable of controlling the pixel color we saw by modifying FOREGROUND COLOR/ BACKGROUND COLOR and the CHARACTER.
[attachment=31851:8.jpg]
My idea: Foreground color stands for the color of character. An ASCII character can occupy several pixels of a rectangle, and different percent of area of coverage could be seen an operation of BLENDING. For example, If Font size is 3x5, a char '=' takes up 40% of total pixels, Foreground=RED, Background= BLACK, then we got an equivalent color of (0.4,0,0) here. Actually, between 15 preset colors, I can perform Linear Interpolation to generate new color for the palette,
color = Lerp(Background,Foreground,percent of coverage)
[attachment=31853:color_space_Original.jpg]
picture:15 preset colors in color space
--Soft Renderer
I had developped a fixed render pipeline whose API design is modelled after D3D9 -- A configurable state machine ('shaders' can be directly written with c++ in a soft renderer).If you have the experience in writing SHADERS, then Vertex Shader/Pixel Shader can be easily re-written into c++ language as a function.I might share some soft renderer developing experience as a learner~
So Let's review the basic stages a render pipeline have:
1?Vertex Transformation (Lighting can be performed here)
2?Polygon clipping
3?Rasterization
4?Per-pixel processing
5?Present to screen
Because after analysis of project needs and considering the limited time, I decided not to implement some complex graphic techniques.
1,Vertex Transformation
eh,Just do what you do when you are writing Vertex Shader. Transformation, vertex lighting ,etc. BUT, it seems that DirectX and openGL like to map the Z coordinate to a non-linearly distributed [0,1] homogeneous clipping space, I always wonder the reason why the designer did it like this, I GUESS that the resolution of Z coordinates could be enhanced near the camera position using this method. I mean, D3DX generates a perspective projection matrix will make 1/z linearly distributed on [0,1].
This is the D3DX version perspective project matrix according to 'Introduction to Game Programming with DirectX11'
float term11 = 1.0f / (aspectRatio*tanf(ViewAngleY / 2.0f));
float term22 = 1.0f / tanf(ViewAngleY / 2.0f));
float term33 = FarPlaneZ / (FarPlaneZ - NearPlaneZ);
float term34 = -NearPlaneZ*FarPlaneZ / (FarPlaneZ - NearPlaneZ);
MATRIX4x4 outMatrix;
outMatrix.SetRow(0, { term11,0,0,0 });
outMatrix.SetRow(1, { 0,term22,0,0 });
outMatrix.SetRow(2, { 0,0,term33,term34 });
outMatrix.SetRow(3, { 0,0,1,0 });
But I make an experiment to write my own version of perspective projection matrix which map view space Z to a linearly distributed region [0,1].And I didn't store a Z coord in W coord, in soft renderer I can declare another temporary variable to store the view space Z coord before multiply the projection matrix. At last, x,y coordinate will be divided by their own view space Z coord.
float term11 = 1.0f / (aspectRatio*tanf(ViewAngleY / 2.0f));
float term22 = 1.0f / tanf(ViewAngleY / 2.0f);
float term33 = 1.0f / (FarPlaneZ - NearPlaneZ);
float term34 = -NearPlaneZ / (FarPlaneZ - NearPlaneZ);
MATRIX4x4 outMatrix;
outMatrix.SetRow(0, { term11,0,0,0 });
outMatrix.SetRow(1, { 0,term22, 0,0 });
outMatrix.SetRow(2, { 0,0,term33,term34 });
outMatrix.SetRow(3, { 0,0,1,0 });
2,Polygon Clipping
At first, I directly skip the step!!! In the first place I think that clipping triangles is just an optimization--- invisible parts of triangles can be quickly dumped before more detailed processing. But later I realize that this stage is CRUCIAL to get correct result, not only for optimizing purpose.
[attachment=31860:1.png]
This is a top-view picture, red line represent a triangle, especially under such circumstances I always encounter big problem that the triangle is twisted and STRETCH ACROSS the screen. I don't know the cause of this problem analytically, but It seems that this problem can be resolved by triangles clipping.
[attachment=31861:3.jpg]
But intersection between triangle and box could be complicated. So I violently discard the triangle if 3 vertices are all out of boundary. It's really a rough method...
3,Rasterization
until now, I have got many triangles in homogeneous space. I haven't read any books about developing soft renderer, so my own method of rasterization may not be good enough, but it did work.
[attachment=31862:4.jpg]
First, homo space coordinate will be transformed into pixel space coord, then I will apply Line-Triangle intersection where the line has Y coordinate with integer value. Such intersection might yield 0/1/2 intersect point. If I got 2 points, whose X coordinates are X1 and X2, a row of pixels can be drawn within region [floor(x1),floor(x2)] . Someone hint me that use Bresenham's method to rasterize edges first, then to fill the region could be better, maybe= =.
For every pixel rasterized, I will compute the bilinear interpolation coordinate (s,t) for them by solving a 2-unknowns linear equation.
[attachment=31864:5.jpg]
then don't forget perspective correct interpolation using (s,t) and depth of 3 vertices of triangle. Remember to use perspective correct interpolation to compute the depth of current pixel , and then, perform depth-test(z-test).
4,per-pixel processing
Right now, we got a bunch of pixels. Texture mapping is performed at this stage. Texture Coordinate had been interpolated, color can be extracted using various filter mode.
5,Present to screen
...yeah, present to screen.