flutter_scene icon indicating copy to clipboard operation
flutter_scene copied to clipboard

Custom material shaders

Open bdero opened this issue 1 year ago • 0 comments

Implement a workflow for users to write shaders that customize managed lighting models. To start off, support standard (PBR) and unlit (blank slate) lighting models.

Why managed?

We could potentially go the route of making custom materials extremely simple by having the user straight up write the final Flutter GPU vertex/fragment shaders in their entirety. But such an approach would present an enormous usability tax on users:

  • In order to keep the animation system working, users would be forced to add a bunch of confusing boilerplate functions or macro calls in their shaders (or just not support customizing the vertex stage, which IMO would be an unacceptable limitation).
  • The vast majority of the time, users will want to customize an existing lighting model rather than entirely roll their own (but we'd still allow users to basically do this by providing an unlit lighting model). We can optimize for the common case by allowing the user to customize existing lighting models instead.

Design proposal

  • Users can import a MaterialLibrary using a Native Assets buildhook buildMaterialLibrary.
  • The descriptor handed to buildMaterialLibrary is a list of paths to .material files.
  • buildMaterialLibrary renders out a shader set for each material, with the vertex vertex stage containing at least two variations (for skinned and unskinned).
  • .material files are text files which contain:
    • A lighting_model metadata field to specify the lighting model. For starters, support standard (default, uses Flutter Scene's PBR lighting) and unlit (a blank slate). This determines the writable globals in the fragment stage. All globals are optional and have reasonable defaults. (The globals below loosely resemble's Godot's convention for specifying in/out parameters)
    • Writable fragment globals are as follows:
      • unlit exposes out vec4 COLOR global.
      • standard exposes out vec3 ALBEDO, out float ALPHA, out float METALLIC, out float ROUGHNESS, vec3 EMISSION, float AMBIENT_OCCLUSION, and vec3 NORMAL_MAP (where NORMAL_MAP is the tangent space normal, as opposed to the vertex normal -- similar to other varying attributes, vertex normals are accessible via in vec3 v_normal).
    • For vertex shaders, the basic globals include: inout vec3 MODEL_POSITION, inout vec3 NORMAL (note: no tangents are necessary, we currently work out the tangent space while rendering), and inout vec2 TEXTURE_COORDS. All of these globals are already set to the input geometry data before void vertex() (the user's code) is called. We can consider adding flags later for e.g. setting the world position directly instead.
    • Metadata lists specifying custom uniform inputs (including textures), and which stages each uniform intends to target) as well as shared varying parameters. Uniform block fields can have defaults.
    • A shader section, containing GLSL code.
      • The shader section can optionally contain void vertex() and/or void fragment().
      • The vertex/fragment stages have documented sets of globals available (as specified above).
  • At runtime, material libraries can be loaded with /* static */ MaterialLibrary.fromAsset('mylibrary.matlib').
  • Materials of the correct base type can be instantiated with matlib.createMaterial('custommat').
  • Internally, the Material is handed the vert/frag gpu.Shaders along with a custom uniform map.
  • Material provides functionality for setting custom uniform parameters (which are type checked in debug builds). E.g. material.setUniform(String name, dynamic value). Uniform layout and allocation management are internal concerns.

bdero avatar Sep 18 '24 23:09 bdero