The popular real-time shading languages
(OpenGL GLSL [GLSL],
NVidia Cg [Cg] and
Direct 3D HLSL [HLSL])
do not provide a ready solution for connecting shaders from independent sources.
The CgFX
and HLSL .fx
files encapsulate shading language code
in techniques (for various graphic card capabilities)
and within a single technique specify operations for each rendering pass.
In neither case can we simply connect multiple shader source code files
and expect the result to be a valid program.
The X3D Programmable shaders component [X3D Shaders] makes the three shading languages mentioned above available to X3D authors. Complete shader code may be assigned to specific shapes. Although it does not offer any way of compositing shader effects, this component is still an important base for our work. It defines how to comfortably keep shader code inside X3D nodes. It also shows how to pass uniform values (including textures) to the shaders.
An old method to combine effects, used even before the shading languages, is a multi-pass rendering. Each rendering pass adds or multiplies to the buffer contents, adding a layer with desired effect. However, this is expensive — in each pass we usually have to repeat some work, at least transforming and clipping the geometry. It is also not flexible — we can only modify the complete result of the previous pass. In our work, we want to allow a single rendering pass to be as powerful as it can.
Arranging shader functions in a pipeline has similar disadvantages as multi-pass rendering, except there's no speed penalty in this case.
Common approach for writing a flexible shader code is to create a library of functions and allow the author to choose and compose them in a final shader to achieve the desired look. But this approach is very limited, as we cannot modify a particular calculation part without replicating the algorithm outside of this calculation. For example, if we want to scale the light contribution by a shadow function, we will have to also replicate the code iterating over the light sources.
Sh (http://libsh.org/, [Sh]) allows writing shader code (that can run on GPU) directly inside a C++ program. For this, Sh extends the C++ language (through C++ operator overloading and macros tricks). It allows an excellent integration between C++ code and shaders, hiding the ugly details of passing variables between normal code (that executes on CPU) and shader code (that usually executes on GPU). We can use object-oriented concepts to create a general shader that can later be extended, for example by overriding virtual methods. However, this is a solution closely coupled with C++. It's suitable if we have a 3D engine in C++, we want to use it in our own C++ program and extend its shaders. Our solution is simpler, treating shader effects as part of the 3D content and can be integrated into a renderer regardless of its programming language. We do not need a C++ compiler to generate a final GPU shader and users do not need to be familiar with C++.
OGRE (http://www.ogre3d.org/), an open-source 3D engine written in C++, has a system for adding shader extensions (see [OGRE Shader]). Its idea is similar to our system (enhance the built-in shaders with our own effects), however the whole job of combining a shader is done by operating on particular shader by C++ code. The developer has to code the logic deciding which shaders are extended and most of the specification about how the extension is called is done in the C++ code. This has the nice advantage of being able to encapsulate some fixed-function features as well, however the whole system must be carefully controlled by the C++ code. In our approach, we allow the authors to write direct shading language code quickly and the integration is built inside appropriate X3D nodes.
AnySL [AnySL] allows to integrate internal renderer shaders with user shaders, by introducing a new shader language.
Spark [Spark] is a recent work presenting a new language to develop composable shaders for GPU.
In this work, we deliberately decided not to introduce a new shading language. One of the problems with introducing a new language is that it is “yet another language to learn” for developers and users. For developers of 3D rendering applications, there's an additional effort of integrating the new language with a renderer, which often is not a trivial task. This creates a practical problem for new languages — because they are not popular, it's even harder for them to become popular. Of course, a new language has also the possibility to introduce new features, and “win” developers this way. But we think that existing shading languages, like GLSL, are already comfortable for a lot of practical purposes. That's, in a nutshell, our motivation behind extending an existing shading language, instead of inventing a new one.
[X3D DeclarativeShader] presents a declarative approach to an advanced shader in X3D. However, it only allows a fixed set of functionality, kind of an advanced and enhanced material. It does not expose any shader functionality to the authors. It merely allows the authors to use some advanced algorithms that in practice will be usually implemented by shaders inside the application.
[X3D Volume] proposes a new X3D component
for rendering volumetric data (coming from 3D textures).
It is interesting in relation to our work, as it
allows to compose various rendering styles for visualizing volumetric data.
Many styles of rendering are predefined
(all the nodes descending from the X3DComposableVolumeRenderStyleNode
)
and they can be combined together by the
ComposedVolumeStyle
node.
The effects introduced in our work could serve as a low-level implementation
for such rendering styles, utilizing GPU shading languages.
Each rendering style could be defined as a prototype that expands into our
Effect
node, overriding some plug receiving data about the 3D volume,
add adding the necessary visualization.
At the end, we would like to mention a solution from a completely different domain, that is surprisingly similar to ours in some ways. Drupal (http://drupal.org/), an open-source CMS system written in PHP, has a very nice system of modules. Each module can extend the functionality of the base system (or other module) by implementing a hook, which is just a normal PHP function with a special name and appropriate set of parameters. Modules can also define their own hooks (for use by other modules) and invoke them when appropriate. This creates a system where it's trivially easy to define new hooks and to use existing hooks. Many modules can implement the same hook and cooperate without any problems. The whole hook system is defined completely in PHP, as it's a scripting language and we can query the list of loaded functions by name, and call function by its name. Drupal approach is quite similar to our core idea of combining effects. Our effects are similar to Drupal's modules and our “plugs” are analogous to Drupal hooks.