composer-suite

repository·main·Indexed 20 days ago

https://github.com/hmans/composer-suite

A suite of specialized libraries for game development using React and Three.js. The monorepo includes shader-composer for functional shader authoring, render-composer for render pipeline and post-processing management, r3f-stage for creating navigable example galleries, and @hmans/r3f-create-loader for centralized asset preloading and management.

Tokens
52.1K
Snippets
191
Records
255
Agent score
69%

What's inside composer-suite

  1. Overview of VFX Composer

    main

    VFX Composer is a visual effects library designed for Three.js and @react-three/fiber (via the vfx-composer-r3f package). It enables the declarative construction of complex visual effects that are compiled into GPU-executed shaders using the Shader Composer library.

    Warning: This library is a work-in-progress and is not considered stable. APIs may break in future releases. Use at your own risk.

  2. Overview of Composer Suite

    main

    Composer Suite is a collection of libraries designed to streamline game development using React and Three.js (specifically React-Three-Fiber). It provides specialized tooling for various game development domains, including:

    • GPU-driven VFX and particle systems
    • Game UI and screen-space elements
    • Camera rigs and automation
    • Input management for multi-device setups
    • State management helpers
    • Shader and Material composition
    • Render pipelines
    • Audio
    • Animation orchestration

    While most libraries are optimized for React, some are designed to be used with vanilla Three.js or outside of the React ecosystem.

  3. Understand Emitter behavior and scene integration

    main

    Emitters as Scene Objects

    <Emitter> components are actual Three.js scene objects. This means:

    • You can animate them using standard Three.js animation tools.
    • You can parent them to other objects in the scene.
    • Newly spawned particles inherit the current position, rotation, and scale of the emitter at the moment of emission.

    Multiple Emitters

    • You can use multiple <Emitter> components with different configurations (rates, limits, or setup callbacks).
    • Important: All particles spawned by multiple emitters will belong to the same <Particles> instance they are connected to. Ensure your <Particles> component has sufficient capacity to handle the combined total of all emitters.
  4. How Shader Composer works

    main

    Shader Composer is a code-first library for authoring Three.js shaders using a functional JavaScript API. It is modeled after node-based shader tools (like Unity's Shader Graph) but uses code to define the graph.

    Core Workflow

    1. Define a Shader Graph: Create a tree of shader units. The root of this tree is typically a master unit (e.g., ShaderMaterialMaster).
    2. Compile: Use the compileShader function to transform the unit tree into a compiled shader object.
    3. Apply: Plug the resulting shader into a THREE.ShaderMaterial or use three-custom-shader-material to inject it into existing Three.js materials.

    Basic Example

    // 1. Define the root (master unit)
    const root = ShaderMaterialMaster({
      color: new Color("hotpink")
    })
    
    // 2. Compile the shader
    const [shader] = compileShader(root)
    
    // 3. Use with Three.js
    const material = new THREE.ShaderMaterial(shader)
    const [shader] = compileShader(root)
    const material = new THREE.ShaderMaterial(shader)
  5. How depth and scene buffers work in Render Composer

    main

    The <RenderPipeline> component splits rendering into two passes: a scene pass and an effects pass. This allows you to create objects (like water or volumetric fog) that require access to the scene's depth or color textures.

    To use these buffers, you must:

    1. Set the target meshes to the Layers.TransparentFX layer using the layers-mask prop. This moves them from the scene pass to the effects pass.
    2. Use the useRenderPipeline hook to retrieve the depth and color textures.
    const StylizedWater = () => {
      const { depth, color } = useRenderPipeline()
    
      return <mesh layers-mask={1 << Layers.TransparentFX}>{/* ... */}</mesh>
    }
    const StylizedWater = () => {
      const { depth, color } = useRenderPipeline()
    
      return <mesh layers-mask={1 << Layers.TransparentFX}>{/* ... */}</mesh>
    }
  6. Understand Unit<T> and Input<T> types

    main

    When authoring complex units or functions, Shader Composer uses two primary TypeScript types to ensure type safety and flexibility:

    • Unit<T>: Represents an actual shader unit of GLSL type T. It is a functional node in the graph.
    • Input<T>: Represents a value that can be passed into a unit. An Input<T> can be either a Unit<T> or a plain JavaScript value of type T.

    This distinction allows you to write functions that accept both raw values (like a THREE.Color) and complex sub-graphs (like the output of a Sin() unit) interchangeably.

  7. Core Concepts of VFX Composer

    main

    VFX Composer is built around three primary abstractions:

    1. Particles 🎆: A highly optimized particle system engine built on THREE.InstancedMesh. It supports any geometry and is typically used in conjunction with a VFXMaterial to handle animation.
    2. VFXMaterial 🎨: A custom material used to render visual effects. It accepts a list of effect modules which are compiled into a single shader. VFXMaterial can extend standard Three.js materials (like THREE.ShaderMaterial or THREE.MeshPhysicalMaterial) and can inject shaders into materials loaded from external files like GLTF.
    3. Effect Modules 🎁: Functions that transform attributes such as position, color, and opacity. These modules can be chained together to create complex behaviors. VFX Composer provides a built-in library of modules, but users can also implement custom ones.
  8. Quickstart: Setting up Render Composer

    main

    To use Render Composer in a react-three-fiber project, wrap your scene in <RC.Canvas> and <RC.RenderPipeline>. This provides a pre-configured render pipeline with sane defaults and access to depth/color buffers.

    import * as RC from "render-composer"
    
    function App() {
      return (
        <RC.Canvas>
          <RC.RenderPipeline>
            {/* Normal R3F content goes here */}
            <directionalLight position={[30, 10, 10]} intensity={1.5} />
            <mesh>
              <icosahedronGeometry />
              <meshStandardMaterial color="hotpink" />
            </mesh>
          </RC.RenderPipeline>
        </RC.Canvas>
      )
    }
    import * as RC from "render-composer"
    
    function App() {
      return (
        <RC.Canvas>
          <RC.RenderPipeline>
            <directionalLight position={[30, 10, 10]} intensity={1.5} />
            <mesh>
              <icosahedronGeometry />
              <meshStandardMaterial color="hotpink" />
            </mesh>
          </RC.RenderPipeline>
        </RC.Canvas>
      )
    }
  9. Quickstart with Application and global styles

    main

    Import Application from r3f-stage to wrap your react-three-fiber components.

    Important: r3f-stage provides a global stylesheet. You must import r3f-stage/styles.css in your application and remove any other conflicting global styles.

    import { Application } from "r3f-stage"
    import "r3f-stage/styles.css"
    
    function App() {
      return (
        <Application>
          <mesh>
            <dodecahedronGeometry />
            <meshStandardMaterial />
          </mesh>
        </Application>
      )
    }
  10. Set up shader-composer-three for Three.js

    main

    To use Shader Composer with Three.js, you must import the configureThree function from @shader-composer/three and call it to initialize the Three.js bindings. This is typically done alongside compileShader from @shader-composer/core to prepare your environment for shader compilation.

    import { compileShader } from "@shader-composer/core"
    import configureThree from "@shader-composer/three"
    
    configureThree()
  11. Transition between states using `.enter()`

    main

    To move the state machine to a new state, use the .enter(state) function.

    State Composer does not provide built-in transition guards or complex transition definitions. Instead, it encourages implementing transitions as standard JavaScript functions. You can combine state checks, side effects, and transitions using logical operators or standard if statements.

    Common Patterns:

    • Standard Function: Use if statements to check the current state and execute side effects before calling .enter().
    • Logical Chaining: Use && to create concise transitions that only execute .enter() if a condition (like .is()) is met.
    • Guard Functions: Implement custom boolean functions to act as guards within your logic chains.
    /* Pattern 1: Standard imperative function */
    export const enterGameplay = () => {
      if (!GameState.is("menu")) return
      initializeGameplay()
      GameState.enter("gameplay")
    }
    
    /* Pattern 2: Concise logical chaining */
    export const returnToTitle = () =>
      GameState.is("gameplay") && GameState.enter("title")
    
    /* Pattern 3: Using custom guard functions */
    const canStartGame = () => { /* returns boolean */ }
    
    export const startGame = () =>
      GameState.is("menu") && canStartGame() && GameState.enter("title")