1
\$\begingroup\$

I'm trying to integrate ECS design into my game engine.

Currently I'm trying to mimic the components of unity (although not pure ECS) with MeshFilter (holds Mesh object) and MeshRenderer (holds various attributes such as castShadows, receiveShadows etc..) components.

I also have MeshRenderer system which I defined such as that it gets all entities with MeshFilter and MeshRenderer components so it could basically render these meshes.

The logic of the MeshRenderer system would basically be:

  1. iterate the entities
  2. grab the Mesh from the MeshFilter
  3. grab the Material from the Mesh
  4. use relevant Textures and Shaders from the Material to draw the mesh

This is all fine but I wish to represent lights as entities as-well. I've made relevant components such as DirectionalLight, SpotLight etc.. that holds relevant information such as attenuation, color, shadow map (another object that holds RenderBuffer object of the shadow map) etc..

Now it seems to me that this MeshRenderer system should also take into account any entity with XXXLight components as-well since part of the rendering of an object is to also take into account any lights in the scene and also generate relevant shadows from/upon it.

Problem is that the system is defined to operate on entities with MeshFilter and MeshRenderer component and I'm not sure what to do. Should I make it so systems could operate on a list of entities with relevant components? Wouldn't it make the logic of the system's Update method a mess which entity is render-able mesh and which is light and then plug it all together?

I'm wondering what would be the best course of action here, what can I do?

\$\endgroup\$
2
  • 1
    \$\begingroup\$ It may help to break down the multiple steps in your rendering and think of them as separate passes. We don't just have one big function that does everything you might need to do with lights and meshes in some random interleaved order. If you're using deferred rendering for example you might first render all your opaque meshes to filll your G-buffer, with no lights. Then you'd render each light using that buffer (with a step in between to render the shadow-casting meshes in range in depth-only mode to form a shadow map for each shadow-casting light)... \$\endgroup\$
    – DMGregory
    Apr 1, 2020 at 12:15
  • \$\begingroup\$ I don't want to get into deferred rendering and all of that because I haven't learned about it yet. Right now I have 2 passes one for the shadow map and one for the rendering of the objects themselves. Taking that into account what would you suggest me to do ? \$\endgroup\$
    – Jorayen
    Apr 1, 2020 at 12:42

1 Answer 1

1
\$\begingroup\$

As DMGregory already pointed out, it might be beneficial to - instead of just one MeshRendering system as you proposed - setup / control the OpenGL state in some centralized RenderingPipeline, and execute specific systems at different points in this rendering pipeline.

For reference, I've had a similar problem some time ago, here's how I integrated rendering pipelines into my implementation of an ECS:

Next to the "usual" systems with an update method, there are 'RenderSystems', which are just systems with a render() method and a RenderStage field. The RenderStages are static enums like RENDER_STAGE_DEFERRED_GEOMETRY_PASS or RENDER_STAGE_DIRECTIONAL_LIGHTING etc.

For each render stage, the ECS holds a list of references to render systems that should run during that stage, and a method to run all render systems for a specific stage that can be called from some form of RenderingPipeline.

The RenderingPipeline takes care of setting up the OpenGL state for a render pass, e.g. binding the required buffers (e.g. a G-Buffer, or some form of Light- or Post-Processing-Framebuffer), and instructing the ECS to execute all systems in a specific RenderStage (e.g. the RenderingPipeline callsecs.render(RENDER_STAGE_DEFERRED_GEOMETRY_PASS once the G-Buffer is setup and bound, or combines all the rendered data (geometry buffer, shadow buffers, light buffers) into a final image after all systems have finished rendering by means of a filter / post-processing stage (which RenderSystems can also hook onto).

Some pros and cons I found when utilizing this approach:

  • Holding a set of references to another set of systems makes the implementation kind of clumsy and bloats the underlying core of the ECS

  • Rendering was split up into many different systems, making it potentially harder to get a good overview of the implementation

  • The systems allow entities to essentially render themselves when the correct OpenGL state has been set

  • Everything has to be orchestrated by the RenderingPipeline (never call ecs.render(RENDER_STAGE_POINT_SHADOW_PASS)? No Point-Light shadows for you.)

  • Adding aspects to an entity that require rendering became very easy (Want something to have a shadow? Attach a ShadowComponent to the Entity so that it is picked up by the (Directional/Point)ShadowRenderingSystem and rendered to a depth-buffer at a specific render stage)

Maybe this helps, maybe not, anyway, good luck :)

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .