User Defined Shaders
Andor Salga | 14 July, 2009 | 13:06
We are adding a feature to the library which will allow users to create their own vertex and fragment shaders, compile them and then assign them to geometry. While trying to solve how this can be done, I realized the problem is actually much more complex than I initially anticipated. While working on the requirements, I kept stumbling into different issues. Instead of trying to analyze each of these issues in a lengthy blog, I decided to tackle each one separately.
Let’s begin with a use case. A user has a model of a person, which is actually composed of separate parts or “primitive sets”. We know this because we know the internal structure of the .DAE file. If the user does not know the structure of the file, they would be limited to assigning their shader to the entire object rather than portions of it. Let’s say the user knows the structure and needs to render the following primitive sets:
From the use case, it can be seen the main requirements are:
Assume an effect object was created from the shaders. The user assigned the effect to part of the Collada object, but how will the uniform and attribute variables be set in the shaders? The library must somehow provide a mechanism for the user to pass data from their script to the library which in turn is sent to the shaders. Furthermore, to achieve some effects like the outline for cel shading, the logic isn’t part of the shader code at all, it’s part of the application. I first came up with the idea of creating an object which can have parameters added to it and when instantiated, must have those parameters filled and would be sent to the renderer. The solution didn’t address the cel shading outline problem and seemed a bit awkward. I had some other ideas, but none of them seemed clean enough to actually implement. I then came up with a simple solution: allow the user to provide a callback function. Since the user wrote the shader, they also know all the uniform and attribute variables which must be set. They would be able to set the variables and perform any other logic needed to create the desired effect in the callback. This technique does expose the user to the graphics context, but if the user is writing a shader, it can be assumed they have experience with OpenGL commands. To curb any confusion which may arise, tutorials, references and helper functions will be provided to aid users in writing their callbacks.
Creating an effect now involves the user specifying the shaders as strings as well as an appropriate callback. These objects can be encapsulated in an effect instance and then passed to a collada object, node, primitive set, etc. When the geometry is rendered, the renderer will detect it has a user defined effect and call the user’s callback. The user’s code will be responsible for switching to use their program object, setting the shader variables and perform any other logic needed to accomplish the rendering effect.
As mentioned earlier, this only solves one problem and many other small problems must still be addressed.
Let’s begin with a use case. A user has a model of a person, which is actually composed of separate parts or “primitive sets”. We know this because we know the internal structure of the .DAE file. If the user does not know the structure of the file, they would be limited to assigning their shader to the entire object rather than portions of it. Let’s say the user knows the structure and needs to render the following primitive sets:
- Shoes rendered with a cel shader
- Shirt rendered with a sepia shader
- Everything else with a scanline shader
From the use case, it can be seen the main requirements are:
- Must allow the user to specify shader code to create an effect.
- Must allow user to assign effect to entire object, node, geometry or primitive set of a collada object.
Assume an effect object was created from the shaders. The user assigned the effect to part of the Collada object, but how will the uniform and attribute variables be set in the shaders? The library must somehow provide a mechanism for the user to pass data from their script to the library which in turn is sent to the shaders. Furthermore, to achieve some effects like the outline for cel shading, the logic isn’t part of the shader code at all, it’s part of the application. I first came up with the idea of creating an object which can have parameters added to it and when instantiated, must have those parameters filled and would be sent to the renderer. The solution didn’t address the cel shading outline problem and seemed a bit awkward. I had some other ideas, but none of them seemed clean enough to actually implement. I then came up with a simple solution: allow the user to provide a callback function. Since the user wrote the shader, they also know all the uniform and attribute variables which must be set. They would be able to set the variables and perform any other logic needed to create the desired effect in the callback. This technique does expose the user to the graphics context, but if the user is writing a shader, it can be assumed they have experience with OpenGL commands. To curb any confusion which may arise, tutorials, references and helper functions will be provided to aid users in writing their callbacks.
Creating an effect now involves the user specifying the shaders as strings as well as an appropriate callback. These objects can be encapsulated in an effect instance and then passed to a collada object, node, primitive set, etc. When the geometry is rendered, the renderer will detect it has a user defined effect and call the user’s callback. The user’s code will be responsible for switching to use their program object, setting the shader variables and perform any other logic needed to accomplish the rendering effect.
As mentioned earlier, this only solves one problem and many other small problems must still be addressed.
