Texture/Glyph Bombing Shader

I was reading the orange book, and they had a shader which I couldn’t find implemented in jME anywhere, its called Texture or Glyph Bombing. You can read more about it here:
http://http.developer.nvidia.com/GPUGems/gpugems_ch20.html

In short you create a texture atlas of images, and then just bombard a model with them in a random arrangement. But really your model is divided into cells, and then a random image is populated there. You can add scale/rotation if you wish, and even animations. Apparently its useful for repeated stuff like wallpaper and dirt. The downside is that it requires quite a lot of texture lookups. Anyways heres a video:

[video]http://www.youtube.com/watch?v=NYVA5k610Mc[/video]

Images:

Imgur

And heres the code:
Testcase:
[java]package com.mmm.util.test;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Texture;

public class TestTextureBombing extends SimpleApplication {

public static void main(String[] args) {
    new TestTextureBombing().start();
}

@Override
public void simpleInitApp() {
    Box b = new Box(1, 1, 1);
    Spatial spatial = new Geometry("box", b);
    Material textureBombMat = new Material(assetManager, "MatDefs/TextureBomb.j3md");
    textureBombMat.setTexture("TextureAtlas", assetManager.loadTexture("Textures/Glyphs.png"));
    textureBombMat.setTexture("NoiseTex", assetManager.loadTexture("Textures/Noise.png"));

    // unshadedMat.setColor("Color", new ColorRGBA(0, 1.0f, 1.0f, 1.0f));
    // unshadedMat.setTexture("ColorMap", assetManager.loadTexture("Textures/ColorMap.png"));

    textureBombMat.setFloat("ScaleFactor", 10f);       // Scales the tex coords by 10, increasing the cell count
    textureBombMat.setFloat("NumImages", 10f);         // A 10 x 10 texture atlas (100 images)
    textureBombMat.setFloat("Percentage", 0.9f);      // 90% of cells will be filled
    textureBombMat.setBoolean("RandomScale", true);   // Should the images be randomly scaled?
    textureBombMat.setBoolean("RandomRotate", true);  // Should they be randomly rotated?
    textureBombMat.setFloat("SamplesPerCell", 1.0f);   // Should be >= 1.0f (How many images per cell)
    textureBombMat.setBoolean("Animated", false);       // Should the images be animated?

    // Change filters otherwise there are artifacts
    textureBombMat.getTextureParam("TextureAtlas").getTextureValue().setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
    textureBombMat.getTextureParam("NoiseTex").getTextureValue().setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
    
    spatial.setMaterial(textureBombMat);
    rootNode.attachChild(spatial);
}

}[/java]

TextureBomb.vert:
[java]
attribute vec3 inPosition;
attribute vec2 inTexCoord;
uniform mat4 g_WorldViewProjectionMatrix;
varying vec2 texCoord;

void main() {
gl_Position = g_WorldViewProjectionMatrix * vec4 (inPosition, 1.0);
texCoord = inTexCoord.xy;
}[/java]

TextureBomb.frag
[java]
#define TWO_PI 6.28318

#ifdef HAS_COLOR
uniform vec4 m_Color;
#endif

#ifdef HAS_COLORMAP
uniform sampler2D m_ColorMap;
#endif

uniform sampler2D m_TextureAtlas;
uniform sampler2D m_NoiseTex;

uniform float g_Time;

uniform float m_ScaleFactor;
uniform float m_Percentage;
uniform float m_SamplesPerCell;
uniform float m_RO1;
uniform float m_NumImages;

uniform bool m_RandomScale;
uniform bool m_RandomRotate;

uniform bool m_Animated;

varying vec2 texCoord;

void main()
{
vec4 color = vec4(1.0);

#ifdef HAS_COLORMAP
    color *= texture2D (m_ColorMap, texCoord);
#endif

#ifdef HAS_COLOR
    color *= m_Color;
#endif

texCoord *= m_ScaleFactor;
vec2 cell = floor(texCoord); 
vec2 offset = texCoord - cell; 

for (int i = -1; i <= int (m_RandomRotate); i++) { 
    for (int j = -1; j <= int (m_RandomRotate); j++) { 
        vec2 currentCell   = cell + vec2(float(i), float(j)); 
        vec2 currentOffset = offset - vec2(float(i), float(j)); 
     
        vec2 randomUV = currentCell * vec2(m_RO1); 
     
        for (int k = 0; k < int (m_SamplesPerCell); k++) { 
            vec4 random = texture2D(m_NoiseTex, randomUV * (m_Animated ? g_Time * 0.05 : 1.0)); 
            randomUV   += random.ba; 
         
           if (random.r < m_Percentage) { 
                vec2 glyphIndex; 
                mat2 rotator; 
                vec2 index; 
                float rotationAngle, cosRot, sinRot; 
             
                index.s = floor(random.b * m_NumImages); 
                index.t = floor(random.g * m_NumImages); 
             
                if (m_RandomRotate) { 
                    rotationAngle = TWO_PI * random.g; 
                    cosRot = cos(rotationAngle); 
                    sinRot = sin(rotationAngle); 
                    rotator[0] = vec2(cosRot, sinRot); 
                    rotator[1] = vec2(-sinRot, cosRot); 
                    glyphIndex = -rotator * (currentOffset - random.rg); 
                } 
                else { 
                    glyphIndex = currentOffset - random.rg; 
                } 

                if (m_RandomScale) {
                    glyphIndex /= vec2(0.5 * random.r + 0.5); 
                }

                glyphIndex = (clamp(glyphIndex, 0.0, 1.0) + index) * (1 / m_NumImages); 

                vec4 image = texture2D(m_TextureAtlas, glyphIndex); 

                if (image.r != 1.0) {
                    color.rgb = mix(random.rgb * 0.7, color.rgb, image.r);
                }
            } 
        } 
    } 
} 

gl_FragColor = color; 

}
[/java]

TextureBomb.j3md
[java]
MaterialDef TextureBomb {

MaterialParameters {

    Color Color
    Texture2D ColorMap
    Texture2D TextureAtlas
    Texture2D NoiseTex
    Float ScaleFactor
    Float Percentage : 0.5
    Float SamplesPerCell : 1.0
    Float RO1 : 0.01
    Float NumImages

    Boolean Animated : False
    Boolean RandomScale : False
    Boolean RandomRotate : False
}

Technique {

    VertexShader GLSL100:   Shaders/TextureBomb.vert
    FragmentShader GLSL100: Shaders/TextureBomb.frag

    WorldParameters {
        WorldViewProjectionMatrix
        Time
    }

    Defines {
        HAS_COLORMAP : ColorMap
        HAS_COLOR : Color
    }
}

}

[/java]

7 Likes

Super cool!

Will you commit it to ShaderBlow or i can do it?

1 Like

I don’t have access to do it, so go right ahead :slight_smile:

Edit: just remembered its a plugin now, and I am a commiter in the plugin repo

Wow great, bet that can be hell of a good addition to the normal repeated textures on terrain.

1 Like
@wezrule said: I don't have access to do it, so go right ahead :)

Edit: just remembered its a plugin now, and I am a commiter in the plugin repo

Yep, you can commit it. As ShaderBlow is now only on JME-Contributions.
Anyway, if you want me to make it, so i can make it. But it will be cool if you try it once and then you can always commit your cool shaders there.

Here is the project: Google Code Archive - Long-term storage for Google Code Project Hosting.

Here is the wiki(you can add video and description here): http://jmonkeyengine.org/wiki/doku.php/sdk:plugin:shaderblow

pretty cool!!

1 Like

@EmpirePhoenix maybe, idk too much about terrain. I also had to use no mipmaps on the minification filter, otherwise there were some artifacts

@mifth ok, I will clean the code some more, and do some better examples

@nehon thanks, but all I did was port the example and add a few things

really cool, like!

1 Like

@atomix thanks

Ok It is now added to the Shadow blow plugin, and updated the wiki

2 Likes

I’m having an issue where the example application will fail if ScaleFactor is greater than 4, do you know why it may be acting up for me?

@splinterman said: I'm having an issue where the example application will fail if ScaleFactor is greater than 4, do you know why it may be acting up for me?
can you put the log here? Also, what's your videocard?
<cite>@splinterman said:</cite> I'm having an issue where the example application will fail if ScaleFactor is greater than 4, do you know why it may be acting up for me?

I have only tested this on my NVidia graphics card, is your’s ATI by any chance?

@mifth does the example work ok for you?

@wezrule said: I have only tested this on my NVidia graphics card, is your's ATI by any chance?

@mifth does the example work ok for you?

Did not test yet. I have nVidia too.

At first glance i guess here could be an issue with casting:
[java]
glyphIndex = (clamp(glyphIndex, 0.0, 1.0) + index) * vec2(1.0 / m_NumImages);
//OR
glyphIndex = (clamp(glyphIndex, 0.0, 1.0) + index) * vec2( float(1 / int( m_NumImages)) );

                if (image.r != 1.0) {
                    color.rgb = mix(random.rgb * vec3(0.7), color.rgb, image.r);
                }

[/java]

1 Like

@wezrule I tested it on my machine and it works ok for me as i have nVidia too.

But i found that mipmaps do not work for the shader even if i set them:
[java]
Texture texGlyphs = assetManager.loadTexture(“TestTextures/TextureBombing/Glyphs.png”);
texGlyphs.setMinFilter(Texture.MinFilter.Trilinear);
texGlyphs.setMagFilter(Texture.MagFilter.Bilinear);

    Texture texNoise = assetManager.loadTexture("TestTextures/TextureBombing/Noise.png");
    texNoise.setMinFilter(Texture.MinFilter.Trilinear);
    texNoise.setMagFilter(Texture.MagFilter.Bilinear);

    textureBombMat.setTexture("TextureAtlas", texGlyphs);
    textureBombMat.setTexture("NoiseTex", texNoise);

[/java]

Texture patterns always look like without mipmaps. Or i do something wrong?

There was no log besides some cryptic appcrash details in the failure alert dialog.

running texCoord = texCoord/(1.0*m_ScaleFactor); on line 42 seemed to fix the issue. I still see a lot of artifacts though such as small black blocks, tilted glyphs, and some glyphs flickering in and out even though animations are set to false.

I am using an AMD Radeon HD 7670

<cite>@mifth said:</cite> @wezrule I tested it on my machine and it works ok for me as i have nVidia too.

But i found that mipmaps do not work for the shader even if i set them:
[java]
Texture texGlyphs = assetManager.loadTexture(“TestTextures/TextureBombing/Glyphs.png”);
texGlyphs.setMinFilter(Texture.MinFilter.Trilinear);
texGlyphs.setMagFilter(Texture.MagFilter.Bilinear);

    Texture texNoise = assetManager.loadTexture("TestTextures/TextureBombing/Noise.png");
    texNoise.setMinFilter(Texture.MinFilter.Trilinear);
    texNoise.setMagFilter(Texture.MagFilter.Bilinear);

    textureBombMat.setTexture("TextureAtlas", texGlyphs);
    textureBombMat.setTexture("NoiseTex", texNoise);

[/java]

Texture patterns always look like without mipmaps. Or i do something wrong?

I can use Trilinear filter, but then there are artifacts, so I had to use no mipmap filtering.

<cite>@splinterman said:</cite> There was no log besides some cryptic appcrash details in the failure alert dialog.

running texCoord = texCoord/(1.0*m_ScaleFactor); on line 42 seemed to fix the issue. I still see a lot of artifacts though such as small black blocks, tilted glyphs, and some glyphs flickering in and out even though animations are set to false.

I am using an AMD Radeon HD 7670

That is dividing by the scale factor though, rather than multiplying. Hmm wierd, did you set the minification filter to no mipmaps?

Oh I believe the division was due to me using the ShaderBlow version. Retrying the code you pasted and using multiplication still has pretty much the same result with artifacts. Yes I was using no-mipmaps.

ok hmm, can you post a screenshot? thanks

Note the Checkerboard on the top-right blue glyph

Hmm, i’m not sure sorry, some other ATI bug in my code somewhere I guess. (I have commited @mifth 's spot of the implicit float cast though)