|
@@ -0,0 +1,416 @@
|
|
|
|
+# RFC: Material and Lighting Shaders
|
|
|
|
+
|
|
|
|
+## Summary
|
|
|
|
+
|
|
|
|
+This proposal outlines a new abstraction in Atom to decouple material and light evaluation, from the underlying rendering pipeline (e.g. deferred, forward, visibility buffer, etc.), enabling material reuse between different pipelines (within or across different projects) and even simplifying the implementation of new material types (composite or multi-layered materials).
|
|
|
|
+
|
|
|
|
+A simple way to understand this proposal is to consider one of the primary goals of PBR, which is to decouple the material parameterization, from the lighting evaluation. Taking advantage of this decoupling in the rendering model requires that the representation of a material itself also be decoupled. Currently however, materials are coupled not only to lighting, but also to the geometric representation of the material. The goal of this RFC is to introduce the first of several steps needed to ensure that materials in the engine strictly encapsulate the physical properties of the shaded object.
|
|
|
|
+
|
|
|
|
+## What is the relevance of this feature?
|
|
|
|
+
|
|
|
|
+The Atom renderer that ships with O3DE currently provides a *shader-centric* workflow for authoring materials and lights. A future *material canvas* feature is planned that will add support for *node-editing* workflows for authoring materials, but the current shader-centric workflow is intended to be maintained (in addition to graph-based authoring of full shaders). The current problem can be simply stated by observing that the implementation of material and lighting algorithms is strongly coupled to the shaders used to author them. Concretely, the rendering pipeline for the standard or enhanced PBR models today are implemented in a Forward+ rendering pipeline (with some screen-space targets as needed for various techniques, e.g. motion buffer). This applies to PBR materials that are driven by texture data (detail maps, control textures), and also materials that are procedurally shaded.
|
|
|
|
+
|
|
|
|
+Without architectural changes, the material and light implementations cannot be easily ported to alternative rendering pipelines (e.g. deferred, visibility buffer, etc.). This is especially important for workloads that need to render high-density meshes (the Achilles’ heel of a Forward+ pipeline, due to quad-dispatch inefficiencies), but also restricts research into alternative rendering pathways. Over time, materials authored using the current abstraction are liable to ossify architectural decisions made early.
|
|
|
|
+
|
|
|
|
+**Ideally, O3DE should provide an abstraction that allows engineers and artists to define how a material is shaded and lit *once*, and leverage this abstraction to drive *any* particular rendering pipeline (be it forward, deferred, v-buffer, ray tracing, or any other future technique).**
|
|
|
|
+
|
|
|
|
+## Feature Design Description
|
|
|
|
+
|
|
|
|
+The primary mechanism to achieve the goal outlined above is to provide two new *logical* shader types that shade at material and lighting frequency respectively. Note that unlike normal shaders which shade at a GPU-oriented frequency (vertex, pixel, ray, etc.), the material and light shaders shade at an indeterminate rate, not defined by any GPU abstraction. This decoupling will enable us to author materials and lights that can be *repurposed* in the context of any rendering pipeline we could imagine (barring capabilities of the rendering pipeline itself, e.g. transparent rendering in deferred).
|
|
|
|
+
|
|
|
|
+Let’s look at a simple example of how this might work:
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+struct FragmentData
|
|
|
|
+{
|
|
|
|
+ float3 position;
|
|
|
|
+ float3 normal;
|
|
|
|
+ float3 tangent;
|
|
|
|
+ float3 bitangent;
|
|
|
|
+ float3 worldPosition;
|
|
|
|
+ float2 uv;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct SurfaceData
|
|
|
|
+{
|
|
|
|
+ float4 baseColor;
|
|
|
|
+ float roughness;
|
|
|
|
+ bool metallic;
|
|
|
|
+ float3 normal;
|
|
|
|
+ float4 emissive;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+SurfaceData MainMS(FragmentData fragmentData)
|
|
|
|
+{
|
|
|
|
+ SurfaceData surfaceOut;
|
|
|
|
+ // Material shading code
|
|
|
|
+ return surfaceOut;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// In this example, we just define a simple directional light
|
|
|
|
+// More sophisticated data could be used to support more complicated
|
|
|
|
+// light types, with attenuation radii, falloff parameters, tints,
|
|
|
|
+// etc.
|
|
|
|
+struct DirectionalLightData
|
|
|
|
+{
|
|
|
|
+ float3 direction;
|
|
|
|
+ float intensity;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct LightOutput
|
|
|
|
+{
|
|
|
|
+ float4 diffuse;
|
|
|
|
+ float4 specular;
|
|
|
|
+ float4 albedo;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+LightOutput MainLS(SurfaceData surfaceData, DirectionalLightData lightIn)
|
|
|
|
+{
|
|
|
|
+ LightOutput lightOut;
|
|
|
|
+ // Light evaluation code
|
|
|
|
+ return lightOut;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// Other entry points could be defined for other light types
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+In the code sample above, we introduce two new logical entry points corresponding to the material and lighting entry points (MainMS and MainLS respectively). The argument to MainMS is expected to be the output of a vertex shader *after* interpolation (FragmentData in this example). In a typical deferred or forward pipeline, this data is produced directly by interpolating vertex attributes with the built-in interpolators. In a visibility-buffer approach, these interpolants are produced manually by other means (e.g. encoding or deriving barycentric coordinates and performing a full triangle fetch in a shader). MainMS produces the surface data needed to evaluate the BRDF in the MainLS. Note that the light shader entry point has two arguments. The first argument is the surface data produced by a material shader, and the second argument can be customized according to the light-type implemented (e.g. a spot light would specify a cone angle, world position, etc.).
|
|
|
|
+
|
|
|
|
+On its own, the snippet defined above does nothing, and there are a few pieces of machinery the renderer needs to provide. The two primary components needed are:
|
|
|
|
+
|
|
|
|
+* An interpolator
|
|
|
|
+* An integrator
|
|
|
|
+
|
|
|
|
+The role of the *interpolator* is to go from triangle data to interpolated fragment data (providing the data to be consumed by the material shader). The role of the *integrator* is to combine the outputs of the evaluation of one or more lights per pixels. Ultimately, it is the goal of the proposed abstraction to link the MainMS and MainLS functions with other code needed to provide concrete vertex, pixel, or compute shaders needed to evaluate the entire pipeline.
|
|
|
|
+
|
|
|
|
+Let’s see how we might define a deferred pipeline in code.
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+// Deferred pipeline
|
|
|
|
+
|
|
|
|
+struct VSInput
|
|
|
|
+{
|
|
|
|
+ float3 position : POSITION;
|
|
|
|
+ float3 normal : NORMAL;
|
|
|
|
+ float3 tangent : TANGENT;
|
|
|
|
+ float3 bitangent : BITANGENT;
|
|
|
|
+ float2 uv0 : UV0;
|
|
|
|
+ float2 uv1 : UV1;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// Mesh vertex shader entry point
|
|
|
|
+FragmentData MainVS(VSInput vsIn)
|
|
|
|
+{
|
|
|
|
+ FragmentData fragmentOut;
|
|
|
|
+ // Fill out fragment data (typical vertex shader)
|
|
|
|
+ return fragmentOut;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// GBuffer with custom encoding
|
|
|
|
+struct GBuffer
|
|
|
|
+{
|
|
|
|
+ float4 gbuffer0;
|
|
|
|
+ float4 gbuffer1;
|
|
|
|
+ float4 gbuffer2;
|
|
|
|
+ float4 gbuffer3;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+// NEW! Binding point for a material shader (effectively a function pointer)
|
|
|
|
+SurfaceData materialShader(FragmentData fragmentData) : SF_Material;
|
|
|
|
+
|
|
|
|
+// Pixel shader entry point for gbuffer
|
|
|
|
+GBuffer GBufferPS(FragmentData psIn)
|
|
|
|
+{
|
|
|
|
+ SurfaceData surface = materialShader(psIn);
|
|
|
|
+
|
|
|
|
+ // Fill out gbuffer from surface data using custom encoding scheme
|
|
|
|
+ GBuffer gbuffer = EncodeGBuffer(surface);
|
|
|
|
+
|
|
|
|
+ return gbuffer;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// NEW! Binding point for a light shader (effectively a function pointer)
|
|
|
|
+LightOutput dirLightShader(SurfaceData, DirectionalLight) : SF_DirectionalLight;
|
|
|
|
+
|
|
|
|
+LightOutput FullscreenLightPS(PixelData psIn)
|
|
|
|
+{
|
|
|
|
+ SurfaceData surfaceData = DecodeGBuffer();
|
|
|
|
+
|
|
|
|
+ LightOutput psOut;
|
|
|
|
+
|
|
|
|
+ // Pseudo code for fetching light data from an SRG
|
|
|
|
+ foreach (DirectionalLight dirLight : FetchDirLights())
|
|
|
|
+ {
|
|
|
|
+ // More pseudo code for evaluating the light shader and accumulating
|
|
|
|
+ // the results in the output.
|
|
|
|
+ psOut += dirLightShader(surfaceData, dirLight);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return psOut;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+The vertex and pixel shaders in the snippet above rely on new “function semantics” attached to the `materialShader` and `dirLightShader` function prototypes above. Linking the implementations of these functions obviously cannot happen at runtime, but instead are specified in the shader definition as extern functions.
|
|
|
|
+
|
|
|
|
+With this abstraction, it should be clear how we could do multi-layered materials (bind two or more functions to different semantics), or do specialized handling for particular light types (evaluate point/spot lights in a compute shader that grabs light data from culled light lists and invokes the bound light shader in SF_PointLight and SF_SpotLight for example). The parent shader binding the material shader slots would be responsible for any blending between the layers, and the material shaders themselves can bind, query, or request distinct sets of data (e.g. one layer may sample from a displacement map, while the base layer evaluates the standard PBR model).
|
|
|
|
+
|
|
|
|
+It should also be clear how we could achieve a forward rendering pipeline. Simply bind the material shader *and* the light shaders needed to the same pixel shader and evaluate both.
|
|
|
|
+
|
|
|
|
+"Linking" the function declarations with described semantics is a matter of HLSL snippet pasting and function renaming to ensure material and light forward-declared functions bound to distinct semantics are mapped to names that do not collide. Intuitively, we can think of this as a *build-time manual* link step, where we effectively rename and inline functions into VS, PS, and CS shaders using existing compiler features.
|
|
|
|
+
|
|
|
|
+A trickier use case is implementing a visibility buffer pipeline. In this case, the data supplied to the material shader (FragmentData) is not sufficient to evaluate the material because gradients are needed for texture sampling and finite-difference gradients from pixel quads are not available. Instead, the gradients must be evaluated analytically. However, we want to be able to use the *same material shader* to drive both a visibility buffer *and* other pipelines, potentially in the same project even. The simplest way to resolve this is with a pound def.
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+struct FragmentData
|
|
|
|
+{
|
|
|
|
+ float3 position;
|
|
|
|
+ float3 normal;
|
|
|
|
+ float3 tangent;
|
|
|
|
+ float3 bitangent;
|
|
|
|
+ float3 worldPosition;
|
|
|
|
+ float2 uv;
|
|
|
|
+#ifdef ENABLE_ATTRIBUTE_GRADIENTS
|
|
|
|
+ float2 d_uv;
|
|
|
|
+#endif
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+struct SurfaceData
|
|
|
|
+{
|
|
|
|
+ float4 baseColor;
|
|
|
|
+ float roughness;
|
|
|
|
+ bool metallic;
|
|
|
|
+ float3 normal;
|
|
|
|
+ float4 emissive;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+SurfaceData MainMS(FragmentData fragmentData)
|
|
|
|
+{
|
|
|
|
+ SurfaceData surfaceOut;
|
|
|
|
+ // Material shading code as before, but sample textures using Texture::SampleGrad
|
|
|
|
+ // and fragmentData.d_uv if ENABLE_ATTRIBUTE_GRADIENTS is defined.
|
|
|
|
+ return surfaceOut;
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+The visibility buffer decoding shader might look like the following:
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+struct TriangleData
|
|
|
|
+{
|
|
|
|
+ uint objectId;
|
|
|
|
+ uint instanceId;
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+uint materialId : b0;
|
|
|
|
+SurfaceData materialShader(FragmentData) : SF_Material;
|
|
|
|
+
|
|
|
|
+LightOutput directionalLight(SurfaceData, DirectionalLightData) : SF_DirectionalLight;
|
|
|
|
+LightOutput spotLight(SurfaceData, SpotLightData) : SF_SpotLight;
|
|
|
|
+LightOutput pointLight(SurfaceData, PointLightData) : SF_PointLight;
|
|
|
|
+
|
|
|
|
+LightOutput VisibilityBuffer_PS(VSOutput psIn)
|
|
|
|
+{
|
|
|
|
+ // Pseudo code. Check the current pixel to see if it belongs to the current
|
|
|
|
+ // materialId (bound as a constant)
|
|
|
|
+ if (!ShouldEvaluate(fragmentData, materialId))
|
|
|
|
+ {
|
|
|
|
+ discard;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Fetch triangle data from the visibility buffer
|
|
|
|
+ TriangleData triangleData = FetchTriangle();
|
|
|
|
+
|
|
|
|
+ // Perform a ray cast against the screen-space pixel, fetch triangle
|
|
|
|
+ // vertices, perfrom attribute interpolation, and evaluate gradients
|
|
|
|
+ // analytically
|
|
|
|
+ FragmentData fragmentData = EvaluateFragment(triangleData, psIn);
|
|
|
|
+
|
|
|
|
+ SurfaceData surfaceData = materialShader(fragmentData);
|
|
|
|
+
|
|
|
|
+ // Here, we opt to evaluate the lighting directly, but we could just
|
|
|
|
+ // as easily go from vbuffer to gbuffer and fallback to deferred lighting
|
|
|
|
+ // later.
|
|
|
|
+ LightOutput lightOut;
|
|
|
|
+
|
|
|
|
+ // Fetch directional, spot, and point lights assigned to this pixel
|
|
|
|
+ // and evaluate the shaders in l0, l1, and l2 respectively, accumulating
|
|
|
|
+ // the results in lightOut
|
|
|
|
+ // (Pseudo-code)
|
|
|
|
+ foreach (DirectionalLightData dirLightData : FetchDirLights())
|
|
|
|
+ {
|
|
|
|
+ lightOut += directionalLight(surfaceData, dirLightData);
|
|
|
|
+ }
|
|
|
|
+ foreach (SpotLightData spotLightData : FetchSpotLights())
|
|
|
|
+ {
|
|
|
|
+ lightOut += spotLight(surfaceData, spotLightData);
|
|
|
|
+ }
|
|
|
|
+ foreach (PointLightData pointLightData : FetchSpotLights())
|
|
|
|
+ {
|
|
|
|
+ lightOut += pointLight(surfaceData, pointLightData);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return lightOut;
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+Crucially, we can observe that the *same material and light shaders* can be used in both the visibility buffer shading approach defined above, and any other pipeline we might imagine (forward, deferred, etc.). The only additional mechanism needed is to conditionally add code or data to the material/light shader via preprocessor definitions. We are protected against function signature mismatches by normal compiler type safety mechanisms.
|
|
|
|
+
|
|
|
|
+## Technical Design Description
|
|
|
|
+
|
|
|
|
+While the feature summary as described above is written from the perspective of specific use-cases centered around material and lighting pipelines, the specific implementation can be achieved in a more general fashion through a singular abstraction referred to as “shader extern functions.” A conceptual diagram of the current material architecture and the proposed architecture is displayed below.
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+### Shader Pipeline Changes
|
|
|
|
+
|
|
|
|
+The `.shader` file will receive a new optional property in the `ProgramSettings` group, corresponding to user-linked functions:
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+{
|
|
|
|
+ // Other shader file properties,
|
|
|
|
+ "ProgramSettings":
|
|
|
|
+ {
|
|
|
|
+ // Other program settings
|
|
|
|
+ "ExternFunctions":
|
|
|
|
+ [
|
|
|
|
+ "SF_Material0",
|
|
|
|
+ "SF_Material1",
|
|
|
|
+ "SF_DirectionalLight"
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+The elements within the `ExternFunctions` property group denote semantics bound within the shader itself. Any valid identifier may be used as a semantic, provided it does not collide with a known semantic (e.g. “POSITION” is an invalid semantic for an extern function). Note that when a shader contains extern functions, the shader is not immediately compilable, except when referenced within a material pipeline file described later.
|
|
|
|
+
|
|
|
|
+### Material Type File Changes
|
|
|
|
+
|
|
|
|
+The `materialtype` file currently has a shaders property array which associates shader files with render passes. In effect, this directly couples the material and lighting implementations with a particular rendering pipeline which is what we want to avoid. In addition, the material is currently strongly coupled with the geometric representation as well. Ideally, we’d want to separate the notion of geometry (which impacts render features such as shadows and depth rendering) from the material representation (used for surface evaluation). However, to keep the scope of this RFC down, we will leave the geometric coupling for the time being to address in a future RFC (which will cover indirect rendering, GPU-driven culling, and multi-view rendering).
|
|
|
|
+
|
|
|
|
+First, *all existing functionality* in the `materialtype` file should be kept to provide adequate time to deprecate aspects later on. This includes the direct registration of shaders to shader passes.
|
|
|
|
+
|
|
|
|
+Second, a *new property* called `materialShader` will be added at the top level (same level as the `shaders` property array). This property should reference the `azsf` file defining the material function and supply the entry point. The usage of this suffix indicates that the file is neither a source file (it cannot be compiled on its own), nor an include file (it cannot be included directly).
|
|
|
|
+
|
|
|
|
+Last, we add another new property called `materialPipelineTags`, also at the top level. This property can be overridden in a `material` file (e.g. the material might be opaque by default, but overriding properties that effect depth or transparency might cause the material to override the material pipeline tags to be transparent instead). An example is shown below:
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+// basic_material.materialtype
|
|
|
|
+{
|
|
|
|
+ // Other materialtype properties
|
|
|
|
+ "shaders": [
|
|
|
|
+ // Unchanged
|
|
|
|
+ ],
|
|
|
|
+ "materialPipelineTags": [
|
|
|
|
+ "opaque"
|
|
|
|
+ ],
|
|
|
|
+ "materialShader": {
|
|
|
|
+ "file": "basic_material.azsf",
|
|
|
|
+ "entryPoint": "MainMS"
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+Crucially, the `materialtype` file *does not* specify a material pipeline (described in the next section) that supports it. This is by design, as materials should be agnostic to where and how they are rendered.
|
|
|
|
+
|
|
|
|
+### New File Type: Material Pipeline Files
|
|
|
|
+
|
|
|
|
+Finally, we need a new file type to describe the association between materials and concrete shaders. These files will have the `materialpipeline` suffix and will describe the rendering pipelines used to render materials (e.g. forward, deferred, etc.). An example file is shown below:
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+// deferred.materialpipeline
|
|
|
|
+{
|
|
|
|
+ "tags": [
|
|
|
|
+ "opaque"
|
|
|
|
+ ],
|
|
|
|
+ "shaders": [
|
|
|
|
+ {
|
|
|
|
+ "file": "GBuffer.shader",
|
|
|
|
+ "tag": "GBufferPass"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ "file": "GBufferDirLight.shader",
|
|
|
|
+ "tag": "GBufferDirPass",
|
|
|
|
+ "linkTable": {
|
|
|
|
+ "dirLight": "./dirLight.azsf"
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // Additional shaders below
|
|
|
|
+ ]
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+The `linkTable` property is used to bind additional functions to shader extern functions as needed. In this example, we declare a shader used to apply directional lights to the gbuffer by binding `dirLight.azsf` to the `dirLight` bind point in the “GBufferDirLight” shader. Materials are associated with material pipelines at asset build time. Each shader is matched with the material extern functions and compiled according to the process outlined in the next section. The existence of extern function declarations without a corresponding definition will result in a compilation failure.
|
|
|
|
+
|
|
|
|
+The `tags` property indicates which materials should render using the given pipeline. In the deferred pipeline example, the opaque tag is used. At runtime, geometric complexity may dictate that some objects should be drawn using one pipeline over another. However, for now we make the pipeline selection a purely static property in the interest of managing RFC complexity.
|
|
|
|
+
|
|
|
|
+A corresponding example for the forward pass might look like the following:
|
|
|
|
+
|
|
|
|
+```
|
|
|
|
+// forward.materialpipeline
|
|
|
|
+{
|
|
|
|
+ "tags": [
|
|
|
|
+ "transparent",
|
|
|
|
+ "emissive"
|
|
|
|
+ ],
|
|
|
|
+ "shaders": [
|
|
|
|
+ {
|
|
|
|
+ "file": "Forward.shader",
|
|
|
|
+ "tag": "ForwardPass",
|
|
|
|
+ "linkTable": {
|
|
|
|
+ "dirLight": "./dirLight.azsf",
|
|
|
|
+ "spotLight": "./spotLight.azsf",
|
|
|
|
+ "pointLight": "./pointLight.azsf"
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+}
|
|
|
|
+```
|
|
|
|
+
|
|
|
|
+In this simple forward material pipeline, we have a single shader definition corresponding to the unified shading and lighting pass.
|
|
|
|
+
|
|
|
|
+Crucially, any extern functions in the shaders used in the material pipeline files *must* be resolved when the materials themselves are applied to an object.
|
|
|
|
+
|
|
|
|
+### Material Pipeline Build Process
|
|
|
|
+
|
|
|
|
+Material pipeline files describe how function definitions will be mapped to the extern functions added to a shader file. The extern functions will be implemented in files authored with the new `azsf` file suffix. When a material is used by an object in a scene, we first determine which pipeline is active for the material by scanning registered material pipeline files for matching pipeline tags.
|
|
|
|
+
|
|
|
|
+When the shader pipeline encounters a shader with extern functions specified, a single additional step is introduced to the pipeline. *Before* any other evaluation, we perform the following steps:
|
|
|
|
+
|
|
|
|
+1. For each bound extern function, scan the `azsf` file for a matching function declaration and semantic.
|
|
|
|
+2. Extract the function name from the forward declaration assigned to the semantic
|
|
|
|
+3. In the corresponding `azsf` file, replace in-memory any occurrences of the entry point with the function name extracted from step two
|
|
|
|
+4. Append the result from step 3 to the source file and feed the result into the rest of the pipeline
|
|
|
|
+
|
|
|
|
+The results produced by the process above amounts to a simple function rename and source assembly *prior* to the shader source compilation, so no additional changes to the shader compiler are needed. Note that the HLSL grammar permits the association of semantics with function declarations.
|
|
|
|
+
|
|
|
|
+No changes are expected for the shader variant list, which will continue to expand all permutations as before after the transformation described above is applied. However, we may need to allow pipelines to disable variant expansion which may generate more PSOs than needed in certain cases.
|
|
|
|
+
|
|
|
|
+## What are the advantages of the feature?
|
|
|
|
+
|
|
|
|
+* Materials will be decoupled from lighting implementations, which will permit materials to be used in arbitrary material pipelines defined by the engine or users of the engine
|
|
|
|
+* Material shaders are a necessary precursor to the development of a “Material Canvas” tool (to be preceded by a “Shader Canvas” tool)
|
|
|
|
+* In particular, custom material pipelines would enable the development of a visibility-buffer pipeline to handle cases where engine performance suffers due to overdraw and register pressure
|
|
|
|
+
|
|
|
|
+## What are the disadvantages of the feature?
|
|
|
|
+
|
|
|
|
+* This RFC decouples materials from lighting, but does not yet decouple materials from the underlying geometry. This will need to be addressed in a future RFC
|
|
|
|
+* Some complexity is added to the asset types needed as part of the decoupling (new asset types introduced)
|
|
|
|
+* Some complexity is added to the shader pipeline (but it is expected that total compilation time may decrease given that not all materials need to be forward rendered)
|
|
|
|
+
|
|
|
|
+## How will this be implemented or integrated into the O3DE environment?
|
|
|
|
+
|
|
|
|
+The mechanics of how the feature will work are explained in the TDD, but will be accompanied by a task to actually convert our materials incrementally to the new system. Because the existing `shaders` property array in the material file will be left intact for the time being, the existing hardcoded pipelines can continue to function until the conversion is complete.
|
|
|
|
+
|
|
|
|
+## Are there any alternatives to this feature?
|
|
|
|
+
|
|
|
|
+The only alternative is inaction, in which case the engine can only realistically ever support a single render pipeline at a time (since it’s impractical to duplicate or author materials per-pipeline).
|
|
|
|
+
|
|
|
|
+## How will users learn this feature?
|
|
|
|
+
|
|
|
|
+In addition to examples of materials and accompanying documentation, several sample material pipelines will be provided in the `AtomSampleViewer` project.
|
|
|
|
+
|
|
|
|
+## Are there any open questions?
|
|
|
|
+
|
|
|
|
+While the opening preamble talked about handling material layers explicitly, the TDD doesn’t yet prescribe exactly how this should occur. One alternative is to allow materials to have multiple parent material types, and expose the associated material shaders with distinct binding locations.
|