Dynamical splatting texture loading

pra

20-06-2008 20:58:48

I'm trying to load ET Terrain from a XML file, where the number of splatting textures and coverage maps is not limited. It alerady generates a custom material, but then I noticed the PSSplat2.cg part...

So, I'm limited to 2 coverage maps and 6 splatting textures per pass, unless I edit this file.
I *guess* that I can add a second pass with 2 additional coverage maps and another set of 6 textures, but i'm not sure...
I also guess that each pass that uses PSSplat2 must have 2 coverage maps and 6 textures.

So, should I:
-add more passes as soon as I have more than 6 textures, and fill the rest with blank data, or use another fragment program for the second pass, if I need less textures?
-dynamically create a suitable fragment program? (is this possible at all?)
-other solution?

and, I was also not able to set the fragment program's parameters dynamically, I had to steal them from a preloaded material. I tried it this way:
splatPass->setFragmentProgram("ET/Programs/PSSplat2");
GpuProgramParametersSharedPtr params = splatPass->getFragmentProgramParameters();
params->setNamedConstant("splatScaleX",20);
params->setNamedConstant("splatScaleZ",20);
splatPass->setFragmentProgramParameters(params);

but it gives me an assertation failure in setNamedConstant

CABAListic

20-06-2008 21:50:43

I'm trying to load ET Terrain from a XML file, where the number of splatting textures and coverage maps is not limited. It alerady generates a custom material, but then I noticed the PSSplat2.cg part...

So, I'm limited to 2 coverage maps and 6 splatting textures per pass, unless I edit this file.
I *guess* that I can add a second pass with 2 additional coverage maps and another set of 6 textures, but i'm not sure...
I also guess that each pass that uses PSSplat2 must have 2 coverage maps and 6 textures.

So, should I:
-add more passes as soon as I have more than 6 textures, and fill the rest with blank data, or use another fragment program for the second pass, if I need less textures?
-dynamically create a suitable fragment program? (is this possible at all?)
-other solution?


PSSplat2.cg is really just a demo shader. If it fulfills your needs, fine, but don't let yourself be limited by sticking to it :)
The situation is like this: A fragment program with shader model 2 can use up to 16 texture units (though only newer cards actually have 16 physical texture units, the rest will likely split the pass internally). Assuming that your coverage maps have three channels, you could use them for four coverage maps and 12 splatting textures. This is the limit that you can get into a single pass (in theory).
But of course you can add another pass to use even more textures. Here you can leave vacant texture units blank (i. e. you don't need to have two coverage maps), but you must ensure that you don't skip a texture unit, otherwise one of your splatting textures might be interpreted as the missing coverage map.
However, if you are looking for maximal performance, you will always use a shader which is written for exactly the right number of splatting textures that you'll use in this pass. This ensures that your shaders have exactly as many operations as needed and no more. You can create these shaders dynamically, but since there are only 12 different versions you need, I'd just create them statically via copy&paste.


and, I was also not able to set the fragment program's parameters dynamically, I had to steal them from a preloaded material. I tried it this way:
splatPass->setFragmentProgram("ET/Programs/PSSplat2");
GpuProgramParametersSharedPtr params = splatPass->getFragmentProgramParameters();
params->setNamedConstant("splatScaleX",20);
params->setNamedConstant("splatScaleZ",20);
splatPass->setFragmentProgramParameters(params);

but it gives me an assertation failure in setNamedConstant

The last line is unneeded and perhaps wrong. Apart from that, I have that exact same code in usage, and it's working fine for me.

pra

20-06-2008 23:13:26

ok, thanks, i think I understand it now..

it doesn't even reach the last line. I found a workaround, though:
splattingPass->setFragmentProgram("ET/Programs/PSSplat2");
splattingPass->_load();
splattingPass->getFragmentProgram()->setParameter("splatScaleX","20");
splattingPass->getFragmentProgram()->setParameter("splatScaleZ","20");

i somehow doubt that this is a clean solution, though

pra

28-06-2008 18:59:36

ok... i now have the 12 shaders and also 3 for the fallback technique (it's always 1 coverage map and <= 3 textures per pass, right?)
How bad is it to have multiple passes, and should I try to put as much textures into a single pass as possible, or try to make multiple passes?
You said, passes can be automatically split internally, but is it better to make the graphics card do it, or split them by myself? (i have a function where i just have to specify the desired max. textures per pass, so it doesn't matter for me)

CABAListic

28-06-2008 23:59:39

You'll want as few passes as possible - or perhaps rather as few operations as possible. I'm no expert on the workings of graphics cards, but my approach is to get as much done in a pass as I can and let the card deal with it. If it has to split it, fine, it should be able to do that better than I.
Just be careful not to do more than you need to. If you have just two textures to splat, then it would be wasteful to apply a 12-texture shader because it'd be a lot of unnecessary operations.

pra

29-06-2008 01:48:43

ok, then i'll try to always put as much in a pass as possible.

yes, i check how much textures will be needed in the current pass, and apply the corresponding shader. So if I have 26 textures, it would result in 3 passes, two using the shader for 12 textures, and one the one for 2.

And another thing: if I want to dynamically exclude a texture, is there a way to remove it's mask from the coverage map? because I might want to add another texture after that.
Or if I want to remove one from the middle, and want to close the gap. Man, i think this will be complicated...

CABAListic

29-06-2008 10:58:15

No, that's not going to work, you'd have to recompute the coverage maps.

SongOfTheWeave

01-07-2008 04:05:39

ok, then i'll try to always put as much in a pass as possible.

yes, i check how much textures will be needed in the current pass, and apply the corresponding shader. So if I have 26 textures, it would result in 3 passes, two using the shader for 12 textures, and one the one for 2.

And another thing: if I want to dynamically exclude a texture, is there a way to remove it's mask from the coverage map? because I might want to add another texture after that.
Or if I want to remove one from the middle, and want to close the gap. Man, i think this will be complicated...


My approach is to choose a max allowable number of textures and then always splat that number of textures. The user can choose not to use all the available "texture slots" if they don't need them. Then provide a mechanism for swapping which texture is associated with each "texture slot" so they can change their texture set if they find they want to use a different texture once they've already filled all the "slots"

pra

15-07-2008 18:34:57

I want now to try to dynamically change the splatting textures.
To do that, I want to create new coverage maps basing on the old ones by extracting the necessary channels from the old image and adding it to the new one.
The function CoverageMap::getFormat gives me a hint how the image's channels are used, but how exactly to access it?
It looks like, when, for example, I have a PF_BYTE_RGBA buffer, then for the first pixel it is buffer[0]=R, buffer[1]=G, buffer[2]=B, buffer[3]=A, and then there are four values for the second pixel and so on. Am I correct?


edit: hmm, it seems to actually work, more or less. If I remove a texture, the parts it covered become black. I suppose that's because of the black parts in in the other coverage maps, they are some kind of "holes", right?

What I do now is creating new Ogre::Images for the new covmaps, going through the new textures, looking if there should be a channel alerady, and then taking the values from the old coverage map and setting the new one accordingly. Seems not to be enough.
Can you tell me more about the way coverage maps work, especially if there are multiple?

(I could also post my code, it's not that long, but it's full of german comments)

pra

17-07-2008 20:12:33

Aand another thing: how do I have to change the shader to use 4 channels for splatting?

I guess the parts that look like
float3 cov1 = tex2D(covMap1, iTexCoord0).rgb;
makes it only use the 3 colour channels, ignoring the alpha.
i would guess i have to change it to
float4 cov1 = tex2D(covMap1, iTexCoord0).rgba;
but what to do later, then cov1.x, y and z are used? what's the 4th coordinate's name?

And, is there a reason you used 3 channels in the demo? are there issues with using 4?

CABAListic

17-07-2008 20:41:09

Aand another thing: how do I have to change the shader to use 4 channels for splatting?

I guess the parts that look like
float3 cov1 = tex2D(covMap1, iTexCoord0).rgb;
makes it only use the 3 colour channels, ignoring the alpha.
i would guess i have to change it to
float4 cov1 = tex2D(covMap1, iTexCoord0).rgba;
but what to do later, then cov1.x, y and z are used? what's the 4th coordinate's name?

The coordinate names in shaders are x, y, z and w.

And, is there a reason you used 3 channels in the demo? are there issues with using 4?
No issues, but 3 channel maps can be used in a pixel shader 1.1, so it's backwards compatible. To render a 4-channel map you need at least pixel shader 1.4. If you don't care about that (seeing as most cards nowadays probably have shader model 2 support), then 4-channel coverage maps are fine.

pra

17-07-2008 21:37:11

I see... I think backwards compatibility is a reason, so i'll stick with 3 channels.

edit: dammit... i still can't figure out how to detect holes that go right through all coverage maps

pra

18-07-2008 02:38:40

It seems i've actually done it...
here is the code I use to update the coverage maps after changing the splatting textures:


//newList is a StringVector with the filenames of the new textures
//terrainTextures is a stringvector with the old textures' filenames
size_t newTexCount = newList.size();


unsigned int numCovMaps = Math::ICeil(float(newTexCount) / float(splatChannels));
std::vector<Image> newCovmaps;
newCovmaps.reserve(numCovMaps);
unsigned int curCovmap = 0;
unsigned int curChannel= 0;
//splatWidth and splatHeight are the dimensions of the coverage maps
unsigned int bufSize = splatChannels*splatWidth*splatHeight;
for(unsigned int i=0;i<numCovMaps;i++)
{

Image curImage;
uchar *buffer = new uchar[bufSize];
initBuffer(buffer,bufSize,0);//just fills the buffer with zeroes

for(unsigned int j=0;j<splatChannels;j++)
{
unsigned int curTexIndex = i*numCovMaps+j;
if(curTexIndex>=newList.size())
break;
String curTex = newList[curTexIndex];
int oldIndex = getOldTextureIndex(curTex); //just searches in terrainTextures for the string, returns -1 if not found
if(oldIndex != -1)
{

Image img;
mSplatMgr->saveMapToImage(covmapNrFromTextureIndex(oldIndex,splatChannels),img);

int channelInImg = oldIndex % splatChannels;

//this parts copies the channel
uchar *oldBuffer = img.getData();
for(unsigned int k=0;k<bufSize;k += splatChannels)
{
buffer[k+j] = oldBuffer[k+channelInImg];
}

}
}
curImage.loadDynamicImage(buffer,splatWidth,splatHeight,1,numChannelsToPixelFormat(splatChannels),true);
newCovmaps.push_back(curImage);
}
//this part should remove the "holes" in the coverage maps, caused through the removal of some channels
//here i alerady assume splatChannels to be 3, since I decided to hardcode it that way. not sure how it would be with 4 channels, but it seems like that the sum from all channels from all covmaps at a specific position has to be 255
for(size_t i=0;i<bufSize;i+=3)
{

uchar p1 = 0;
uchar p2 = 0;
uchar p3 = 0;
for(size_t j=1;j<newCovmaps.size();j++)
{
p1 += newCovmaps[j].getData()[i];
p2 += newCovmaps[j].getData()[i+1];
p3 += newCovmaps[j].getData()[i+2];


}
if(p1+p2+p3+newCovmaps[0].getData()[i]
+newCovmaps[0].getData()[i+1]
+newCovmaps[0].getData()[i+2] < 255)
{
newCovmaps[0].getData()[i] = 255-p1-p2-p3-newCovmaps[0].getData()[i+1]-newCovmaps[0].getData()[i+2];
}



}
terrainTextures = newList;
mSplatMgr->setNumTextures(newTexCount);
for(uint i = 0;i<mSplatMgr->getNumMaps();i++)
{
mSplatMgr->loadMapFromImage(i, newCovmaps[i]);
}
//generate the dynamic material. i think you have such functions, too
createETMaterial();

updateTerrainLightmap();
mTerrainMgr->setMaterial(terrainMaterial);

pra

25-07-2008 20:23:15

accidentally I found a problem:
as soon as I add 10 or more textures, I can only paint with the ones from the last coverage map. for other textures, it just paints with black. The decal is invisible, too
here is my automatically generated material:

material ETTerrainMaterial
{
technique
{
pass
{
lighting off

vertex_program_ref ET/Programs/VSLodMorph2
{
}

fragment_program_ref ET/Programs/Splat_10tex
{
param_named splatScaleX float4 20 0 0 0
param_named splatScaleZ float4 20 0 0 0
}

texture_unit
{
texture ETSplatting0
}

texture_unit
{
texture ETSplatting1
}

texture_unit
{
texture ETSplatting2
}

texture_unit
{
texture ETSplatting3
}

texture_unit
{
texture default.png
}

texture_unit
{
texture big_kiesel_white.01.png
}

texture_unit
{
texture GreenSkin.png
}

texture_unit
{
texture lava.png
}

texture_unit
{
texture rockwall.tga
}

texture_unit
{
texture scrin.png
}

texture_unit
{
texture splatting0.png
}

texture_unit
{
texture splatting1.png
}

texture_unit
{
texture splatting2.png
}

texture_unit
{
texture splatting3.png
}
}

pass
{
scene_blend modulate

vertex_program_ref ET/Programs/VSLodMorph2
{
}

fragment_program_ref ET/Programs/PSLighting
{
}

texture_unit
{
texture ETLightmap
}
}

}

technique
{
pass
{
lighting off

vertex_program_ref ET/Programs/VSLodMorph
{
param_named splatScaleX float4 20 0 0 0
param_named splatScaleZ float4 20 0 0 0
}

fragment_program_ref ET/Programs/FallbackSplat_3tex
{
}

texture_unit
{
texture ETSplatting0
}

texture_unit
{
texture default.png
}

texture_unit
{
texture big_kiesel_white.01.png
}

texture_unit
{
texture GreenSkin.png
}
}

pass
{
lighting off

vertex_program_ref ET/Programs/VSLodMorph
{
param_named splatScaleX float4 20 0 0 0
param_named splatScaleZ float4 20 0 0 0
}

fragment_program_ref ET/Programs/FallbackSplat_3tex
{
}

texture_unit
{
texture ETSplatting1
}

texture_unit
{
texture lava.png
}

texture_unit
{
texture rockwall.tga
}

texture_unit
{
texture scrin.png
}
}

pass
{
lighting off

vertex_program_ref ET/Programs/VSLodMorph
{
param_named splatScaleX float4 20 0 0 0
param_named splatScaleZ float4 20 0 0 0
}

fragment_program_ref ET/Programs/FallbackSplat_3tex
{
}

texture_unit
{
texture ETSplatting2
}

texture_unit
{
texture splatting0.png
}

texture_unit
{
texture splatting1.png
}

texture_unit
{
texture splatting2.png
}
}

pass
{
lighting off

vertex_program_ref ET/Programs/VSLodMorph
{
param_named splatScaleX float4 20 0 0 0
param_named splatScaleZ float4 20 0 0 0
}

fragment_program_ref ET/Programs/FallbackSplat_1tex
{
}

texture_unit
{
texture ETSplatting3
}

texture_unit
{
texture splatting3.png
}
}

pass
{
scene_blend modulate

vertex_program_ref ET/Programs/VSLodMorph
{
param_named splatScaleX float4 20 0 0 0
param_named splatScaleZ float4 20 0 0 0
}

fragment_program_ref ET/Programs/PSLighting
{
}

texture_unit
{
texture ETLightmap
}
}

}

}

and the shader for 10 textures:
void main
(
float2 iTexCoord0 : TEXCOORD0,

out float4 oColor : COLOR,

uniform sampler2D covMap1,
uniform sampler2D covMap2,
uniform sampler2D covMap3,
uniform sampler2D covMap4,
uniform sampler2D splat1,
uniform sampler2D splat2,
uniform sampler2D splat3,
uniform sampler2D splat4,
uniform sampler2D splat5,
uniform sampler2D splat6,
uniform sampler2D splat7,
uniform sampler2D splat8,
uniform sampler2D splat9,
uniform sampler2D splat10,

uniform float splatScaleX,
uniform float splatScaleZ
)
{
float3 cov1 = tex2D(covMap1, iTexCoord0).rgb;
float3 cov2 = tex2D(covMap2, iTexCoord0).rgb;
float3 cov3 = tex2D(covMap3, iTexCoord0).rgb;
float3 cov4 = tex2D(covMap4, iTexCoord0).rgb;

iTexCoord0.x *= splatScaleX;
iTexCoord0.y *= splatScaleZ;

oColor = tex2D(splat1, iTexCoord0) * cov1.x
+ tex2D(splat2, iTexCoord0) * cov1.y
+ tex2D(splat3, iTexCoord0) * cov1.z
+ tex2D(splat4, iTexCoord0) * cov2.x
+ tex2D(splat5, iTexCoord0) * cov2.y
+ tex2D(splat6, iTexCoord0) * cov2.z
+ tex2D(splat7, iTexCoord0) * cov3.x
+ tex2D(splat8, iTexCoord0) * cov3.y
+ tex2D(splat9, iTexCoord0) * cov3.z
+ tex2D(splat10, iTexCoord0) * cov4.x;

}

My graphics card is a NVIDIA GeForce 8800 GTS.
It looks extremely strange in OpenGL mode, too:


edit: if I save it right after setting the textures, the first covmap is red, and all the others are black, so I guess they should be OK

CABAListic

25-07-2008 22:33:46

My bet is that your splat10 shader has a bug, check the Ogre log. Because if it's erroneous it would fall back to your 4 pass technique, and that one has a definite error: You are missing a "scene_blend add" for all passes except the first one. That results in only the last one generating output, which matches the results you describe.

pra

25-07-2008 22:51:15

you are absolutely right:

3:45:12: OGRE EXCEPTION(7:InternalErrorException): Unable to compile Cg program ET/Programs/Splat_10tex: CG ERROR : The compile returned an error.
(0) : error C6001: Temporary register limit of 12 exceeded; 14 registers needed to compile program
in CgProgram::loadFromSource at e:\projects\ogrecvs\branches\eihort_clean_vc8\ogre\plugins\cgprogrammanager\src\ogrecgprogrammanagerdll.cpp (line 66)

hmm, what does this mean?
can I somehow make it compile, or should I set max textures per pass to 9?

edit: now there is another issue: the shadowed parts are somehow translucent, i can see the rest of the terrain through them. It seems I broke everything now :(
edit2: ...
i'm such a noob. i set scene_blend for all passes, even the first one.

CABAListic

26-07-2008 08:31:00

Try breaking off the the colour calculation into several instructions:

oColor = tex2D(splat1, iTexCoord0) * cov1.x
+ tex2D(splat2, iTexCoord0) * cov1.y
+ tex2D(splat3, iTexCoord0) * cov1.z
+ tex2D(splat4, iTexCoord0) * cov2.x
+ tex2D(splat5, iTexCoord0) * cov2.y
+ tex2D(splat6, iTexCoord0) * cov2.z;
oColor += tex2D(splat7, iTexCoord0) * cov3.x
+ tex2D(splat8, iTexCoord0) * cov3.y
+ tex2D(splat9, iTexCoord0) * cov3.z
+ tex2D(splat10, iTexCoord0) * cov4.x;

That might help. Otherwise I don't really know, I'm not exactly an expert on shaders, either. Might be that you do have to use less textures. What shader version are you compiling for?

pra

26-07-2008 16:34:23

Doesn't work. same error :(
profiles ps_2_0 arbfp1 <- shader version 2.0?

CABAListic

26-07-2008 16:40:52

Well then it's apparently too many instructions for that shader model. You might try ps_2_x, might give you a bit more, otherwise you'll have to use less textures.