I fixed the bump mapping effect!

I didn't think it would be so easy. I added a node visitor that traverses
all Drawables in the scene and runs an osgUtil::TangentSpaceGenerator on
them. The tangent vectors are added as texture coordinate array bound to
texture unit #1. The bump mapping effect is now much improved.

The remaining concern I have with the proposed sample is the hardcoded size
of the G buffers to 1024x1024 pixel texture rectangle, regardless of output
window size.

I'll wait for some feedback and discussion before providing any further
updates. Thanks for checking my submission.

Christian



2016-03-11 16:30 GMT+01:00 Christian Buchner <[email protected]>:

>
> A very minor correction to the Simple room model.  I had made a typo when
> I downscaled the texture coordinates manually by factor 10. So on one side
> wall the texture would be slightly distorted. This small update (see
> attachment) fixes it.
>
> At this point I am a bit puzzled by the shader "pass2.vert". It accesses
> gl_MultiTexCoord1.xyz to get a tangent vector for each vertex (has to be
> stored in object space, apparently).
>
> However in the entire code submission it appears no piece of code would be
> setting up any texture coordinate array bound on texture unit #1 (the code
> is not using osgUtil::TangentSpaceGenerator either). So in my opinion the
> t_worldspace and b_worldspace vectors will always be 0, which is why the
> bump mapping effect only scales the z coordinate of the normal vector in
> this expression in "pass2.frag". So
>
> vec4(nn.x * t_worldspace + nn.y * b_worldspace + nn.z * n_worldspace, 1.0);
>
> effectively becomes
>
> vec4(nn.z * n_worldspace, 1.0);
>
> Due to lack of tangent vectors, we get a somewhat incorrect bump mapping
> effect. It does not look quite as good as it should.
>
> Christian
>
>
>
> 2016-03-11 15:04 GMT+01:00 Christian Buchner <[email protected]>
> :
>
>> Apologies, I should have attached the actual code submission. I am doing
>> this now with my reply.
>> This submission is zipped to take minimal space.
>>
>> Christian
>>
>>
>> 2016-03-11 15:03 GMT+01:00 Christian Buchner <[email protected]
>> >:
>>
>>> Hi all,
>>>
>>> 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.
>>> Should we put these directly into the source code instead?
>>>
>>> Christian Buchner
>>>
>>>
>>
>
/* 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 <osg/AnimationPath>
#include <osg/PolygonMode>
#include <osgDB/ReadFile>
#include <osgShadow/SoftShadowMap>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgUtil/TangentSpaceGenerator>

#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<osg::TextureRectangle> 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<osg::Camera> 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<osg::LightSource> createLight(const osg::Vec3 &pos)
{
    osg::ref_ptr<osg::LightSource> 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() : tsg(new osgUtil::TangentSpaceGenerator) , NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN) {}
	virtual void apply(osg::Geode& geode)
	{
		for (unsigned int i = 0; i < geode.getNumDrawables(); ++i)
		{
			osg::Geometry *geo = dynamic_cast<osg::Geometry *>(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<osgUtil::TangentSpaceGenerator> tsg;
};

Pipeline createPipelinePlainOSG(
        osg::ref_ptr<osg::Group> scene,
        osg::ref_ptr<osgShadow::ShadowedScene> 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<osg::Camera> 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<osg::Camera> 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, "pass2.vert", "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<osg::Camera> pass3 =
        createRTTCamera(osg::Camera::COLOR_BUFFER, p.pass3Final, true);
    ss = setShaderProgram(pass3, "pass3.vert", "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<osg::Camera> 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<osg::Group> createSceneRoom()
{
    // Room.
    osg::ref_ptr<osg::MatrixTransform> room = new osg::MatrixTransform;
    osg::ref_ptr<osg::Node> roomModel = osgDB::readNodeFile("simpleroom.osgt");
    room->addChild(roomModel);
    room->setMatrix(osg::Matrix::translate(0, 0, 1));
    // Torus.
    osg::ref_ptr<osg::MatrixTransform> torus = new osg::MatrixTransform;
    osg::ref_ptr<osg::Node> torusModel = osgDB::readNodeFile("torus.osgt");
    torus->addChild(torusModel);
    setAnimationPath(torus, osg::Vec3(0, 0, 15), 6, 16);
    // Torus2.
    osg::ref_ptr<osg::MatrixTransform> torus2 = new osg::MatrixTransform;
    torus2->addChild(torusModel);
    setAnimationPath(torus2, osg::Vec3(-20, 0, 10), 20, 0);
    // Torus3.
    osg::ref_ptr<osg::MatrixTransform> torus3 = new osg::MatrixTransform;
    torus3->addChild(torusModel);
    setAnimationPath(torus3, osg::Vec3(0, 0, 40), 3, 25);
    // Scene.
    osg::ref_ptr<osg::Group> 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<osg::Geode> 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<osg::Texture2D> 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);
    return texture.release();
}

osg::ref_ptr<osg::Camera> createTextureDisplayQuad(
    const osg::Vec3 &pos,
    osg::StateAttribute *tex,
    float scale,
    float width,
    float height)
{
    osg::ref_ptr<osg::Camera> hc = createHUDCamera();
    hc->addChild(createScreenQuad(width, height, scale, pos));
    hc->getOrCreateStateSet()->setTextureAttributeAndModes(0, tex);
    return hc;
}

void setAnimationPath(osg::ref_ptr<osg::MatrixTransform> node,
                      const osg::Vec3 &center,
                      float time,
                      float radius)
{
    // Create animation.
    osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath;
    path->setLoopMode(osg::AnimationPath::LOOP);
    unsigned int numSamples = 32;
    float delta_yaw = 2.0f * osg::PI / (static_cast<float>(numSamples) - 1.0f);
    float delta_time = time / static_cast<float>(numSamples);
    for (unsigned int i = 0; i < numSamples; ++i)
    {
        float yaw = delta_yaw * static_cast<float>(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<float>(i),
                     osg::AnimationPath::ControlPoint(pos, rot));
    }
    // Assign it.
    node->setUpdateCallback(new osg::AnimationPathCallback(path));
}

osg::ref_ptr<osg::StateSet> setShaderProgram(osg::ref_ptr<osg::Camera> pass,
                                             std::string vert,
                                             std::string frag)
{
    osg::ref_ptr<osg::Program> program = new osg::Program;
    program->addShader(osgDB::readShaderFile(vert));
    program->addShader(osgDB::readShaderFile(frag));
    osg::ref_ptr<osg::StateSet> ss = pass->getOrCreateStateSet();
    ss->setAttributeAndModes(
        program.get(),
        osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
    return ss;
}

int main()
{
    // Useful declaration.
    osg::ref_ptr<osg::StateSet> ss;
    // Scene.
    osg::Vec3 lightPos(0, 0, 80);
    osg::ref_ptr<osg::Group> scene = createSceneRoom();
    osg::ref_ptr<osg::LightSource> light = createLight(lightPos);
    scene->addChild(light.get());
    // Shadowed scene.
    osg::ref_ptr<osgShadow::SoftShadowMap> shadowMap = new osgShadow::SoftShadowMap;
    shadowMap->setJitteringScale(16);
    shadowMap->addShader(osgDB::readShaderFile("pass1Shadow.frag"));
    shadowMap->setLight(light);
    osg::ref_ptr<osgShadow::ShadowedScene> 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<osg::Camera> qTexN =
        createTextureDisplayQuad(osg::Vec3(0, 0.7, 0),
                                 p.pass2Normals,
                                 p.textureSize);
    osg::ref_ptr<osg::Camera> qTexP =
        createTextureDisplayQuad(osg::Vec3(0, 0.35, 0),
                                 p.pass2Positions,
                                 p.textureSize);
    osg::ref_ptr<osg::Camera> qTexC =
        createTextureDisplayQuad(osg::Vec3(0, 0, 0),
                                 p.pass2Colors,
                                 p.textureSize);
    // Qaud to display 2 pass shadow texture.
    osg::ref_ptr<osg::Camera> qTexS =
        createTextureDisplayQuad(osg::Vec3(0.7, 0.7, 0),
                                 p.pass1Shadows,
                                 p.textureSize);
    // Quad to display 3 pass final (screen) texture.
    osg::ref_ptr<osg::Camera> 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;
    // 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());
    viewer.setRunMaxFrameRate(40);
    viewer.setUpViewInWindow(300, 100, 800, 600);
    return viewer.run();
}

_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to