my C++ console 3D fps Game on Windows from scratch

Started by
11 comments, last by Khatharr 7 years, 11 months ago

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.

Advertisement

Awesome, would love to read some form of post-mortem, i.e unexpected difficulties, what went right/wrong and how you designed the software renderer.

Awesome, would love to read some form of post-mortem, i.e unexpected difficulties, what went right/wrong and how you designed the software renderer.

yeah sure, I would love to share.

>> color = Lerp(Background,Foreground,percent of coverage)

a clever re-mappiing.

reminds me of when i used to remap 24bit targa povray renders to 256 color palettized bitmaps for a 3D blitter engine.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Pretty cool! I’ve done something similar:


The color space was generated by combining a combination of foreground colors, background colors, and the three fill chars:

U+0032 ' ' 0%
U+2591 '?' 25%
U+2592 '?' 50%
U+2593 '?' 75%


After throwing out duplicates, there were about 360 unique colors. I then used voro++ to do fast nearest-neighbor lookup in 3d space.
Then the process was rendering to an 80x60 image, then transforming (r,g,b) to (foreground,background,char) and sending it to the GPU to render.


But now with Windows 10, the terminals are all different P:

Pretty cool! I’ve done something similar:



The color space was generated by combining a combination of foreground colors, background colors, and the three fill chars:

U+0032 ' ' 0%
U+2591 '?' 25%
U+2592 '?' 50%
U+2593 '?' 75%


After throwing out duplicates, there were about 360 unique colors. I then used voro++ to do fast nearest-neighbor lookup in 3d space.
Then the process was rendering to an 80x60 image, then transforming (r,g,b) to (foreground,background,char) and sending it to the GPU to render.


But now with Windows 10, the terminals are all different P:

Those 3 unicode chars seems too hard to find= =. But yeah, looking up color is implemented by finding the nearest color point in terms of Euclidean distance, as you said.

>> But yeah, looking up color is implemented by finding the nearest color point in terms of Euclidean distance

3d space color mapping is the standard method. i was using it too back in the mid to late 1990's.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

>> But yeah, looking up color is implemented by finding the nearest color point in terms of Euclidean distance

3d space color mapping is the standard method. i was using it too back in the mid to late 1990's.

Woa, seems that you got a lot of stories to tell :)

>> Woa, seems that you got a lot of stories to tell :)

the company slogan on my signature block is for real.

"building PC games since 1989"

and that's when i started publishing my games, not when i started making PC games. i started writing PC games in 1982. and my first real computer game ever was a lunar lander clone for the IBM 360 mainframe in 1978.

so yeah, you could say i've seen it all. all the way back to COBOL and punch cards and 24 hour turnaround times for submitting and running a job.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Now translate it into ANSI escape sequences and provide it on a port you can telnet to.

Stephen M. Webb
Professional Free Software Developer

This topic is closed to new replies.

Advertisement