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
http://lists.openscenegraph.org/listinfo.cgi/osg-users-openscenegraph.org

Reply via email to