Jump to content
  • entries
    2
  • comments
    14
  • views
    951

My C++ Game Engine from scratch

frankoguy

2921 views

Hello there.  I'm not really the blogging type.  This is my first ever blog.  So I'll do my best. 

 

I've been trying to showcase my video game engine from scratch in different professional forums with scant mixed results. 

 

I’m currently a happily employed 3D Graphics Programmer in the medical device field who also loves creating graphics programs as a side hobby.

 

It's been my experience that most people who aren't graphics programmers simply don't appreciate how much learning goes into simply being able to code a small fraction of this from scratch.  Most viewers will simply compare this to the most amazing video game they’ve ever seen (developed by teams of engineers) and simply dismiss this without considering that I’m a one-man show.

What I’m hoping to accomplish with this:  I’m not totally sure.  I spent a lot of my own personal time creating this from the ground up using only my own code (without downloading any existing make-my-game-for-me SDKs), so I figured it’s worth showing off.

My design: Oct Tree for scene/game management (optimized collision-detection and scene rendering path) from scratch in C++. 

 

1.      All math (linear algebra, trig, quaternion, vectors), containers, sorting, searching from scratch in C++.

2.      Sound system (DirectSound, DirectMusic, OpenAL) from scratch in C++.

3.      Latest OpenGL 4.0 and above mechanism (via GLEW on win32/win64) (GLSL 4.4).  Very heavy usage of GLSL.

 

Unusual/skilled special effects/features captured in video worth mentioning:

1.       Volumetric Explosions via vertex-shader deformed sphere into shock-wave animation further enhanced with bloom post-processing (via compute shader).

2.       Lens Flare generator, which projects variable edge polygon shapes projected along screen-space vector from center of screen to light-position (again, in screen-space: size and number of flares based on intensity and size of light source).

2.      Real-time animated procedural light ray texture (via fragment shader) additively blended with Volumetric explosions.

3.      Active camouflage (aka Predator camouflage).

4.      Vibrating shield bubble (with same sphere data in Volumetric explosion) accomplished using a technique very similar to active camouflage

5.      Exploding mesh: When I first started creating this, I started out using fixed-function pipeline (years ago).  I used one vertex buffer, one optimized index buffer, then another special unoptimized index buffer that traces through all geometry one volume box at a time.  And each spaceship “piece” was represented with a starting and ending index offset into this unoptimized index buffer.  Unfortunately, the lower the poly-resolution, the more obvious it is what I was doing.  Unfortunately, as a result, when the ship explodes you see the triangle jaggies on the mesh edges.  My engine is currently unfortunately somewhat married to this design—which is why I haven’t redesigned that part yet (It’s on the list, but priorities).  If I were to design this over again, I’d simply represent each piece with a different transform depending upon whether or not the interpolated object-space vertex (input to the pixel shader) was in-front of or behind an arbitrary “breaking plane”.  If the position was beyond the breaking point boundary planes, discard the fragment.  This way, I can use one vertex buffer + one optimized index buffer and achieve better looking results with faster code.

 



14 Comments


Recommended Comments

Love reading about this sort of thing - we have a few community members who do lower level development and should appreciate your effort here!

Share this comment


Link to comment
On ‎12‎/‎23‎/‎2017 at 10:45 AM, Spencer Lockhart said:

Wow that's awesome, Frank! Please keep us updated

Better cloaking transitions, improved light rays and lens flares.  Upcoming: working on way to guarantee light rays render ontop of occluding opaque fragments if vector from eye to light source is unobstructed (TBD).

Share this comment


Link to comment

Many subtle improvements.  Light Ray occlusion fix:  If Center of light source is occluded by foreground object, will not render light ray.  If light center is not occluded, will render light rays on-top of opaque fragments.  How to do this: render light rays into separate render target.  In addition to standard color output, encode light source center screen-space fragment coord into every output pixel in separate render target (every single light ray pixel for the current light ray will have the same screen-space encoded light center coord: pos_light=g_mProj*g_mView*light_world.  encoded_color=(pos_light.xyz/pos_light.w+1.0)/2.0).  Also, do the same with other opaque fragments for space-ships and such in another separate render target (or make sure you have access to depth buffer).  After rendering standard scene into final color output buffer, render the final light ray texture like so (must have access to standard light ray color buffer and encoded light center fragment positions.  Next, render light-ray texture in screen-space (fill up entire viewport with texture: use standard identity normalized screen coordinates: i.e. (-1,1) (1,1),(1,-1),(-1,-1) to fill up viewport with quad, and use these same coordinates as texture coordinates (i.e. (coord+1.0)/2.0 ).  For every texture coord, lookup the encoded screen-space light position, then use that encoded screen-space light position as a texture coord (already stored as texture coordinate) to look up the foreground space-ship fragment at the screen space light center position.  Do a depth comparison of the encoded screen-space light position to the encoded screen-space opaque fragment position (or do depth lookup into depth buffer)  (i.e. use the looked up screens-space light center fragment coordinate as a texture coordinate lookup into the opaque color-encoded screen-space position buffer).  if center light depth is greater than current opaque fragment depth, then discard the current light-ray fragment (will discard all light ray fragments in light ray image corresponding to this center light position): if(decoded_light_ray_screenspace.z (already z/w) > opaque_fragment_pos.z (also already z/w)) discard;

Edited by frankoguy

Share this comment


Link to comment

These might get a bit more attention as new blog entries rather than just adding comments. :)

Share this comment


Link to comment

@frankoguy thanks a lot for this post is really cool an very interesting, please continue writing i have much of learn with your knowledge and experience

anim.gif

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By CommanderLake
      I've been experimenting with my own n-body simulation for some time and I recently discovered how to optimize it for efficient multithreading and vectorization with the Intel compiler. It did exactly the same thing after making it multithreaded and scaled very well on my ancient i7 3820 (4.3GHz). Then I changed the interleaved xy coordinates to separate arrays for x and y to eliminate the strided loads to improve AVX scaling and copy the coordinates to an interleaved array for OpenTK to render as points. Now the physics is all wrong, the points form clumps that interact with each other but they are unusually dense and accelerate faster than they decelerate causing the clumps to randomly fly off into the distance and after several seconds I get a NaN where 2 points somehow occupy exactly the same x and y float coordinates. This is the C++ DLL:
      #include "PPC.h" #include <thread> static const float G = 0.0000001F; const int count = 4096; __declspec(align(64)) float pointsx[count]; __declspec(align(64)) float pointsy[count]; void SetData(float* x, float* y){ memcpy(pointsx, x, count * sizeof(float)); memcpy(pointsy, y, count * sizeof(float)); } void Compute(float* points, float* velx, float* vely, long pcount, float aspect, float zoom) { #pragma omp parallel for for (auto i = 0; i < count; ++i) { auto forcex = 0.0F; auto forcey = 0.0F; for (auto j = 0; j < count; ++j) { if(j == i)continue; const auto distx = pointsx[i] - pointsx[j]; const auto disty = pointsy[i] - pointsy[j]; //if(px != px) continue; //most efficient way to avoid a NaN failure const auto force = G / (distx * distx + disty * disty); forcex += distx * force; forcey += disty * force; } pointsx[i] += velx[i] -= forcex; pointsy[i] += vely[i] -= forcey; if (zoom != 1) { points[i * 2] = pointsx[i] * zoom / aspect; points[i * 2 + 1] = pointsy[i] * zoom; } else { points[i * 2] = pointsx[i] / aspect; points[i * 2 + 1] = pointsy[i]; } /*points[i * 2] = pointsx[i]; points[i * 2 + 1] = pointsy[i];*/ } } This is the relevant part of the C# OpenTK GameWindow:
      private void PhysicsLoop(){ while(true){ if(stop){ for(var i = 0; i < pcount; ++i) { velx[i] = vely[i] = 0F; } } if(reset){ reset = false; var r = new Random(); for(var i = 0; i < Startcount; ++i){ do{ pointsx[i] = (float)(r.NextDouble()*2.0F - 1.0F); pointsy[i] = (float)(r.NextDouble()*2.0F - 1.0F); } while(pointsx[i]*pointsx[i] + pointsy[i]*pointsy[i] > 1.0F); velx[i] = vely[i] = 0.0F; } NativeMethods.SetData(pointsx, pointsy); pcount = Startcount; buffersize = (IntPtr)(pcount*8); } are.WaitOne(); NativeMethods.Compute(points0, velx, vely, pcount, aspect, zoom); var pointstemp = points0; points0 = points1; points1 = pointstemp; are1.Set(); } } protected override void OnRenderFrame(FrameEventArgs e){ GL.Clear(ClearBufferMask.ColorBufferBit); GL.EnableVertexAttribArray(0); GL.BindBuffer(BufferTarget.ArrayBuffer, vbo); mre1.Wait(); are1.WaitOne(); GL.BufferData(BufferTarget.ArrayBuffer, buffersize, points1, BufferUsageHint.StaticDraw); are.Set(); GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 0, 0); GL.DrawArrays(PrimitiveType.Points, 0, pcount); GL.DisableVertexAttribArray(0); SwapBuffers(); } These are the array declarations:
      private const int Startcount = 4096; private readonly float[] pointsx = new float[Startcount]; private readonly float[] pointsy = new float[Startcount]; private float[] points0 = new float[Startcount*2]; private float[] points1 = new float[Startcount*2]; private readonly float[] velx = new float[Startcount]; private readonly float[] vely = new float[Startcount];  
      Edit 0: It seems that adding 3 zeros to G increases the accuracy of the simulation but I'm at a loss as to why its different without interleaved coordinates. Edit 1: I somehow achieved an 8.3x performance increase with AVX over scalar with the new code above!
    • By DarkRonin
      Hi Guys,
      If your application requires a DLL, is there a way to check that the DLL is present gracefully rather than the typical error that gets thrown?  For example - 'The code execution cannot proceed because xxxxxxxx.dll was not found. Reinstalling the program may fix this problem'.
      In my case the DLL is my own, if that makes any difference.
      Thanks in advance.
       
    • By Acar
      I'm trying to use Assimp to convert a custom format to .FBX. I was abled to export model geometry, bone hierarchy and weights correctly. Rotation of the bones appear to be correct as well.
      My problem is, none of the bones have any translation (0,0,0). So when loaded into Maya, entire bone hierarchy appears as a single dot at the origin. Any ideas as to what I may be doing wrong?
    • By _WeirdCat_
      so I have 3 classes
      main class which stores a pointer
      struct Main { int * p; A subclass1, subclass2; void init() { subclass1.init(p); subclass2.init(p); } }; and two subclasses struct A { int * p; void init(int * ap) { p = ap; } }; Now I would like to create a pointer from subclass1 or subclass2 that will actually create a pointer in Main class, so subclasses can use the same int from main class
      heres what im trying to achieve
      in first scenario you see same windows so you see that on phone1 and phone2 theres a blue wireframed object that im trying to share between multiple windows, but when I change projection window I cant see it, so I need a shared pointer
      https://youtu.be/9HLDsrm7R2w
       
      I already messed with int ** p; for subclasses but im missing something (maybe because int * p in main class is 0)
      and I cant assign it by int ** subp = (*mainclass.p);
       
       
      if someone tells me that I could use shared ptr then I ask him to provide some example code for me cause I never used it and have no xp with it.
      However pointer to pointer c style would be the best approach for me thus theres something wrong in the code I dont fully understand so far, int * p in main class has to be 0 until its not created by subclass...
      maybe I somewhere use it where I shouldnt, well I just changed the code from * to ** and just switched from if (p ==0) return;
      to if ( (*p) == 0) return; etc.
      but theres few thousands lines I need to check so a short brief of handling such types would be appreciated..
    • By phil67rpg
      well I am able to get my sprites to rotate and move in all directions, I have drawn two plane sprites, I am also able to shoot a bullet in the up direction, I want to shoot bullets in all directions just like my plane rotates, I just need a hint on how to proceed, go easy on me this is new stuff to me. However I am making progress.
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!