Hi Robert, Hi all
I also implement a shader composition framework for a client, based on
osg shader composition, so remove it will be a big loss.
My implementation handle OpenGL version, some pragma (optimize and
debug), generate a main function for vertex and fragment shader based
on what is found in shaderComponent list. So change from previous
shader composition to new one will not be straight forward.
some thought about new shader composition
1)
OpenGL Shading Language specification
3.3 Preprocessor :
#pragma allows implementation dependent compiler control
so pragma "requires" and "import_defines" should be use by shader
compiler, replace them by "osg_requires" and "osg_import_defines"
could be prevent futur pragma collisions. Thought ?
but pragma is an elegant way to insert information directly in shader
code. well spot !!!
2)
Define a main function with all possibility and use define to choose
the ones to enable and the ones to not will make
a really complex shader, imagine, you optionally use
- instancing
- bump/deplacement mapping
- vertex blending
- material/texture/multi-texture
- lighting/shadow
- fog
- physic
- one/multi fragment output
You will be quickly crazy. The one that work with SpeedTree
Shader knows what I am talking about.
This is why I prefer your previous approach that inject code found in
StateAttribute in final program.
It is also less intrusive. If i need to insert a new shader component
in the program, no need to change main
shader. More over, I introduce some new ShaderAttribute like "camera"
to define where fragment shader output have to go,
or "drawable" to define which vertex shader input are available,
"Instance" to optionally use draw instance, ... so don't remove
ShaderAttribute. With modern OpenGL and depreciated data like fog,
light, ... we need a way to introduce our custom attribute.
I create a nodekit osgLighting just to manage different light source.
I have 5 different light source (sun, car, streetlight, ...) that all
use shaderAttribute to define data that compose the light and shader
that use this data. So don't remove shaderAttribute this is really a
good idea you have here !!!
3)
On a performance point of view, it will be a good thing to use
subroutine to enable/disable feature.
Many different path in shader composition result in many different
program. And so many change
of program in one frame, perhaps use the program A, then the program
B, then the program A again...
This is the main problem I currently have in my shader composition
implementation.
4)
I always think that lack of define injection in shader is a drawback
in osg. With your function and name convention,
this will be difficult to add this feature. Replace import_define by
import_module will fix this.
Because we talk about shader module, lighting, fog, bump mapping,
shadow ... all of this could be define as shader module.
I really think that shader composition is a big piece of software,
really usefull, and really complex.
I think we need a debate about this with all user that have already
try to implement a shader composition.
Clearly define our needs (have reusable module, keep code in shader
files, use OpenGL feature to improve performance like subroutine,
pipeline object, ...), debate about solution, propose implementation...
This problem is really new and so few research has be done an the
subject. I did my first implementation in octobre 2010
I have used my shader composition in many way. Found some cool stuff
and other bad about my design. I look for existing solution
on internet, I found some one like libsh <http://libsh.org/> but not
easy and clean way to solve it. Recently i found CosMo
<http://www.vrvis.at/publications/pdfs/PB-VRVis-2014-001.pdf> that did
the job in many way
like my implementation, but more deeply in abstraction of the code.
They don't think in term of shader but in term of
pipeline graphics, shader composition will decide which code put in
vertex/tessellation/geometry/fragment shader. I also do this
but it's clearly not a good idea.
You propose a clean and (perhaps too) easy way to do the job. This is
fresh. This is nice to have a new view on this complex problem.
So, Robert, Other, we need to talk about this to create a robust,
clean and easy way to do shader composition.
Thought ?
My 2 cents
David
2015-02-13 19:01 GMT+01:00 Robert Osfield <robert.osfi...@gmail.com
<mailto:robert.osfi...@gmail.com>>:
Hi All,
Attached is a screenshot of running (clock.osgt is in
OpenSceneGraph-Data):
osgshadercomposition clock.osgt
A very simple example that provides a single osg::Program that
have five different shader combinations used on the same subgraph
to produce different results. From left to right they are:
1) White unlit box - is the default implementation with no
define's supplied from osg::StateSet's so no texturing or lighting
2) White lit box - "GL_LIGHTING" define is provided to the
shaders via the stateset->setDefine("GL_LIGHTING");
3) Textured unlit box, "GL_TEXTURE_2D" define is provide to the
shader via stateset->setDefine("GL_TEXTURE_2D");
4) Textured lit box, both "GL_LIGHTING" and "GL_TEXTURE_2D"
defines are provide via stateset->setDefine("GL_TEXTURE_2D"); etc.
5) As for 4 but a macro function is passed to the shader via :
stateset->setDefine("VERTEX_FUNC(v)" , "vec4(v.x,
v.y, v.z * sin(osg_SimulationTime), v.w)");
In each of these instances the define you provide via
StateSet::setDefine(DefineString) is mapped to a set of:
#define DefineString
The is passed to the shader compilation. When
StateSet::setDefine(DefineString, DefineValue) is used it's mapped to:
#define DefineString DefineValue
So for the case 5 the Define's passed to the StateSet map to a set
of shader lines that are inserted before the body of the shader thus:
#define GL_LIGHTING
#define GL_TEXTURE_2D
#define VERTEX_FUNC(v) vec4(v.x, v.y, v.z *
sin(osg_SimulationTime), v.w)
These #defines are then used by the three shaders attached to the
osg::Program in the osgshadercomposition example, the first shader
is OpenSceneGraph-Data/shader/osgshadercomposition.vert which
looks like:
-- start of osgshadercomposition.vert --
#pragma import_defines ( GL_LIGHTING, GL_TEXTURE_2D, VERTEX_FUNC(v) )
#ifdef GL_LIGHTING
// forward declare lighting computation, provided by lighting.vert
shader
void directionalLight( int lightNum, vec3 normal, inout vec4 color );
#endif
#ifdef GL_TEXTURE_2D
varying vec2 texcoord;
#endif
#ifdef VERTEX_FUNC
uniform float osg_SimulationTime;
#endif
varying vec4 basecolor;
void main(void)
{
basecolor = gl_Color;
#ifdef GL_LIGHTING
directionalLight( 0, gl_Normal.xyz, basecolor);
#endif
#ifdef GL_TEXTURE_2D
// if we want texturing we need to pass on texture coords
texcoord = gl_MultiTexCoord0.xy;
#endif
#ifdef VERTEX_FUNC
gl_Position = gl_ModelViewProjectionMatrix *
VERTEX_FUNC(gl_Vertex);
#else
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
#endif
}
-- end of osgshadercomposition.vert --
First thing of note is the first line:
#pragma import_defines(GL_LIGHTING, GL_TEXTURE_2D, VERTEX_FUNC(v))
This #pragma is read and used by the OSG to tell it what Define's
to look up and apply if they are provided in the parental chain of
StateSet's, note the matching is case sensitive and in the case of
macro VERTEX_FUNC(v) you must use exactly the same parameters
including name as the matching is doing straight std::string match
internally. When using import_defines() the defines are all
treated as optional, so if they aren't supplied by the StateSet's
then nothing is passed along. Also if you switch off a define via
setDefine("GL_LIGHTING", osg::StateAttribute::OFF); then this
value will not be passed on.
It is intended that the shaders themselves will use #ifdef blocks
to enable/disable various features so that all the various paths
make sense. This approach make it possible to implement optional
varying and uniform usage as well as operations within the main or
functions. It also ensures that the shader compilation done by
the drive just compiles what is required, it doesn't have extra
uniform variables that might be constant or varyings and uniforms
that might not be used in all code paths.
--
The second shader is OpenSceneGraph-Data/shaders/lighting.vert
that provides the directionalLight() function that is used by the
osgshadercomposition.vert main when GL_LIGHTING is enabled via
StateSet::setDefine("GL_LIGHTING"). Since this shader is not
required when GL_LIGHTING is not defined it's not appropriate to
link the shader to Program, so here we provide a #pragma
requres(GL_LIGHTING) as a directive to the OSG so it know when to
link it. The shader looks :
-- start of lighting.vert
#pragma requires(GL_LIGHTING)
void directionalLight( int lightNum, vec3 normal, inout vec4 color )
{
vec3 n = normalize(gl_NormalMatrix * normal);
float NdotL = dot( n,
normalize(gl_LightSource[lightNum].position.xyz) );
NdotL = max( 0.0, NdotL );
float NdotHV = dot( n, gl_LightSource[lightNum].halfVector.xyz );
NdotHV = max( 0.0, NdotHV );
#if 1
color *= gl_LightSource[lightNum].ambient +
gl_LightSource[lightNum].diffuse * NdotL;
#else
color *= gl_FrontLightModelProduct.sceneColor +
gl_FrontLightProduct[lightNum].ambient +
gl_FrontLightProduct[lightNum].diffuse * NdotL;
#endif
#if 0
if ( NdotL * NdotHV > 0.0 )
color += gl_FrontLightProduct[lightNum].specular * pow(
NdotHV, gl_FrontMaterial.shininess );
#endif
}
-- end of lighting.vert
Since this shader requires GL_LIGHTING it doesn't make any sense
to had an optional code paths using this define so the shader
doesn't have any. If you did want to add in use of defines as in
the osgshadercomposition.vert shader you'd need to specify them
with an additional #pragma import_defines(...) line as per the
osgshadercomposition.vert shader.
The optional linking facility provided by #pragma requires(..)
enables on to optionally pull in whole chunks of the shader, so if
you wanted you could optional link in a geometry shader, or
disable vertex shaders and fallback to use fixed function pipeline
if you so wished. It also allows you to attach multiple shaders
to the main osg::Program and have these shaders provide the same
functions but each with a different implementation, but then use
different define's to pull in the implementation you want and no
link the unneeded ones without any conflicts.
--
Finally we have the osgshadercomposition.frag shader that does the
optional texture mapping, the shader looks like:
-- start of osgshadercomposition.frag
#pragma import_defines ( GL_TEXTURE_2D )
#ifdef GL_TEXTURE_2D
uniform sampler2D texture0;
varying vec2 texcoord;
#endif
varying vec4 basecolor;
void main(void)
{
#ifdef GL_TEXTURE_2D
gl_FragColor = texture2D( texture0, texcoord) * basecolor;
#else
gl_FragColor = basecolor;
#endif
}
-- end of osgshadercomposition.frag
Note the #pragma import_defines pulls in the optional
GL_TEXTURE_2D, and when it's provided both the uniform sample2D
texture0; and the varying texcoords are compiled in, along with
the texture2D(..) fetch in the main().
--
The use of OSG specific #pragma enables the shaders themselves to
be passed as-is to the driver to compile and have these custom
#pragma's simply ignored by the driver. This means one can also
use the same shaders in non OSG applications, such as developing
the shaders in a 3rd party tool. In this tool you'd need to
manually add in the #define's to test the different path ways out
and then comment them out/remove them for the final shaders you
pass to the OSG so the StateSet::setDefine() magic can provide all
the different paths you need.
This ability to have shaders that are readable and usable as-is
without code insertion/mucking about by the application is a real
bonus. When I was modifying the old osgshadercomposition code
paths (now placed in
examples/osgshadercomposition/oldshadercomposition.cpp) I was
struck by how hard is was to work on what on earth was going on
with how the C++ code passing strings for code injection during
shader composition to the final GLSL. Basically the old way is
complicated and so obfuscated it's now seems an obviously bad
approach - a case of hard problems needing complex solutions.
My hope is that the new "#pragma(tic) shader composition" approach
is simple to understand and use, I've certainly found it fun to
start using in - it's now used in the new
osgTerrain::DisplacementTechnique to enable toggling between
different features. It's actually so simple and powerful I can't
believe I never thought of this approach years ago. I guess that
is the way sometimes with hard problems, you spend too close to a
problem that the you can't see through the complexity to find a
simple solution, but once the simple and elegant solution is
presented to you is like it's so OBVIOUS to hard to fathom why it
was overlooked for so long.
Given how much user friendly the new approach is over the previous
attempts at shader composition like the old osg::ShaderComposition
framework and the VirtualProgram approach I believe it's
appropriate to deprecate the ShaderComposition, in fact my
preference would be to remove it from the OSG as it's just adding
weight and complexity to the OSG that offers less power than the
new approach. I know there are some users out there who've
worked/modified osg::ShaderComposition framework to work better,
so removing it completely will be an initial loss, but I feel
keeping the API simpler is worth that bit of pain. Porting to
#pragm(tic) shader composition should be hopefully be relatively
straight forward. The osgvirtualprogram provides another previous
attempt that now feel is eclipsed by the new approach so again I'm
keen to simple remove it so user don't got learning the wrong things.
For osgEarth, it has it's own VirtualProgram infrastructure that
works across a range of OSG versions, but I don't expect any
problems so once OSG-3.4 with the new shader composition
functionality both will happily work together. In fact the
#pragma's and StateSet::setDefine(..) functionality should
hopefully work out of the box with VirtualProgram and make it more
flexible and powerful. Longer term it could be mean that
osgEarth::VirtualProgram could be deprecated and new shaders
composition used instead, but I don't see any hurry to do this.
Now I've written this baby, it's over to you community, see what
fun things you can dream up to do with it ;-)
_______________________________________________
osg-users mailing list
osg-users@lists.openscenegraph.org
<mailto:osg-users@lists.openscenegraph.org>
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org
_______________________________________________
osg-users mailing list
osg-users@lists.openscenegraph.org
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org