Tuesday, February 7, 2017

Particles and dynamic lights

My original plan with particles is to implement it on the GPU with vertex shader or compute shader.  However,  after the completion of texture mapping, I realized that particles and dynamic lights were the only things left before reaching feature parity with the software rasterizer.  So I adjusted my plan, and decided I would just implement particles and dynamic lights in the most straightforward way, so that I can reach the feature parity milestone as quickly as possible, and then worry about all the fancy stuff later.

Particles becomes almost trivial once I decided to do it the easy way.  The entire particle emitting and updating stuff is implemented in r_part.c file. All I need to rewrite is 3 functions:

D_StartParticles : where I clear the previous frame's particles in my particle container.

D_DrawParticle: where I append one particle to my particle container.

D_EndParticles: where I upload the particle container as a vertex buffer and issue a glDrawArrays(GL_POINTS, ...) call.

The shaders are also extremely easy.  All the vertex shader  needs to do is adjusting the
point sprite size gl_PointSize base on the distance of the particle to the view point.  And the fragment shader simply copies the input color to to output.

The last missing feature is dynamic lights.

Dynamic lights sounds difficult but it really isn't.  In fact I would even say it's one of the easiest features to implement.

All it takes is uploading the dynamic light positions to a uniform array (in R_PushDlights(), there are up to 32 dlights in Quake).  Then in the fragment, go through all dlights, compute the light contribution with a dot product of the normal vector and light position, adjusted by the light fall off base on square of light distance to the current fragment.   This is actually already quite a bit better than what the original Quake did in its software renderer, where it does the dlights on the low-res light maps and doesn't use the normal vector and doesn't compute the fall off.

Although the implementation is simple,  the result looks quite nice: