pex-gl / pex-renderer

Physically based renderer (PBR) and scene graph for PEX.
https://pex-gl.github.io/pex-renderer/examples/index.html
MIT License
237 stars 16 forks source link

Provide default setup to avoid boilerplate code #316

Closed vorg closed 2 years ago

vorg commented 2 years ago

It would be good to have a shortcut for creating all the render graph, resource cache, systems and renderers in one call so the user can focus on entities and scene building. The question is how do we call this soup of systems and renderers? defaultSetup ?

const { defaultSetup } = require("pex-renderer")

//we don't need world here
const entities = []

entities.push(cubeMeshEntity)
entities.push(cameraEntity)
entities.push(eirectionalLightEntity)

const systemsAndStuff = defaultSetup()

ctx.frame(() => {
  systemsAndStuff.update(entities)
})
vorg commented 2 years ago

There are many names people use Scene + Renderer, Engine, World.

vorg commented 2 years ago

The most logical name would be defaultRenderer but wink wink it would actually consist of both systems and renderers so it is not accurate.

vorg commented 2 years ago
const ecs = defaultSetup()

ctx.frame(() => {
  ecs.update(entities)
})
const app = defaultSetup()

ctx.frame(() => {
  app.update(entities)
})
const engine = defaultSetup()

ctx.frame(() => {
  engine.update(entities)
})
vorg commented 2 years ago

Maybe it's just defaultSystems

const systems = defaultSystems()

ctx.frame(() => {
  systems.update(entities)
})
vorg commented 2 years ago

To clarify this is what the "default setup" looks like

module.exports = (node, graph) => {
  const {
    systems,
    renderGraph: createRenderGraph,
    resourceCache: createResourceCache,
  } = require("pex-renderer");

  const triggerIn = node.triggerIn("in");
  const triggerOut = node.triggerOut("out");

  const ctx = graph.ctx;

  const renderGraph = createRenderGraph(ctx);
  const resourceCache = createResourceCache(ctx);

  const renderPipelineSystem = systems.renderPipeline({
    ctx: graph.ctx,
    outputEncoding: graph.ctx.Encoding.Gamma,
    resourceCache,
    renderGraph,
  });

  const standardRendererSystem = systems.renderer.standard({
    ctx,
    resourceCache,
    renderGraph,
  });
  const basicRendererSystem = systems.renderer.basic({
    ctx,
    resourceCache,
    renderGraph,
  });
  const lineRendererSystem = systems.renderer.line({
    ctx,
    resourceCache,
    renderGraph,
  });
  const helperRendererSys = systems.renderer.helper({ ctx });
  const skyboxRendererSys = systems.renderer.skybox({ ctx });

  triggerIn.onTrigger = (props) => {
    const { entities, renderWidth, renderHeight } = props;
    resourceCache.beginFrame();
    renderGraph.beginFrame();

    const cameraEntity = entities.find((e) => e.camera);

    const renderView = {
      camera: cameraEntity.camera,
      cameraEntity: cameraEntity,
      viewport: [0, 0, renderWidth, renderHeight],
    };

    node.comment = cameraEntity.camera.fov + "\n" + renderView.viewport;

    renderPipelineSystem.update(props.entities, {
      renderView,
      renderers: [
        standardRendererSystem,
        // basicRendererSystem,
        lineRendererSystem,
        skyboxRendererSys,
        // helperRendererSys,
      ],
    });

    renderGraph.endFrame();
    resourceCache.endFrame();

    // node.comment = props.entities.length;

    triggerOut.trigger(props);
  };
};
vorg commented 2 years ago

So systems is already taken by internal pex-render namespace.

dmnsgn commented 2 years ago

Is it engine then?

const engine = defaultEngine()

ctx.frame(() => {
  engine.update(entities)
})
dmnsgn commented 2 years ago

Or SystemGroup. Which can be a system by itself, running a list of systems.

vorg commented 2 years ago

SystemGroup would still need to contain logic about system order and renderers to provide each system. In this case we could return compositeSystem so system made out of systems aka object implementing update() method

OR

we just push renderers to one long systems array and do

const defaultSystems = defaultSetup()
defaultSystems.forEach((system) => {
   system.update(entities, defaultSystems)
})

assuming that renderer systems have empty update method and that renderPipeline can find renderer systems on it's own (by checking renderStage prop)

vorg commented 2 years ago

@dmnsgn and would nodes have Scene and DefaultEngine in this case?

dmnsgn commented 2 years ago

@dmnsgn and would nodes have Scene and DefaultEngine in this case?

Scene and Engine


Am I missing a piece of the puzzle or would this work?

pex-renderer/systems/engine.js

export default function engine(ctx) {
  const renderGraph = createRenderGraph(ctx);
  const resourceCache = createResourceCache(ctx);

  const renderPipelineSystem = systems.renderPipeline({
    ctx,
    outputEncoding: ctx.Encoding.Gamma,
    resourceCache,
    renderGraph,
  });

  const standardRendererSystem = systems.renderer.standard({
    ctx,
    resourceCache,
    renderGraph,
  });
  const lineRendererSystem = systems.renderer.line({
    ctx,
    resourceCache,
    renderGraph,
  });
  const helperRendererSys = systems.renderer.helper({ ctx });
  const skyboxRendererSys = systems.renderer.skybox({ ctx });

  const renderers = [
    standardRendererSystem,
    lineRendererSystem,
    skyboxRendererSys,
    helperRendererSys,
  ];

  return {
    renderers,
    update(entities, { viewport }) {
      resourceCache.beginFrame();
      renderGraph.beginFrame();

      const cameraEntity = entities.find((e) => e.camera);

      const renderView = {
        camera: cameraEntity.camera,
        cameraEntity: cameraEntity,
        viewport,
      };

      renderPipelineSystem.update(entities, { renderView, renderers });

      renderGraph.endFrame();
      resourceCache.endFrame();
    },
  };
}

app.js

import { engineSystem } from "pex-renderer";
const engine = engineSystem({ ctx });

const entities = []
// entities.push(entity);

ctx.frame(() => {
  engine.update(entities);
})

Or are you saying it is not clean to update a system (renderPipelineSystem) inside another system (engineSystem)?

vorg commented 2 years ago

@dmnsgn you are not. It's exactly what i've implemented today https://github.com/pex-gl/pex-renderer/blob/304-port-to-ecs/default-engine.js https://github.com/pex-gl/pex-renderer/blob/304-port-to-ecs/examples/basic-engine.js

const engine = createDefaultEngine({ ctx });

ctx.frame(() => {
  const now = Date.now();
  const deltaTime = (now - prevTime) / 1000;
  prevTime = now;

  const cameraEntity = world.entities.find((e) => e.camera);

  engine.update(world.entities, deltaTime);
  engine.render(world.entities, cameraEntity);

  gui.draw();
});

It's just "engine" is not a word i've used in 10 years. And the package is not pex-engine but pex-renderer despite actually doing engine's work. So it feels like we are using concept that is bigger than the library itself.

I also quite like the look of helpers example (currently no longer running) but it implies that systems can setup themselves and find their dependencies (other systems and renderers)

world.addSystem(systems.geometry({ ctx }));
world.addSystem(systems.animation());
world.addSystem(systems.transform());
world.addSystem(systems.camera());
world.addSystem(systems.skybox({ ctx }));
world.addSystem(systems.reflectionProbe({ ctx }));
world.addSystem(systems.renderer({ ctx, outputEncoding: ctx.Encoding.Gamma }));
world.addSystem(systems.helper({ ctx }));

...

ctx.frame(() => {
  world.update();
  gui.draw();
});
vorg commented 2 years ago

There is also a question of how much of a kitchen sink that "engine" thing should be and should it handle:

vorg commented 2 years ago

To document thinking process especially in the context of Nodes

Screenshot 2022-09-22 at 11 59 40 DefaultEngine - pretends it to be a bit more than it is?

Screenshot 2022-09-22 at 11 59 21 DefaultSystems undersells it as it is also a renderer

Screenshot 2022-09-22 at 11 59 55 DefaultRenderer is not accurate as it has systems inside and multiple renderers (standard/pbr, lines, particles etc)

vorg commented 2 years ago

So maybe it should be GraphicsEngine - not a game engine and not just a renderer either as geometrySystem, animationSystem etc are part of defining "graphics". Or even better RenderEngine.

vorg commented 2 years ago

Implemented in https://github.com/pex-gl/pex-renderer/blob/304-port-to-ecs/render-engine.js