From 2ef6909d9bac946c7e5658a6a5cd6468156ef2d2 Mon Sep 17 00:00:00 2001 From: Christian Buchner Date: Thu, 19 May 2016 17:20:29 +0100 Subject: [PATCH] I am hereby submitting a deferred rendering code sample, originally written by Michael Kapelko in 2013. I am submitting this code with his approval. Deferred rendering is now the de-facto standard rendering technique in many modern game engines, hence I think it is important to have this technique demonstrated in an osg code example. This particular sample adds soft shadows as well as bump mapping into the rendering pipeline. The image files whitemetal_diffuse.jpg and whitemetal_normal.jpg from OpenSceneGraph-Data images folder are required (The OSG_FILE_PATH environment variable must be set correctly) Two additional osgt models are included with the demo (best to also put them into OpenSceneGraph-Data, I think. The shaders are currently defined in separate .frag and .vert files. --- examples/CMakeLists.txt | 1 + examples/osgdeferred/CMakeLists.txt | 12 + examples/osgdeferred/osgdeferred.cpp | 377 +++++++++++++++++++++++++++ examples/osgdeferred/osgdeferred.h | 66 +++++ runexamples.bat | 3 + 5 files changed, 459 insertions(+) create mode 100644 examples/osgdeferred/CMakeLists.txt create mode 100644 examples/osgdeferred/osgdeferred.cpp create mode 100644 examples/osgdeferred/osgdeferred.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b59c30930..6d3bce725 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -37,6 +37,7 @@ IF(DYNAMIC_OPENSCENEGRAPH) ADD_SUBDIRECTORY(osgcopy) ADD_SUBDIRECTORY(osgcubemap) ADD_SUBDIRECTORY(osgdelaunay) + ADD_SUBDIRECTORY(osgdeferred) ADD_SUBDIRECTORY(osgcluster) ADD_SUBDIRECTORY(osgdatabaserevisions) ADD_SUBDIRECTORY(osgdepthpartition) diff --git a/examples/osgdeferred/CMakeLists.txt b/examples/osgdeferred/CMakeLists.txt new file mode 100644 index 000000000..cc38a23f4 --- /dev/null +++ b/examples/osgdeferred/CMakeLists.txt @@ -0,0 +1,12 @@ +SET(TARGET_SRC + osgdeferred.cpp +) + +SET(TARGET_H + osgdeferred.h +) + +SET(TARGET_ADDED_LIBRARIES osgShadow) + +#### end var setup ### +SETUP_EXAMPLE(osgdeferred) diff --git a/examples/osgdeferred/osgdeferred.cpp b/examples/osgdeferred/osgdeferred.cpp new file mode 100644 index 000000000..4ace27341 --- /dev/null +++ b/examples/osgdeferred/osgdeferred.cpp @@ -0,0 +1,377 @@ + +/* OpenSceneGraph example, osgdeferred. + * + * Original code by Michael Kapelko, published to osg example with permission + * OSG Deferred Shading ( https://bitbucket.org/kornerr/osg-deferred-shading ) + * Shader cleanup, removal of osgFX EffectCompositor and exchange of textures + * done by Christian Buchner + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "osgdeferred.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef OSG_LIBRARY_STATIC +// in case of a static build... +USE_OSGPLUGIN(osg2) +USE_OSGPLUGIN(png) +USE_OSGPLUGIN(jpeg) +USE_OSGPLUGIN(glsl) +USE_SERIALIZER_WRAPPER_LIBRARY(osg) +USE_GRAPHICSWINDOW() +#endif + + +osg::TextureRectangle *createFloatTextureRectangle(int textureSize) +{ + osg::ref_ptr tex2D = new osg::TextureRectangle; + tex2D->setTextureSize(textureSize, textureSize); + tex2D->setInternalFormat(GL_RGBA16F_ARB); + tex2D->setSourceFormat(GL_RGBA); + tex2D->setSourceType(GL_FLOAT); + return tex2D.release(); +} + +osg::Camera *createHUDCamera(double left, + double right, + double bottom, + double top) +{ + osg::ref_ptr camera = new osg::Camera; + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setClearMask(GL_DEPTH_BUFFER_BIT); + camera->setRenderOrder(osg::Camera::POST_RENDER); + camera->setAllowEventFocus(false); + camera->setProjectionMatrix(osg::Matrix::ortho2D(left, right, bottom, top)); + camera->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + return camera.release(); +} + +osg::ref_ptr createLight(const osg::Vec3 &pos) +{ + osg::ref_ptr light = new osg::LightSource; + light->getLight()->setPosition(osg::Vec4(pos.x(), pos.y(), pos.z(), 1)); + light->getLight()->setAmbient(osg::Vec4(0.2, 0.2, 0.2, 1)); + light->getLight()->setDiffuse(osg::Vec4(0.8, 0.8, 0.8, 1)); + return light; +} + +class CreateTangentSpace : public osg::NodeVisitor +{ +public: + CreateTangentSpace() : NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN), tsg(new osgUtil::TangentSpaceGenerator) {} + virtual void apply(osg::Geode& geode) + { + for (unsigned int i = 0; i < geode.getNumDrawables(); ++i) + { + osg::Geometry *geo = dynamic_cast(geode.getDrawable(i)); + if (geo != NULL) + { + // assume the texture coordinate for normal maps is stored in unit #0 + tsg->generate(geo, 0); + // pass2.vert expects the tangent array to be stored inside gl_MultiTexCoord1 + geo->setTexCoordArray(1, tsg->getTangentArray()); + } + } + traverse(geode); + } +private: + osg::ref_ptr tsg; +}; + +Pipeline createPipelinePlainOSG( + osg::ref_ptr scene, + osg::ref_ptr shadowedScene, + const osg::Vec3 lightPos) +{ + Pipeline p; + p.graph = new osg::Group; + p.textureSize = 1024; + + // Pass 1 (shadow). + p.pass1Shadows = createFloatTextureRectangle(p.textureSize); + osg::ref_ptr pass1 = + createRTTCamera(osg::Camera::COLOR_BUFFER, p.pass1Shadows); + pass1->addChild(shadowedScene.get()); + + // pass2 shades expects tangent vectors to be available as texcoord array for texture #1 + // we use osgUtil::TangentSpaceGenerator to generate these + CreateTangentSpace cts; + scene->accept(cts); + + // Pass 2 (positions, normals, colors). + p.pass2Positions = createFloatTextureRectangle(p.textureSize); + p.pass2Normals = createFloatTextureRectangle(p.textureSize); + p.pass2Colors = createFloatTextureRectangle(p.textureSize); + osg::ref_ptr pass2 = + createRTTCamera(osg::Camera::COLOR_BUFFER0, p.pass2Positions); + pass2->attach(osg::Camera::COLOR_BUFFER1, p.pass2Normals); + pass2->attach(osg::Camera::COLOR_BUFFER2, p.pass2Colors); + pass2->addChild(scene.get()); + osg::StateSet *ss = setShaderProgram(pass2, "shaders/pass2.vert", "shaders/pass2.frag"); + ss->setTextureAttributeAndModes(0, createTexture("Images/whitemetal_diffuse.jpg")); + ss->setTextureAttributeAndModes(1, createTexture("Images/whitemetal_normal.jpg")); + ss->addUniform(new osg::Uniform("diffMap", 0)); + ss->addUniform(new osg::Uniform("bumpMap", 1)); + ss->addUniform(new osg::Uniform("useBumpMap", 1)); + + // Pass 3 (final). + p.pass3Final = createFloatTextureRectangle(p.textureSize); + osg::ref_ptr pass3 = + createRTTCamera(osg::Camera::COLOR_BUFFER, p.pass3Final, true); + ss = setShaderProgram(pass3, "shaders/pass3.vert", "shaders/pass3.frag"); + ss->setTextureAttributeAndModes(0, p.pass2Positions); + ss->setTextureAttributeAndModes(1, p.pass2Normals); + ss->setTextureAttributeAndModes(2, p.pass2Colors); + ss->setTextureAttributeAndModes(3, p.pass1Shadows); + ss->addUniform(new osg::Uniform("posMap", 0)); + ss->addUniform(new osg::Uniform("normalMap", 1)); + ss->addUniform(new osg::Uniform("colorMap", 2)); + ss->addUniform(new osg::Uniform("shadowMap", 3)); + // Light position. + ss->addUniform(new osg::Uniform("lightPos", lightPos)); + // Graph. + p.graph->addChild(pass1); + p.graph->addChild(pass2); + p.graph->addChild(pass3); + return p; +} + +osg::Camera *createRTTCamera(osg::Camera::BufferComponent buffer, + osg::Texture *tex, + bool isAbsolute) +{ + osg::ref_ptr camera = new osg::Camera; + camera->setClearColor(osg::Vec4()); + camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT); + camera->setRenderOrder(osg::Camera::PRE_RENDER); + if (tex) + { + tex->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR); + tex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); + camera->setViewport(0, 0, tex->getTextureWidth(), tex->getTextureHeight()); + camera->attach(buffer, tex); + } + if (isAbsolute) + { + camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF); + camera->setProjectionMatrix(osg::Matrix::ortho2D(0.0, 1.0, 0.0, 1.0)); + camera->setViewMatrix(osg::Matrix::identity()); + camera->addChild(createScreenQuad(1.0f, 1.0f)); + } + return camera.release(); +} + +osg::ref_ptr createSceneRoom() +{ + // Room. + osg::ref_ptr room = new osg::MatrixTransform; + osg::ref_ptr roomModel = osgDB::readNodeFile("simpleroom.osgt"); + room->addChild(roomModel); + room->setMatrix(osg::Matrix::translate(0, 0, 1)); + // Torus. + osg::ref_ptr torus = new osg::MatrixTransform; + osg::ref_ptr torusModel = osgDB::readNodeFile("torus.osgt"); + torus->addChild(torusModel); + setAnimationPath(torus, osg::Vec3(0, 0, 15), 6, 16); + // Torus2. + osg::ref_ptr torus2 = new osg::MatrixTransform; + torus2->addChild(torusModel); + setAnimationPath(torus2, osg::Vec3(-20, 0, 10), 20, 0); + // Torus3. + osg::ref_ptr torus3 = new osg::MatrixTransform; + torus3->addChild(torusModel); + setAnimationPath(torus3, osg::Vec3(0, 0, 40), 3, 25); + // Scene. + osg::ref_ptr scene = new osg::Group; + scene->addChild(room); + scene->addChild(torus); + scene->addChild(torus2); + scene->addChild(torus3); + return scene; +} + +osg::Geode *createScreenQuad(float width, + float height, + float scale, + osg::Vec3 corner) +{ + osg::Geometry* geom = osg::createTexturedQuadGeometry( + corner, + osg::Vec3(width, 0, 0), + osg::Vec3(0, height, 0), + 0, + 0, + scale, + scale); + osg::ref_ptr quad = new osg::Geode; + quad->addDrawable(geom); + int values = osg::StateAttribute::OFF | osg::StateAttribute::PROTECTED; + quad->getOrCreateStateSet()->setAttribute( + new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, + osg::PolygonMode::FILL), + values); + quad->getOrCreateStateSet()->setMode(GL_LIGHTING, values); + return quad.release(); +} + +osg::Texture2D *createTexture(const std::string &fileName) +{ + osg::ref_ptr texture = new osg::Texture2D; + texture->setImage(osgDB::readImageFile(fileName)); + texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT); + texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT); + texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR); + texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR); + texture->setMaxAnisotropy(16.0f); + return texture.release(); +} + +osg::ref_ptr createTextureDisplayQuad( + const osg::Vec3 &pos, + osg::StateAttribute *tex, + float scale, + float width, + float height) +{ + osg::ref_ptr hc = createHUDCamera(); + hc->addChild(createScreenQuad(width, height, scale, pos)); + hc->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex); + return hc; +} + +void setAnimationPath(osg::ref_ptr node, + const osg::Vec3 ¢er, + float time, + float radius) +{ + // Create animation. + osg::ref_ptr path = new osg::AnimationPath; + path->setLoopMode(osg::AnimationPath::LOOP); + unsigned int numSamples = 32; + float delta_yaw = 2.0f * osg::PI / (static_cast(numSamples) - 1.0f); + float delta_time = time / static_cast(numSamples); + for (unsigned int i = 0; i < numSamples; ++i) + { + float yaw = delta_yaw * static_cast(i); + osg::Vec3 pos(center.x() + sinf(yaw)*radius, + center.y() + cosf(yaw)*radius, + center.z()); + osg::Quat rot(-yaw, osg::Z_AXIS); + path->insert(delta_time * static_cast(i), + osg::AnimationPath::ControlPoint(pos, rot)); + } + // Assign it. + node->setUpdateCallback(new osg::AnimationPathCallback(path)); +} + +osg::ref_ptr setShaderProgram(osg::ref_ptr pass, + const std::string& vert, + const std::string& frag) +{ + osg::ref_ptr program = new osg::Program; + program->addShader(osgDB::readShaderFile(vert)); + program->addShader(osgDB::readShaderFile(frag)); + osg::ref_ptr ss = pass->getOrCreateStateSet(); + ss->setAttributeAndModes( + program.get(), + osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); + return ss; +} + +int main() +{ + // Useful declaration. + osg::ref_ptr ss; + // Scene. + osg::Vec3 lightPos(0, 0, 80); + osg::ref_ptr scene = createSceneRoom(); + osg::ref_ptr light = createLight(lightPos); + scene->addChild(light.get()); + // Shadowed scene. + osg::ref_ptr shadowMap = new osgShadow::SoftShadowMap; + shadowMap->setJitteringScale(16); + shadowMap->addShader(osgDB::readShaderFile("shaders/pass1Shadow.frag")); + shadowMap->setLight(light); + osg::ref_ptr shadowedScene = new osgShadow::ShadowedScene; + shadowedScene->setShadowTechnique(shadowMap.get()); + shadowedScene->addChild(scene.get()); + Pipeline p = createPipelinePlainOSG(scene, shadowedScene, lightPos); + // Quads to display 1 pass textures. + osg::ref_ptr qTexN = + createTextureDisplayQuad(osg::Vec3(0, 0.7, 0), + p.pass2Normals, + p.textureSize); + osg::ref_ptr qTexP = + createTextureDisplayQuad(osg::Vec3(0, 0.35, 0), + p.pass2Positions, + p.textureSize); + osg::ref_ptr qTexC = + createTextureDisplayQuad(osg::Vec3(0, 0, 0), + p.pass2Colors, + p.textureSize); + // Qaud to display 2 pass shadow texture. + osg::ref_ptr qTexS = + createTextureDisplayQuad(osg::Vec3(0.7, 0.7, 0), + p.pass1Shadows, + p.textureSize); + // Quad to display 3 pass final (screen) texture. + osg::ref_ptr qTexFinal = + createTextureDisplayQuad(osg::Vec3(0, 0, 0), + p.pass3Final, + p.textureSize, + 1, + 1); + // Must be processed before the first pass takes + // the result into pass1Shadows texture. + p.graph->insertChild(0, shadowedScene.get()); + // Quads are displayed in order, so the biggest one (final) must be first, + // otherwise other quads won't be visible. + p.graph->addChild(qTexFinal.get()); + p.graph->addChild(qTexN.get()); + p.graph->addChild(qTexP.get()); + p.graph->addChild(qTexC.get()); + p.graph->addChild(qTexS.get()); + + // Display everything. + osgViewer::Viewer viewer; + + // add the stats handler + viewer.addEventHandler(new osgViewer::StatsHandler); + + // Make screenshots with 'c'. + viewer.addEventHandler( + new osgViewer::ScreenCaptureHandler( + new osgViewer::ScreenCaptureHandler::WriteToFile( + "screenshot", + "png", + osgViewer::ScreenCaptureHandler::WriteToFile::OVERWRITE))); + + viewer.getCamera()->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR); + viewer.setSceneData(p.graph.get()); + + return viewer.run(); +} + diff --git a/examples/osgdeferred/osgdeferred.h b/examples/osgdeferred/osgdeferred.h new file mode 100644 index 000000000..dcfc05142 --- /dev/null +++ b/examples/osgdeferred/osgdeferred.h @@ -0,0 +1,66 @@ + +#include +#include +#include +#include +#include +#include + +struct Pipeline +{ + int textureSize; + osg::ref_ptr graph; + osg::Texture *pass1Shadows; + osg::Texture *pass2Colors; + osg::Texture *pass2Normals; + osg::Texture *pass2Positions; + osg::Texture *pass3Final; +}; + +osg::TextureRectangle *createFloatTextureRectangle(); + +osg::Camera *createHUDCamera(double left = 0, + double right = 1, + double bottom = 0, + double top = 1); + +osg::ref_ptr createLight(const osg::Vec3 &pos); + +Pipeline createPipelineEffectCompositor( + osg::ref_ptr scene, + osg::ref_ptr shadowedScene, + const osg::Vec3 lightPos); + +Pipeline createPipelinePlainOSG( + osg::ref_ptr scene, + osg::ref_ptr shadowedScene, + const osg::Vec3 lightPos); + +osg::Camera *createRTTCamera(osg::Camera::BufferComponent buffer, + osg::Texture *tex, + bool isAbsolute = false); + +osg::ref_ptr createSceneRoom(); + +osg::Geode *createScreenQuad(float width, + float height, + float scale = 1, + osg::Vec3 corner = osg::Vec3()); + +osg::Texture2D *createTexture(const std::string &fileName); + +osg::ref_ptr createTextureDisplayQuad(const osg::Vec3 &pos, + osg::StateAttribute *tex, + float scale, + float width = 0.3, + float height = 0.2); + +void setAnimationPath(osg::ref_ptr node, + const osg::Vec3 ¢er, + float time, + float radius); + +osg::ref_ptr setShaderProgram(osg::ref_ptr pass, + const std::string& vert, + const std::string& frag); + diff --git a/runexamples.bat b/runexamples.bat index 19c454e1d..3ccf98782 100644 --- a/runexamples.bat +++ b/runexamples.bat @@ -51,6 +51,9 @@ osgcubemap echo osgdistortion osgdistortion +echo osgdeferred +osgdeferred + echo osgforest osgforest