Terrain Splatting with a Terrain Mesh

You are an experienced programmer and have a problem with the engine, shaders, or advanced effects? Here you'll get answers.
No questions about C++ programming or topics which are answered in the tutorials!
Post Reply
Sinsemilla
Posts: 37
Joined: Mon Jan 09, 2012 5:07 pm

Terrain Splatting with a Terrain Mesh

Post by Sinsemilla »

I finally managed to write my first Irrlicht Shader :D , it is Terrain splatting (based on freetimecoders snippet) and it's working quite good with a TerrainSceneNode. But i wanted more. I wanted to use it with a terrain mesh, which i added with addTerrainMesh, instead of using the terrain node. Unfortunately the way i draw the texture on the terrain seems to lead to a problem with the terrain mesh. I realize that my problem is probably setMaterialTexture which i use in my Shader Callback:

Code: Select all

m_pMultitextureNode->pNode->setMaterialTexture(0, m_pTextureSplat->pSplatTexture);
m_pMultitextureNode->pNode->setMaterialTexture(1, m_pTextureSplat->pRedTexture);
m_pMultitextureNode->pNode->setMaterialTexture(2, m_pTextureSplat->pGreenTexture);
m_pMultitextureNode->pNode->setMaterialTexture(3, m_pTextureSplat->pBlueTexture);
 
I also tried with:

Code: Select all

for (int i = 0; i < m_pMultitextureNode->pNode->getMaterialCount(); i++) {
    m_pMultitextureNode->pNode->getMaterial(i).setTexture(0, m_pTextureSplat->pSplatTexture);
    m_pMultitextureNode->pNode->getMaterial(i).setTexture(1, m_pTextureSplat->pRedTexture);
    m_pMultitextureNode->pNode->getMaterial(i).setTexture(2, m_pTextureSplat->pGreenTexture);
    m_pMultitextureNode->pNode->getMaterial(i).setTexture(3, m_pTextureSplat->pBlueTexture);
}
but in vain, the result is in both ways a 16-times repeated texture on the terrain mesh as one can see in the screenshot below. The question i have now, is how do i set the Texture correctly on the mesh in my shader callback, so that it isn't repeated?

Image

Example code: http://www.mediafire.com/?8531649t4bv4zt3
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Re: Terrain Splatting with a Terrain Mesh

Post by hybrid »

Texture repetition is always due to the texture coords. If you use numbers larger than 1 you get repetition. So maybe your coords are wrongly generated, or you use a wrong texture matrix, which can also change texture coords.
Sinsemilla
Posts: 37
Joined: Mon Jan 09, 2012 5:07 pm

Re: Terrain Splatting with a Terrain Mesh

Post by Sinsemilla »

Thanks for answer hybrid. Can you give me a hint on how to use texture coords?

Besides, i think the problem seems not to be lying in the shader, but in the use of setTextureMaterial on a terrain mesh. The screenshot shows the perfectly splatted texture, but simply repeated 16 times. If i do this without any use of shaders i get the exactly same result:

Code: Select all

video::ITexture* pTexture = m_pDriver->getTexture("./graphics/textures/terrain/terrain.png");
 
scene::IMesh* terrainMesh = m_pSceneMgr->addTerrainMesh(name.c_str(), heightMap, heightMap, core::dimension2d<f32>(1, 1), 10);
 
if (terrainMesh) {
    m_pTerrain = m_pSceneMgr->addMeshSceneNode(terrainMesh);
}
 
m_pTerrain->setMaterialTexture(0, pTexture);
 

Here are the shaders:
Vertex Shader:

Code: Select all

 
void main()
{
    gl_TexCoord[0] = gl_MultiTexCoord0;
    gl_TexCoord[1] = gl_MultiTexCoord1;
    gl_Position = ftransform();
}
 
Pixel Shader:

Code: Select all

uniform sampler2D splatMap;
uniform sampler2D layer_red;
uniform sampler2D layer_green;
uniform sampler2D layer_blue;
 
void main() {
    vec4 SplatCol = texture2D(splatMap, gl_TexCoord[0].xy);
    vec4 RedCol   = texture2D(layer_red, gl_TexCoord[1].xy);
    vec4 GreenCol = texture2D(layer_green, gl_TexCoord[1].xy);
    vec4 BlueCol  = texture2D(layer_blue, gl_TexCoord[1].xy);
 
    gl_FragColor  = vec4(SplatCol.r * RedCol + SplatCol.g * GreenCol + SplatCol.b * BlueCol) * vec4(1, 1, 1, SplatCol.a);
}

The shader Callback (TerrainTexturing.cpp):

Code: Select all

#include "TerrainTexturing.h"
 
TerrainTexturing::TerrainTexturing(IrrlichtDevice *pDevice, scene::ISceneManager* pSceneMgr)
{
    //Assign all parameters
    m_pSceneMgr = pSceneMgr;
    m_pDriver = pSceneMgr->getVideoDriver();
 
    io::path vertShader;
    io::path fragShader;
    io::path vsFile = "terrain.vsh";
    io::path psFile = "terrain.psh";
 
    // log action
    core::stringc msg = core::stringc("compiling material terrainsplat");
    pDevice->getLogger()->log(msg.c_str(), ELL_INFORMATION);
 
    // create destination path for (driver specific) shader files
    if (m_pDriver->getDriverType() == video::EDT_OPENGL) {
        vertShader = core::stringc("./graphics/shaders/glsl/") + vsFile;
        fragShader = core::stringc("./graphics/shaders/glsl/") + psFile;
    }
 
    //Create the shader material
    m_nShaderMaterial = m_pDriver->getGPUProgrammingServices()->addHighLevelShaderMaterialFromFiles(vertShader, "main", video::EVST_VS_1_1,
                                                                                                    fragShader, "main", video::EPST_PS_1_1,
                                                                                                    this, video::EMT_TRANSPARENT_ALPHA_CHANNEL);
    m_pTextureSplat = new STextureSplat();
    m_pMultitextureNode = new SMultiTextureNode;
}
 
TerrainTexturing::~TerrainTexturing()
{
    if (m_pMultitextureNode != NULL) {
        delete m_pMultitextureNode;
        m_pMultitextureNode = NULL;
    }
    if (m_pTextureSplat != NULL) {
        delete m_pTextureSplat;
        m_pTextureSplat = NULL;
    }
}
 
bool TerrainTexturing::addNode(scene::ISceneNode *pNode)
{
    if (m_pMultitextureNode->pNode == pNode) {
        return false;
    } else {
        m_pMultitextureNode->pNode = pNode;
    }
 
    return true;
}
 
TerrainTexturing::STextureSplat *TerrainTexturing::addTextures(scene::ISceneNode *pNode, video::ITexture *pSplat, video::ITexture *pRed, video::ITexture *pGreen, video::ITexture *pBlue)
{
    if (m_pMultitextureNode->pNode == pNode) {
        m_pTextureSplat->pRedTexture = pRed;
        m_pTextureSplat->pGreenTexture = pGreen;
        m_pTextureSplat->pBlueTexture = pBlue;
        m_pTextureSplat->pSplatTexture = pSplat;
        return m_pTextureSplat;
    }
    return NULL;
}
 
void TerrainTexturing::drawAll()
{
    //meshbuffer trick, only draw non culled nodes
    if (!m_pSceneMgr->isCulled(m_pMultitextureNode->pNode)) {
        m_pMultitextureNode->pNode->OnRegisterSceneNode();
        m_pMultitextureNode->pNode->updateAbsolutePosition();
 
        scene::IMesh* pMesh = ((scene::IMeshSceneNode*)m_pMultitextureNode->pNode)->getMesh();
 
        //Reset the transformation
        if (m_pMultitextureNode->pNode->getType() == scene::ESNT_MESH) {
            m_pDriver->setTransform(video::ETS_WORLD, core::IdentityMatrix);
        } else {
            m_pDriver->setTransform(video::ETS_WORLD, m_pMultitextureNode->pNode->getAbsoluteTransformation());
        }
 
        //set the splatting textures
        m_pMultitextureNode->pNode->setMaterialTexture(0, m_pTextureSplat->pSplatTexture);
        m_pMultitextureNode->pNode->setMaterialTexture(1, m_pTextureSplat->pRedTexture);
        m_pMultitextureNode->pNode->setMaterialTexture(2, m_pTextureSplat->pGreenTexture);
        m_pMultitextureNode->pNode->setMaterialTexture(3, m_pTextureSplat->pBlueTexture);
 
        //pass the shader material to the terrain node
        if (m_pMultitextureNode->pNode->getType() == scene::ESNT_MESH) {
            m_pMultitextureNode->pNode->setMaterialType((video::E_MATERIAL_TYPE)m_nShaderMaterial);
        }
    }
}
 
void TerrainTexturing::OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
{
    int layer0 = 0;
    int layer1 = 1;
    int layer2 = 2;
    int layer3 = 3;
 
    services->setPixelShaderConstant("splatMap", (float*)&layer0, 1);
    services->setPixelShaderConstant("layer_red", (float*)&layer1, 1);
    services->setPixelShaderConstant("layer_green", (float*)&layer2, 1);
    services->setPixelShaderConstant("layer_blue", (float*)&layer3, 1);
}
 
The shader Callback (TerrainTexturing.h):

Code: Select all

#ifndef TERRAINTEXTURING_H
#define TERRAINTEXTURING_H
 
#include <irrlicht.h>
#include <SMaterial.h>
 
using namespace irr;
 
class TerrainTexturing: public video::IShaderConstantSetCallBack
{
    public:
        struct STextureSplat {
            video::ITexture *pRedTexture;
            video::ITexture *pGreenTexture;
            video::ITexture *pBlueTexture;
            video::ITexture *pSplatTexture;
        };
 
    private:
        struct SMultiTextureNode {
            scene::ISceneNode *pNode;
            STextureSplat* pTextureSplat;
        };
 
    public:
        TerrainTexturing(IrrlichtDevice *pDevice, scene::ISceneManager* pSceneMgr);
        ~TerrainTexturing();
 
        void drawAll();
        virtual void OnSetConstants(video::IMaterialRendererServices* pServices,s32 userData);
        bool addNode(scene::ISceneNode *pNode);
        STextureSplat *addTextures(scene::ISceneNode *pNode, video::ITexture *pSplat, video::ITexture *pRed, video::ITexture *pGreen, video::ITexture *pBlue);
 
    private:
        scene::ISceneManager *m_pSceneMgr;
        video::IVideoDriver  *m_pDriver;
        scene::ISceneNode    *m_pNode;
        SMultiTextureNode    *m_pMultitextureNode;
        STextureSplat        *m_pTextureSplat;
        int                   m_nShaderMaterial;
 
};
 
#endif
 
And here is how i use it in my main:

Code: Select all

 
video::IImage* heightMap = m_pDriver->createImageFromFile("./graphics/textures/terrain/terrain-heightmap.png");
video::ITexture* pTexture = m_pDriver->getTexture("./graphics/textures/terrain/terrain-firstpass.png");
 
scene::IMesh* terrainMesh = m_pSceneMgr->addTerrainMesh(name.c_str(), heightMap, heightMap, core::dimension2d<f32>(1, 1), 10);
 
if (terrainMesh) {
     m_pTerrain = m_pSceneMgr->addMeshSceneNode(terrainMesh);
}
 
m_pTextureMgr->addNode((ISceneNode*)m_pTerrain);
 
m_pTextureMgr->addTextures((ISceneNode*)m_pTerrain,
m_pDriver->getTexture("./graphics/textures/terrain/terrain-secondpass.png"),
m_pDriver->getTexture("./graphics/textures/terrain/rock.jpg"),
m_pDriver->getTexture("./graphics/textures/terrain/grass.jpg"),
m_pDriver->getTexture("./graphics/textures/terrain/sand.jpg"));
 
int lastFPS = -1;
 
while (device->run()) {
    //Draw the scene
    m_pDriver->beginScene(true, true, video::SColor(50, 50, 50, 50));
 
    m_pSceneMgr->drawAll();
    m_pTextureMgr->drawAll();
 
    m_pDriver->endScene();
 
    int fps = m_pDriver->getFPS();
 
    if (lastFPS != fps) {
        core::stringw str = L"Texture Splatting with water - Move with Mouse and WASD/Arrow Keys - FPS:";
        str += fps;
 
        m_pDevice->setWindowCaption(str.c_str());
        lastFPS = fps;
    }
}
 
 
 
hybrid
Admin
Posts: 14143
Joined: Wed Apr 19, 2006 9:20 pm
Location: Oldenburg(Oldb), Germany
Contact:

Re: Terrain Splatting with a Terrain Mesh

Post by hybrid »

No, I guess it's the terrain mesh that sets the coords scale somehow. The terrain is built from blocks, each with their own texture coords range. This will automatically lead to repetition of the textures. Moreover, it looks like you don't have second texture coords, so your access to those will lead to some constant value. Maybe you're better off with the hillplane mesh as a base, though you have to create the height map offset on your own then. Or use some randomization as an overlay.
Post Reply