Hi,
I'm designing a new shader system for my engine - so I decided to checkout how others do it... I stumbled uppon Hodgman's response, I saw the Bitsquid's presentation about their Data-driven renderer and finally FrameGraph (D.A.G. applied to yet another thing ). And... now I'm overloaded with informations...
Initially I thought that I've understanded Hodgman's post so I tried to wrap my shader into Passes, and those into Techniques. Since I know Lua pretty well, I also decided to use it for custom "effect" files:
Shader "common" [[
// GLSL snippet
]]
Shader "simpleVertexShader" [[
// GLSL snippet
]]
Shader "simpleFragShader" [[
// Another GLSL snippet
]]
ShaderProgram "simpleProgram" {
vertex_shader = { "common", "simpleVertexShader" },
fragment_shader = { "simpleFragShader" }
}
ShaderProgram "simplerProgram" {
options = { maxLights=2, cheapApproximations=true },
vertex_shader = { "common", "simpleVertexShader" },
fragment_shader = { "simpleFragShader" }
}
Technique "normal" {
{ name="opaque_pass", layer="opaque", program="simpleProgram" },
}
Technique "selected" {
{ name="outline_pass", layer="opaque", program="simplerProgram", uniforms={ outlineColor={0.5, 0.1, 0.1, 1.0} } },
{ name="opaque", layer="opaque", program="simpleProgram" }
}
So basically a Technique is a container for TechniquePasses, each Pass has a name, a shading program and is assigned to a Layer. A layer is a special place in a pipeline, and it has its own RenderQueue and optional profiling scope (e.g. "shadow mapping"). My tought process was as follows: I can have multiple Pipelines (Forward, Shadow, Deferred, Postprocess, Raytracing). Pipelines are divided in stages ("layers") that run at different times and they enforce the ultimate render order (the connections between pipelines builds RenderPaths my version of FrameGraph). TechniquePasses are also guaranted to execute in declaration order (not exactly one right after another, but the order won't be changed - I reserved some bits in the sortkey for pass index). During rendering I look up the Material, it gives me a Technique (the Material-Technique connection is actually baked offline), then I iterate over it's passes submitting drawables to respective renderqueues (layers).
In theory a "Toon Shading" rendering technique should be pipeline-independent (it should be possible to use it forward and deferred). So does "Phong" and "BlinnPhong", but there might be issues with Deferred Pipelines (different G-Buffer layouts in some cases)...
I started implementing it, but it went to far for my taste - It feels too much coupled (high-level rendering mixed with low-level) and with high "mental tax". Swapping Techniques at runtime is hard and the overall complexity just skyrocketed... and this should be low-level render wrapper
So taking 2 steps back - what actually is a "Technique"? Should it describe how to render a material in different pipelines (e.g. "DepthOnly", "Deferred", "Forward"), shading methods ("Phong", "BlinnPhong", "PBR" techniques) or more basic variations ("selected", "nightVision", "normal", "glowing" - currently I'm using such variations as shader options). Up to this point I always imagined a "Technique" as a dumb container for passes for either in the same stage (like in toon shading: contour pass + normal pass) or for special effects like some kind of magic shield (opaque + transparent passes), but they are not that popular these days. Well, I believe the last time I've used them was when XNA was becoming popular.
And one more question: should the Shading System allow for technique swaps at run-time? I'm starting to think, that it should be static - in order to make Materials less complicated (no dynamic lookups, single UBO/cbuffer layout, etc.)
↧