How to edit a texture in runtime

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
LuckyLMJ
Posts: 4
Joined: Fri Nov 25, 2022 10:09 pm

How to edit a texture in runtime

Post by LuckyLMJ »

I'm trying to render a 3-d globe model procedurally using only base maps (a colour map, height map, country map, etc), and I'd really like to be able to make a specific spot of my colour map a different colour depending on what country is there on the country map. However, to do this, I need to either apply a translucent version of my country map on top of my colour map, or I need to read from one texture and then manually edit the colour of the pixels of the other. Either way, I need to be able to edit the texture manually, which I can't figure out how to do.

I heard somewhere about a setPixel() function, but I can't figure out how it works as running

Code: Select all

myTexture->setPixel(x, y, colour);
throws an error that ITexture doesn't have a member SetPixel.


Any help would be greatly appreciated!
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: How to edit a texture in runtime

Post by CuteAlien »

Bunch of ways to approach this.

One is to draw other polygons (each one looking like a country) on top with some half-transparent material. Pretty simple if you have the polygons. You probably should disable ZBuffer in the SMaterial of the country polygons (otherwise you get z-fighting or you have to raise their height and they seem to be flying... which can be nice as well maybe - depends on what you prefer).

Next it depends a bit on how you recognize that specific part of your map belongs to a certain country.
You could for example split your map into one country per meshbuffer. Then you can modify single meshbuffers. For example change their vertex-colors and use some material which regards vertex colors. Or simply replace that specific texture by another one. Which is a relative cheap operation - so basically you have a set of textures - one not highlighted and one highlighted.

Another option is to use a material which has 2 textures. So you have one base-texture and the second one is one per country. Then you only have to replace the second texture (which can have some brighter color for a single country and rest black). Likely the solution I'd use for this.

Then yes, it's possible to directly draw into textures. One way (and usually the best) to do that is using pixel-shaders. Thought it means you have to learn a bit about pixel shaders - check example 10.Shaders. Downside is that you lose some of the ease of default materials (mainly lighting, but also you have to do some of the calculations yourself in shaders, but generally you can copy those). Advantage - super fast and super flexible and basically the thing you want to learn one day when doing 3D graphics. You still have to figure out how pass the information about which parts need which color to the shader code.

Another way to draw into textures is simpler, but super slow. Textures are generally on the GPU (at least for hardware drivers like D3D or OpenGL). So you have to download them to CPU. Then modify pixels (which again is super slow), then upload them again to the GPU. To download them you use ITexture::lock(). Which gives you a data pointer directly to the pixel values. You can then change them by directly writing into memory. Doing that depends on things like resolution, bitdepth, color type (although for textures rgba 32-bit is pretty standard). You may even have to regard pitch (width in memory may not be identical to width of texture * width per pixel, but a bit more so the result is a multiple of 2 or 4 bytes or something like that). After modify the memory ITexture::unlock() will upload the texture again to the GPU. Doing all that is fine if you don't have to do it too often, otherwise avoid it if you can.

And that last step can be made even simpler ... and slower.
IVideoDriver::createImage can create an image from a texture (internally doing the ITexture::lock() part and copying to data to the image). And IImage has the (super-slow) setPixel/getPixel functions. Then you can create a texture again from the image (IVideoDriver::addTexture) and voila - you got a new texture you can use. This sometimes makes sense if you have to do it once at program start or after loading or so.
Better (but still slow) is to create the IImage once with the right size. Then use the copyTo functions in IImage to copy the data from a locked ITexture, modify it, then use copyTo again to copy it to the (still locked) ITexture and then unlock the texture again. The added copying makes it a bit slower than working with ITexture data directly and you have to be careful about pitch (thought if you use textures with sizes of 2^x you are probably fine), but it's not that much extra speed compared to moving then texture from/to the GPU so it might not matter.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
LuckyLMJ
Posts: 4
Joined: Fri Nov 25, 2022 10:09 pm

Re: How to edit a texture in runtime

Post by LuckyLMJ »

I'll try some of those suggestions out, thanks a lot.
One thing which isn't exactly clear, is what data type is the pointer which you get using ITexture::lock()? Visual Studio says it's a "void*" but that doesn't make much sense. It doesn't matter that much as I'll probably use one of the less slow methods but it'll be good to know for the future.

Thanks so much for all of your help.
CuteAlien
Admin
Posts: 9734
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: How to edit a texture in runtime

Post by CuteAlien »

It's whatever data the texture needs. It's a block of pitch*height bytes. The content depends on whatever getColorFormat is. Like ECF_A8R8G8B8 means it's 32-bit (usually the default as unless you specifically try to get other texture formats that's always used - at least for hardware drivers). Not quite sure about the byte order. Constant says argb, but I think it was usually bgra (the constant names are based on certain D3D constants which are about the way textures had once been saved on file in bmp format and then in memory they usually got flipped due to Intels little endian memory architecture). Or I remember it all wrong. May even be platform dependent. First byte is usually left-top (with rendertarget textures it can be left-bottom on OpenGL).

Anyway - you can cast the result to bytes (or irr::u8* in Irrlicht) and figure it out from there by experimenting.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Post Reply