123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498 |
- // O3DE: this file is taken from slang repository,
- // and slightly syntax adapted to AZSL grammar.
- // shaders.slang
- //
- // This example builds on the simplistic shaders presented in the
- // "Hello, World" example by adding support for (intentionally
- // simplistic) surface materil and light shading.
- //
- // The code here is not meant to exemplify state-of-the-art material
- // and lighting techniques, but rather to show how a shader
- // library can be developed in a modular fashion without reliance
- // on the C preprocessor manual parameter-binding decorations.
- //
- // We are going to define a simple model for surface material shading.
- //
- // The first building block in our model will be the representation of
- // the geometry attributes of a surface as fed into the material.
- //
- struct SurfaceGeometry
- {
- float3 position;
- float3 normal;
- // TODO: tangent vectors would be the natural next thing to add here,
- // and would be required for anisotropic materials. However, the
- // simplistic model loading code we are currently using doesn't
- // produce tangents...
- //
- // float3 tangentU;
- // float3 tangentV;
- // We store a single UV parameterization in these geometry attributes.
- // A more complex renderer might need support for multiple UV sets,
- // and indeed it might choose to use interfaces and generics to capture
- // the different requirements that different materials impose on
- // the available surface attributes. We won't go to that kind of
- // trouble for such a simple example.
- //
- float2 uv;
- };
- //
- // Next, we want to define the fundamental concept of a refletance
- // function, so that we can use it as a building block for other
- // parts of the system. This is a case where we are trying to
- // show how a proper physically-based renderer (PBR) might
- // decompose the problem using Slang, even though our simple
- // example is *not* physically based.
- //
- interface IBRDF
- {
- // Technically, a BRDF is only a function of the incident
- // (`wi`) and exitant (`wo`) directions, but for simplicity
- // we are passing in the surface normal (`N`) as well.
- //
- float3 evaluate(float3 wo, float3 wi, float3 N);
- };
- //
- // We can now define various implemntations of the `IBRDF` interface
- // that represent different reflectance functions we want to support.
- // For now we keep things simple by defining about the simplest
- // reflectance function we can think of: the Blinn-Phong reflectance
- // model:
- //
- class BlinnPhong : IBRDF
- {
- // Blinn-Phong needs diffuse and specular reflectances, plus
- // a specular exponent value (which relates to "roughness"
- // in more modern physically-based models).
- //
- float3 kd;
- float3 ks;
- float specularity;
- // Here we implement the one requirement of the `IBRDF` interface
- // for our concrete implementation, using a textbook definition
- // of Blinng-Phong shading.
- //
- // Note: our "BRDF" definition here folds the N-dot-L term into
- // the evlauation of the reflectance function in case there are
- // useful algebraic simplifications this enables.
- //
- float3 evaluate(float3 V, float3 L, float3 N)
- {
- float nDotL = saturate(dot(N, L));
- float3 H = normalize(L + V);
- float nDotH = saturate(dot(N, H));
- return kd*nDotL + ks*pow(nDotH, specularity);
- }
- };
- //
- // It is important to note that a reflectance function is *not*
- // a "material." In most cases, a material will have spatially-varying
- // properties so that it cannot be summarized as a single `IBRDF`
- // instance.
- //
- // Thus a "material" is a value that can produce a BRDF for any point
- // on a surface (e.g., by sampling texture maps, etc.).
- //
- interface IMaterial
- {
- // Different concrete material implementations might yield BRDF
- // values with different types. E.g., one material might yield
- // reflectance functions using `BlinnPhong` while another uses
- // a much more complicated/accurate representation.
- //
- // We encapsulate the choice of BRDF parameters/evaluation in
- // our material interface with an "associated type." In the
- // simplest terms, think of this as an interface requirement
- // that is a type, instead of a method.
- //
- // (If you are C++-minded, you might think of this as akin to
- // how every container provided an `iterator` type, but different
- // containers may have different types of iterators)
- //
- associatedtype BRDF : IBRDF;
- // For our simple example program, it is enough for a material to
- // be able to return a BRDF given a point on the surface.
- //
- // A more complex implementation of material shading might also
- // have the material return updated surface geometry to reflect
- // the result of normal mapping, occlusion mapping, etc. or
- // return an opacity/coverage value for partially transparent
- // surfaces.
- //
- BRDF prepare(SurfaceGeometry geometry);
- };
- // We will now define a trivial first implementation of the material
- // interface, which uses our Blinn-Phong BRDF with uniform values
- // for its parameters.
- //
- // Note that this implemetnation is being provided *after* the
- // shader parameter `gMaterial` is declared, so that there is no
- // assumption in the shader code that `gMaterial` will be plugged
- // in using an instance of `SimpleMaterial`
- //
- //
- class SimpleMaterial : IMaterial
- {
- // We declare the properties we need as fields of the material type.
- // When `SimpleMaterial` is used for `TMaterial` above, then
- // `gMaterial` will be a `ParameterBlock<SimpleMaterial>`, and these
- // parameters will be allocated to a constant buffer that is part of
- // that parameter block.
- //
- // TODO: A future version of this example will include texture parameters
- // here to show that they are declared just like simple uniforms.
- //
- float3 diffuseColor;
- float3 specularColor;
- float specularity;
- // To satisfy the requirements of the `IMaterial` interface, our
- // material type needs to provide a suitable `BRDF` type. We
- // do this by using a simple `typedef`, although a nested
- // `struct` type can also satisfy an assocaited type requirement.
- //
- // A future version of the Slang compiler may allow the "right"
- // associated type definition to be inferred from the signature
- // of the `prepare()` method below.
- //
- typealias BRDF = BlinnPhong;
- BlinnPhong prepare(SurfaceGeometry geometry)
- {
- BlinnPhong brdf;
- brdf.kd = diffuseColor;
- brdf.ks = specularColor;
- brdf.specularity = specularity;
- return brdf;
- }
- };
- //
- // Note that no other code in this file statically
- // references the `SimpleMaterial` type, and instead
- // it is up to the application to "plug in" this type,
- // or another `IMaterial` implementation for the
- // `TMaterial` parameter.
- //
- // A light, or an entire lighting *environment* is an object
- // that can illuminate a surface using some BRDF implemented
- // with our abstractions above.
- //
- interface ILightEnv
- {
- // The `illuminate` method is intended to integrate incoming
- // illumination from this light (environment) incident at the
- // surface point given by `g` (which has the reflectance function
- // `brdf`) and reflected into the outgoing direction `wo`.
- //
- float3 illuminate<B:IBRDF>(SurfaceGeometry g, B brdf, float3 wo);
- //
- // Note that the `illuminate()` method is allowed as an interface
- // requirement in Slang even though it is a generic. Constract that
- // with C++ where a `template` method cannot be `virtual`.
- };
- // Given the `ILightEnv` interface, we can write up almost textbook
- // definition of directional and point lights.
- class DirectionalLight : ILightEnv
- {
- float3 direction;
- float3 intensity;
- float3 illuminate<B:IBRDF>(SurfaceGeometry g, B brdf, float3 wo)
- {
- return intensity * brdf.evaluate(wo, direction, g.normal);
- }
- };
- class PointLight : ILightEnv
- {
- float3 position;
- float3 intensity;
- float3 illuminate<B:IBRDF>(SurfaceGeometry g, B brdf, float3 wo)
- {
- float3 delta = position - g.position;
- float d = length(delta);
- float3 direction = normalize(delta);
- float3 illuminance = intensity / (d*d);
- return illuminance * brdf.evaluate(wo, direction, g.normal);
- }
- };
- // In most cases, a shader entry point will only be specialized for a single
- // material, but interesting rendering almost always needs multiple lights.
- // For that reason we will next define types to represent *composite* lighting
- // environment with multiple lights.
- //
- // A naive approach might be to have a single undifferntiated list of lights
- // where any type of light may appear at any index, but this would lose all
- // of the benefits of static specialization: we would have to perform dynamic
- // branching to determine what kind of light is stored at each index.
- //
- // Instead, we will start with a type for *homogeneous* arrays of lights:
- //
- /* AZSLc: generic classes not supported for now (only generic functions)
- class LightArray<L : ILightEnv, let N : int> : ILightEnv
- */
- class LightArray : ILightEnv
- {
- // The `LightArray` type has two generic parameters:
- //
- // - `L` is a type parameter, representing the type of lights that will be in our array
- // - `N` is a generic *value* parameter, representing the maximum number of lights allowed
- //
- // Slang's support for generic value parameters is currently experimental,
- // and the syntax might change.
- int count;
- ILightEnv lights[10];
- float3 illuminate<B:IBRDF>(SurfaceGeometry g, B brdf, float3 wo)
- {
- // Our light array integrates illumination by naively summing
- // contributions from all the lights in the array (up to `count`).
- //
- // A more advanced renderer might try apply sampling techniques
- // to pick a subset of lights to sample.
- //
- float3 sum = 0;
- for( int ii = 0; ii < count; ++ii )
- {
- sum += lights[ii].illuminate(g, brdf, wo);
- }
- return sum;
- }
- };
- // `LightArray` can handle multiple lights as long as they have the
- // same type, but we need a way to have a scene with multiple lights
- // of different types *without* losing static specialization.
- //
- // The `LightPair<T,U>` type supports this in about the simplest way
- // possible, by aggregating a light (environment) of type `T` and
- // one of type `U`. Those light environments might themselves be
- // `LightArray`s or `LightPair`s, so that arbitrarily complex
- // environments can be created from just these two composite types.
- //
- // This is probably a good place to insert a reminder the Slang's
- // generics are *not* C++ templates, so that the error messages
- // produced when working with these types are in general reasonable,
- // and this is *not* any form of "template metaprogramming."
- //
- // That said, we expect that future versions of Slang will make
- // defining composite types light this a bit less cumbersome.
- //
- /* AZSLc: not supported
- struct LightPair<T : ILightEnv, U : ILightEnv> : ILightEnv
- */
- class LightPair : ILightEnv
- {
- T first;
- U second;
- float3 illuminate<B:IBRDF>(SurfaceGeometry g, B brdf, float3 wo)
- {
- return first.illuminate(g, brdf, wo)
- + second.illuminate(g, brdf, wo);
- }
- };
- // As a final (degenerate) case, we will define a light
- // environment with *no* lights, which contributes no illumination.
- //
- class EmptyLightEnv : ILightEnv
- {
- float3 illuminate<B:IBRDF>(SurfaceGeometry g, B brdf, float3 wo)
- {
- return 0;
- }
- };
- // The code above constitutes the "shader library" for our
- // application, while the code below this point is the
- // implementation of a simple forward rendering pass
- // using that library.
- //
- // While the shader library has used many of Slang's advanced
- // mechanisms, the vertex and fragment shaders will be
- // much more modest, and hopefully easier to follow.
- // We will start with a `struct` for per-view parameters that
- // will be allocated into a `ParameterBlock`.
- //
- // As written, this isn't very different from using an HLSL
- // `cbuffer` declaration, but importantly this code will
- // continue to work if we add one or more resources (e.g.,
- // an enironment map texture) to the `PerView` type.
- //
- struct PerView
- {
- float4x4 viewProjection;
- float3 eyePosition;
- };
- //ParameterBlock<PerView> gViewParams;
- // Declaring a block for per-model parameter data is
- // similarly simple.
- //
- struct PerModel
- {
- float4x4 modelTransform;
- float4x4 inverseTransposeModelTransform;
- };
- // AZSLc: not AZSL
- //ParameterBlock<PerModel> gModelParams;
- // We want our shader to work with any kind of lighting environment
- // - that is, and type that implements `ILightEnv`. Furthermore,
- // we want the parameters of that lighting environment to be passed
- // as parameter block - `ParameterBlock<L>` for some type `L`.
- //
- // We handle this by defining a global generic type parameter for
- // our shader, and constrainting it to implement `ILightEnv`...
- //
- // AZSLc: not AZSL
- //type_param TLightEnv : ILightEnv;
- //
- // ... and then defining a parameter block that uses that type
- // parameter as the "element type" of the block:
- //
- // AZSLc: not AZSL
- //ParameterBlock<TLightEnv> gLightEnv;
- // Our handling of the material parameter for our shader
- // is quite similar to the case for the lighting environment:
- //
- // AZSLc: not AZSL
- //type_param TMaterial : IMaterial;
- //ParameterBlock<TMaterial> gMaterial;
- // Our vertex shader entry point is only marginally more
- // complicated than the Hello World example. We will
- // start by declaring the various "connector" `struct`s.
- //
- struct AssembledVertex
- {
- float3 position : POSITION;
- float3 normal : NORMAL;
- float2 uv : UV;
- };
- struct CoarseVertex
- {
- float3 worldPosition;
- float3 worldNormal;
- float2 uv;
- };
- struct VertexStageOutput
- {
- CoarseVertex coarseVertex : CoarseVertex;
- float4 position : SV_Position;
- };
- // Perhaps most interesting new feature of the entry
- // point decalrations is that we use a `[shader(...)]`
- // attribute (as introduced in HLSL Shader Model 6.x)
- // in order to tag our entry points.
- //
- // This attribute informs the Slang compiler which
- // functions are intended to be compiled as shader
- // entry points (and what stage they target), so that
- // the programmer no longer needs to specify the
- // entry point name/stage through the API (or on
- // the command line when using `slangc`).
- //
- // While HLSL added this feature only in newer versions,
- // the Slang compiler supports this attribute across
- // *all* targets, so that it is okay to use whether you
- // want DXBC, DXIL, or SPIR-V output.
- //
- [shader("vertex")]
- VertexStageOutput vertexMain(
- AssembledVertex assembledVertex)
- {
- VertexStageOutput output;
- float3 position = assembledVertex.position;
- float3 normal = assembledVertex.normal;
- float2 uv = assembledVertex.uv;
- float3 worldPosition = mul(gModelParams.modelTransform, float4(position, 1.0)).xyz;
- float3 worldNormal = mul(gModelParams.inverseTransposeModelTransform, float4(normal, 0.0)).xyz;
- output.coarseVertex.worldPosition = worldPosition;
- output.coarseVertex.worldNormal = worldNormal;
- output.coarseVertex.uv = uv;
- output.position = mul(gViewParams.viewProjection, float4(worldPosition, 1.0));
- return output;
- }
- // Our fragment shader is almost trivial, with the most interesting
- // thing being how it uses the `TMaterial` type parameter (through the
- // value stored in the `gMaterial` parameter block) to dispatch to
- // the correct implementation of the `getDiffuseColor()` method
- // in the `IMaterial` interface.
- //
- // The `gMaterial` parameter block declaration thus serves not only
- // to group certain shader parameters for efficient CPU-to-GPU
- // communication, but also to select the code that will execute
- // in specialized versions of the `fragmentMain` entry point.
- //
- [shader("fragment")]
- float4 fragmentMain(
- CoarseVertex coarseVertex : CoarseVertex) : SV_Target
- {
- // We start by using our interpolated vertex attributes
- // to construct the local surface geometry that we will
- // use for material evaluation.
- //
- SurfaceGeometry g;
- g.position = coarseVertex.worldPosition;
- g.normal = normalize(coarseVertex.worldNormal);
- g.uv = coarseVertex.uv;
- float3 V = normalize(gViewParams.eyePosition - g.position);
- // Next we prepare the material, which involves running
- // any "pattern generation" logic of the material (e.g.,
- // sampling and blending texture layers), to produce
- // a BRDF suitable for evaluating under illumination
- // from different light sources.
- //
- // Note that the return type here is `TMaterial.BRDF`,
- // which is the `BRDF` type *assocaited* with the (unknown)
- // `TMaterial` type. When `TMaterial` gets substituted for
- // a concrete type later (e.g., `SimpleMaterial`) this
- // will resolve to a concrete type too (e.g., `SimpleMaterial.BRDF`
- // which is an alias for `BlinnPhong`).
- //
- // AZSLc: dot in slang is coloncolon in AZSL
- TMaterial::BRDF brdf = gMaterial.prepare(g);
- // Now that we've done the first step of material evaluation
- // and sampled texture maps, etc., it is time to start
- // integrating incident light at our surface point.
- //
- // Because we've wrapped up the lighting environment as
- // a single (composite) object, this is as simple as calling
- // its `illuminate()` method. Our particular fragment shader
- // is thus abstracted from how the renderer chooses to structure
- // this integration step, somewhat similar to how an
- // `illuminance` loop in RenderMan Shading Language works.
- //
- float3 color = gLightEnv.illuminate(g, brdf, V);
- return float4(color, 1);
- }
|