OpenWalnut  1.5.0dev
WMTemplateRenderPipelines.cpp
1 //---------------------------------------------------------------------------
2 //
3 // Project: OpenWalnut ( http://www.openwalnut.org )
4 //
5 // Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6 // For more information see http://www.openwalnut.org/copying
7 //
8 // This file is part of OpenWalnut.
9 //
10 // OpenWalnut is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // OpenWalnut is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
22 //
23 //---------------------------------------------------------------------------
24 
25 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
26 // This is a tutorial on how to use the WGEOfffscreen interface. This tutorial also includes some shader files in the shaders subdirectory. You
27 // will be referred to them later.
28 //
29 // You will need the knowledge of these tutorials before you can go on:
30 // * WMTemplate
31 // * WMTemplateShaders
32 //
33 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
34 
35 #include <memory>
36 #include <string>
37 
38 #include <osg/StateAttribute>
39 
40 #include "WDemoGeometry.h"
41 #include "WMTemplateRenderPipelines.h"
42 #include "core/graphicsEngine/WGEManagedGroupNode.h"
43 #include "core/graphicsEngine/WGERequirement.h"
44 #include "core/graphicsEngine/callbacks/WGENodeMaskCallback.h"
45 #include "core/graphicsEngine/offscreen/WGEOffscreenRenderNode.h" // <- this is the awesome new header you will need
46 #include "core/graphicsEngine/shaders/WGEShader.h"
47 #include "core/kernel/WKernel.h"
48 
49 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
50 // All the basic setup ... Refer to WMTemplate.cpp if you do not understand these commands.
51 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
52 
54  : WModule()
55 {
56 }
57 
59 {
60 }
61 
62 std::shared_ptr< WModule > WMTemplateRenderPipelines::factory() const
63 {
64  // NOTE: Refer to WMTemplate.cpp if you do not understand these commands.
65  return std::shared_ptr< WModule >( new WMTemplateRenderPipelines() );
66 }
67 
68 const std::string WMTemplateRenderPipelines::getName() const
69 {
70  // NOTE: Refer to WMTemplate.cpp if you do not understand these commands.
71  return "Template Render Pipelines";
72 }
73 
75 {
76  // NOTE: Refer to WMTemplate.cpp if you do not understand these commands.
77  return "Show how to use colormapping in your modules.";
78 }
79 
81 {
82  // NOTE: Refer to WMTemplate.cpp if you do not understand these commands.
83 
84  // We do not need any connectors. Have a look at WMTemplate.cpp if you want to know what this means.
86 }
87 
89 {
90  // NOTE: Refer to WMTemplate.cpp if you do not understand these commands.
91 
92  m_propCondition = std::shared_ptr< WCondition >( new WCondition() );
93 
94  // show hud?
95  m_showHUD = m_properties->addProperty( "Show HUD", "Check to enable the debugging texture HUD.", true );
96 
98 }
99 
101 {
102  // NOTE: Refer to WMTemplate.cpp if you do not understand these commands.
103 
104  // We need graphics to draw anything:
105  m_requirements.push_back( new WGERequirement() );
106 }
107 
108 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
109 // ATTENTION: now it gets interesting ...
110 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
111 
113 {
114  m_moduleState.setResetable( true, true );
116 
117  // Now, we can mark the module ready.
118  ready();
119 
120  // In this tutorial, we will show you how to develop quite complex offscreen render pipelines with ease in OpenWalnut. For this, we use a
121  // rather simple example. We want to render some geometry in a cartoonish way. Although this can be done in one additional pass, we split it
122  // into two additional passes here to demonstrate WGEOffscreen* a little bit more in detail.
123  //
124  // NOTE: Refer to WMTemplateShaders.cpp if you do not understand the next sections. It is important to understand these basics before going
125  // on.
126  //
127  // First, a short overview on offscreen rendering. Nowadays in OpenGL and OSG, offscreen rendering is done using framebuffer objects (FBO).
128  // You can attach textures to certain targets on the FBO. For example, your shader wants to output a color, a normal and you will need the
129  // depth. Then you attach the first two color buffers and the depth buffer. So you get three textures. After the draw call (done internally
130  // by OSG), the textures will be filled with a color, normals and your requested depth buffer. You can then bind these textures to another
131  // state and re-use them. To write a per-pixel processing, you will use a full-screen quad and bind the mentioned textures to it. Then, you
132  // can utilize a fragment shader to process each input texture texel which matches a pixel. To write your resulting image to the on-screen
133  // framebuffer, you de-activate (unbind in OpenGL terms) the FBO and render a full-screen quad. Your fragment shader then defines the final
134  // pixel color and depth.
135  //
136  // OpenWalnut's offscreen render mechanism also follows this principles. What we have done is providing a comfortable interface to OSG
137  // cameras (they represent the FBO) and a comfortable way to bind and attach textures. This way, you are able to write complex pipelines
138  // somehow like you would write module pipelines: define a render pass, bind its inputs, define its outputs. In this tutorial, you will learn
139  // how to do this. Remember that you can also use the mechanisms shown in WMTemplateShaders to define complex parameters and switches for the
140  // shaders you use in each pass.
141  //
142  // Now lets get started ...
143  //
144 
145  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
146  // 1. Setup some geometry.
147  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
148 
149  // Create a module root node.
150  osg::ref_ptr< WGEGroupNode > rootNode = new WGEGroupNode();
151  WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( rootNode );
152 
153  // So let us start by creating our geometry. This is taken from the WMTemplateShaders example.
154  osg::ref_ptr< osg::MatrixTransform > sceneNode = new osg::MatrixTransform();
155  sceneNode->setMatrix( osg::Matrixd::rotate( 1.57, 1.0, 0.0, 0.0 ) ); // First parameter is the angle in radians.
156 
157  // Now we can add your demo geometry:
158  osg::ref_ptr< osg::Node > spheres = WDemoGeometry::createSphereGeometry();
159  osg::ref_ptr< osg::Node > plane = WDemoGeometry::createPlaneGeometry();
160 
161  // Allow blending here? Yes. We need it later.
162  plane->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
163  plane->getOrCreateStateSet()->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
164 
165  // To see our new geometry we need to add it to the group of ours:
166  sceneNode->addChild( spheres );
167  sceneNode->addChild( plane );
168 
169  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
170  // 2. Setup the offscreen pipeline.
171  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
172 
173  // First, is to create a node in the scene-graph, which will encapsulate our render pipeline:
174  osg::ref_ptr< WGEOffscreenRenderNode > offscreen(
176  // Specify a reference camera all the offscreen passes will use
177  WKernel::getRunningKernel()->getGraphicsEngine()->getViewer()->getCamera()
178  )
179  );
180  // The reference you have to specify is important to keep certain states in sync with it when you add further render passes.
181 
182  // Now, we create all the passes we need. Remember the above description about what we are going to do? We will create two passes now that do
183  // a simple edge detection and a color-reduction (cel-shading alike). The results have to be merged again and shown on screen. This will be
184  // the final on-screen render pass.
185 
186  // First pass: render the geometry to textures. This can be done by addGeometryRenderPass:
187  osg::ref_ptr< WGEOffscreenRenderPass > renderToTexture = offscreen->addGeometryRenderPass(
188  sceneNode, // The node to render
189  new WGEShader( "WMTemplateRenderPipelines-Render", m_localPath ), // The shader to use
190  "Render" // A name. Useful for debugging
191  );
192  // You might now ask why we need the shader for the creation of the pass? Well it is not needed. There are methods available which do not
193  // require them. It simply applies your shader to the render pass itself.
194 
195  // Edge Detection:
196  osg::ref_ptr< WGEOffscreenRenderPass > edgeDetection = offscreen->addTextureProcessingPass(
197  new WGEShader( "WMTemplateRenderPipelines-Edge", m_localPath ),
198  "Edge Detection"
199  );
200 
201  // Cel Shader:
202  osg::ref_ptr< WGEOffscreenRenderPass > celShading = offscreen->addTextureProcessingPass(
203  new WGEShader( "WMTemplateRenderPipelines-Cel", m_localPath ),
204  "Cel Shading"
205  );
206 
207  // Finally, we want to merge the results on screen:
208  osg::ref_ptr< WGEOffscreenRenderPass > finalPass = offscreen->addFinalOnScreenPass(
209  new WGEShader( "WMTemplateRenderPipelines-Merge", m_localPath ),
210  "Merge"
211  );
212 
213  // Thats it. We now render our geometry to a texture. But to use this rendering result in both processing passes, we have to connect the
214  // output of the first geometry render pass to the processing passes:
215 
216  // So we need to tell the offscreen pass to attach the according outputs for us:
217  osg::ref_ptr< osg::Texture2D > geometryColor = renderToTexture->attach(
218  WGECamera::COLOR_BUFFER0, // Which output?
219  GL_RGBA // Its pixel format.
220  );
221  osg::ref_ptr< osg::Texture2D > geometryDepth = renderToTexture->attach( WGECamera::DEPTH_BUFFER );
222 
223  // The reason for choosing RGBA as pixel format is simple: we need to transport which pixel was occupied by a fragment. The default clear
224  // color of all passes is vec4(0,0,0,0). This means, no fragment -> alpha == 0.
225 
226  // ... and bind these textures to both processing passes:
227  edgeDetection->bind( geometryDepth, 0 );
228  celShading->bind( geometryColor, 0 );
229 
230  // That was easy, right? Please keep in mind that each render pass is basically an OSG Camera and works as an framebuffer object.
231 
232  // Lets go on and connect the output of the processing passes to the final on-screen pass. But first? Yes! Attach the outputs:
233  osg::ref_ptr< osg::Texture2D > edges = edgeDetection->attach(
234  WGECamera::COLOR_BUFFER0, // Which output?
235  GL_LUMINANCE // Only gray colors are needed
236  );
237 
238  osg::ref_ptr< osg::Texture2D > colors = celShading->attach(
239  WGECamera::COLOR_BUFFER0, // Which output?
240  GL_RGBA // RGBA
241  );
242 
243  // Now we can bind them. Just like textures in a stateset (and in fact, it is not more than that).
244  finalPass->bind( colors, 0 );
245  finalPass->bind( edges, 1 );
246 
247  // The final pass should also blend properly:
248  finalPass->getOrCreateStateSet()->setMode( GL_BLEND, osg::StateAttribute::ON );
249 
250  // Finally, just like every other node, ADD to the node.
251  rootNode->insert( offscreen );
252 
253  // IMPORTANT!
254  //
255  // OSG automatically culls invisible geometry and modifies nested camera clipping settings. This means for us that we have to tell OSG the
256  // real size of our geometry. Or else, OSG will think we are a thin plane, thus changing near-far clipping for the camera rendering the
257  // geometry which isn't just a thin plane. Thats somehow a bug or a feature. We do not know. To avoid this, we can add a cull proxy to our
258  // scene. If you know how to properly fix this (might even be located in the WGEOffscreen classes), please tell us.
259 
260  // You usually get this from your dataset grid. We hardcode it.
261  WBoundingBox bbox( -100.0, -100.0, -100.0, 200.0, 200.0, 200.0 );
262  // Cull proxy. Updated on dataset change
263  osg::ref_ptr< osg::Node > cullProxy = wge::generateCullProxy( bbox );
264  rootNode->insert( cullProxy );
265 
266  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
267  // 4. Debugging
268  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
269 
270  // Offscreen rendering has a drawback. You usually cannot see the intermediate results (textures). For this, OpenWalnut provides us with a
271  // little helper: the texture HUD.
272 
273  // The texture hud is already a part of WGEOffscreenRenderNode. Your task is to activate it. It basically is an osg::Node and thus, can be
274  // activated by its node mask, a callback and a property:
275  offscreen->getTextureHUD()->addUpdateCallback( new WGENodeMaskCallback( m_showHUD ) );
276 
277  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
278  // 5. Shaders!
279  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
280 
281  // Now head over to the shaders. Start with WMTemplateRenderPipelines-Render-vertex.glsl. These shaders are trivial. There is nothing special
282  // to take care of. Especially the vertex shaders are boring. The fragment shaders can be used to process each pixel of your textures ...
283 
284  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
285  // 6. ... do stuff.
286  ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
287 
288  // You already know this module loop code. You can now use your knowledge from WMTemplateShaders to implement cool features and control them
289  // in you shaders of the pipelines ...
290 
291  // Now the remaining module code. In our case, this is empty.
292  while( !m_shutdownFlag() )
293  {
295  if( m_shutdownFlag() )
296  {
297  break;
298  }
299  }
300 
301  // Never miss to clean up. Especially remove your OSG nodes. Everything else you add to these nodes will be removed automatically.
302  debugLog() << "Shutting down ...";
303  WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( rootNode );
304 }
305 
virtual void wait() const
Wait for the condition.
void setResetable(bool resetable=true, bool autoReset=true)
Sets the resetable flag.
virtual void add(std::shared_ptr< WCondition > condition)
Adds another condition to the set of conditions to wait for.
Class to encapsulate boost::condition_variable_any.
Definition: WCondition.h:42
Class to wrap around the osg Group node and providing a thread safe add/removal mechanism.
Definition: WGEGroupNode.h:48
This callback is useful to en-/disable nodes using the node mask based on properties.
This type of node basically is a convenience class for managing and creating offscreen renderings.
This requirement ensures an up and running WGE.
Class encapsulating the OSG Program class for a more convenient way of adding and modifying shader.
Definition: WGEShader.h:48
static WKernel * getRunningKernel()
Returns pointer to the currently running kernel.
Definition: WKernel.cpp:117
std::shared_ptr< WGraphicsEngine > getGraphicsEngine() const
Returns pointer to currently running instance of graphics engine.
Definition: WKernel.cpp:122
virtual std::shared_ptr< WModule > factory() const
Due to the prototype design pattern used to build modules, this method returns a new instance of this...
virtual void properties()
Initialize the properties for this module.
virtual const std::string getDescription() const
Gives back a description of this module.
virtual ~WMTemplateRenderPipelines()
Destructor.
virtual void connectors()
Initialize the connectors this module is using.
virtual void moduleMain()
Entry point after loading the module.
std::shared_ptr< WCondition > m_propCondition
A condition for property updates.
virtual void requirements()
Initialize requirements for this module.
WPropBool m_showHUD
Enable debug hud.
virtual const std::string getName() const
Gives back the name of this module.
Class representing a single module of OpenWalnut.
Definition: WModule.h:72
boost::filesystem::path m_localPath
The path where the module binary resides in.
Definition: WModule.h:734
Requirements m_requirements
The list of requirements.
Definition: WModule.h:754
virtual void properties()
Initialize properties in this function.
Definition: WModule.cpp:212
wlog::WStreamedLogger debugLog() const
Logger instance for comfortable debug logging.
Definition: WModule.cpp:575
std::shared_ptr< WProperties > m_properties
The property object for the module.
Definition: WModule.h:640
void ready()
Call this whenever your module is ready and can react on property changes.
Definition: WModule.cpp:505
WConditionSet m_moduleState
The internal state of the module.
Definition: WModule.h:703
virtual void connectors()
Initialize connectors in this function.
Definition: WModule.cpp:208
WBoolFlag m_shutdownFlag
Condition getting fired whenever the thread should quit.
osg::ref_ptr< osg::Node > createSphereGeometry()
Create several spheres.
osg::ref_ptr< osg::Node > createPlaneGeometry()
Create a plane.
osg::ref_ptr< osg::Node > generateCullProxy(const WBoundingBox &bbox)
Generate a proxy cube, which ensures OSG does proper near-far plane calculation and culling.
Definition: WGEUtils.cpp:236