Shader Grid Creation

I’m doing a game like D&D and i want to draw a grid dynamically in the ground when the player enters in an encounter area.
I achieved that successfully with projective texture mapping plugin but since I’m generating dynamically a texture with an unknown number of tiles, if the encounter area is for example 13x13 tiles, it’s too slow to render.
I’m trying the next approach that is a grid shader but I’m having a hard time to find good resources since i never learned shader programming.
I want a kind of parallax mapping grid on a custom area of the terrain.
Is that possible to achieve?
Can someone show me the right path?

Thanks in advance!

If you’re generating the texture procedurally anyway why don’t you just generate the grid as well (on the texture or as a parallax texture)?

Hi Normen!
Thanks for your reply!
At this point my solution is to slow, I’m searching for a way to speed up things, but even if i want to stick with my solution how do i apply parallax on the projective texture mapping plugin?
I’m also searching other solution like terrain splatting but i don’t know if it does what i want.

I mean painting directly on the texture that already is on the ground, i.e. modifying that texture to have a grid.

This is what i have so far:

image free hosting

But takes too long for my taste to generate only those tiles.
Is there a way to generate a lot more tiles and quicker? Like texture splatting? If so, how do i accomplish that?

“My piece of string is not the right size. Can you tell me how to make it the size I want?”

We can’t help optimize code we can’t see. We don’t even really know what you are trying to accomplish with your “procedural texture that is too slow to make”.

Of course there are better ways. They will all come with different trade offs that will largely depend on your requirements that we don’t know.

This is the code I’m using:

/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package medievalsoul;

import com.jme3.asset.TextureKey;
import com.jme3.ext.projectivetexturemapping.SimpleTextureProjector;
import com.jme3.ext.projectivetexturemapping.TextureProjectorRenderer;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.queue.GeometryList;
import com.jme3.renderer.queue.OpaqueComparator;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.WireFrustum;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.medievalsoul.texture.TextureGrid;

/**
*

  • @author Miguel
    */

public class ProjectiveTexturing
{
private ProjectorData pd1;
private TextureProjectorRenderer ptr;
private GameManager gm;
private boolean showWireframe;

public ProjectiveTexturing(GameManager gm, List<Geometry> targets, Vector3f position, String texturePath, float size, boolean showWireframe, int tileAmount)
{
    this.gm = gm;
    this.showWireframe = showWireframe;
    
    //"/Textures/rune.png"

    Texture2D texture1 = new  Texture2D(TextureGrid.renderGrid(texturePath, tileAmount));

    pd1 = new ProjectorData();
    initProjectorData(pd1, new Vector3f(position), texture1);
    
    if(targets != null)
    {
        GeometryList gl = new GeometryList(new OpaqueComparator());

        for(Geometry geo : targets)
            gl.add(geo);
        
        pd1.projector.setTargetGeometryList(gl);
    }
    else
    {
        pd1.projector.setFallOffDistance(2.1f);
        pd1.projector.setFallOffPower(4f);
    }

    pd1.projector.getProjectorCamera().setFrustumPerspective(90f, 1f, size, 10f);
    pd1.projector.getProjectorCamera().setParallelProjection(true);
   
    
    ptr = new TextureProjectorRenderer(gm.getAssetManager());
    ptr.getTextureProjectors().add(pd1.projector);

    Logger.getLogger("").log(
      Level.SEVERE, "NUM_PROJECTORS: {0}, NUM_PASSES: {1}", new Object[]{ptr.getTextureProjectors().size(), ptr.getTextureProjectors().size()});
    
    //remove processor to avoid conflicts ex:glow filter

    gm.getViewPort().getProcessors().removeAll(targets);
    
    gm.getViewPort().addProcessor(ptr);
}

private void initProjectorData(ProjectorData pd, Vector3f location, Texture2D texture)
{
    texture.setMinFilter(Texture.MinFilter.Trilinear);
    texture.setMagFilter(Texture.MagFilter.Bilinear);
    texture.setAnisotropicFilter(16);
    //texture.setWrap(Texture.WrapMode.EdgeClamp);
    texture.setWrap(Texture.WrapMode.BorderClamp);

    int textureWidth = texture.getImage().getWidth();
    int textureHeight = texture.getImage().getHeight();
    float textureAspectRatio = ((float) textureWidth) / ((float) textureHeight);

    pd.projector = new SimpleTextureProjector(texture);
    Camera projectorCamera = pd.projector.getProjectorCamera();
    projectorCamera.setLocation(location);
    projectorCamera.lookAt(Vector3f.ZERO.clone(), Vector3f.UNIT_X.clone());
    projectorCamera.setFrustumPerspective(45, textureAspectRatio, 1f, 5f);
    projectorCamera.setParallelProjection(false);

    pd.frustumPoints = new Vector3f[8];
    for (int i = 0; i < 8; i++)
    {
      pd.frustumPoints[i] = new Vector3f();
    }

    pd.frustum = new WireFrustum(pd.frustumPoints);
    Geometry frustumMdl = new Geometry("f", pd.frustum);
    
    if(this.showWireframe)
        frustumMdl.setCullHint(Spatial.CullHint.Never);
    else
        frustumMdl.setCullHint(Spatial.CullHint.Always);
    
    
    frustumMdl.setShadowMode(RenderQueue.ShadowMode.Off);
    frustumMdl.setMaterial(new Material(gm.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"));
    frustumMdl.getMaterial().setColor("Color", ColorRGBA.White);    
    gm.getRootNode().attachChild(frustumMdl);        
}

public void update(Vector3f position)
{
    pd1.projector.getProjectorCamera().setLocation(new Vector3f(position));
    pd1.projector.getProjectorCamera().lookAtDirection(Vector3f.UNIT_Y.negate(), Vector3f.UNIT_X.clone());
    pd1.projector.updateFrustumPoints(pd1.frustumPoints);
    pd1.frustum.update(pd1.frustumPoints);
}

public void setLocalTranslation(Vector3f position)
{
    pd1.projector.getProjectorCamera().setLocation(new Vector3f(position));
}

private class ProjectorData
{
  SimpleTextureProjector projector;
  WireFrustum frustum;
  Vector3f[] frustumPoints;
}

}

/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package net.medievalsoul.texture;

import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.plugins.AWTLoader;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import jme3tools.converters.ImageToAwt;
import medievalsoul.GameManager;

/**
*

  • @author Miguel
    */
    public class TextureGrid
    {
    private static GameManager gm;

    public TextureGrid(GameManager gm)
    {
    this.gm = gm;
    }

    private static BufferedImage createTransformed(BufferedImage image, AffineTransform at)
    {
    BufferedImage newImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = newImage.createGraphics();
    g.transform(at);
    g.drawImage(image, 0, 0, null);
    g.dispose();
    return newImage;
    }

    public static Image split(BufferedImage img, int tileAmount)
    {
    AffineTransform at = AffineTransform.getRotateInstance(Math.PI, img.getWidth()/2, img.getHeight()/2.0);
    img = createTransformed(img, at);
    BufferedImage pic = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
    Graphics g = pic.getGraphics();

     int width = pic.getWidth() / tileAmount;
     int height = pic.getHeight() / tileAmount;
    
     // Tile the image to fill our area.
     for (int x = 0; x < pic.getWidth(); x += width) {
         for (int y = 0; y < pic.getHeight(); y += height) {
             g.drawImage(img.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH), x, y, null);
         }
     }
     g.dispose(); 
     AWTLoader loader = new AWTLoader();
     Image image = loader.load(pic, true);
     return image;  
    

    }

    public static Image renderGrid(String imagePath, int gridSize)
    {
    //“com/jme3/app/Monkey.png”
    Texture2D texture = (Texture2D) gm.getAssetManager().loadTexture(imagePath);
    BufferedImage bf = ImageToAwt.convert(texture.getImage(), false, true, 0);
    return split(bf, gridSize);
    }
    }

This is the bit of code that is generating the texture to use in the projective texture mapping:

Texture2D texture1 = new Texture2D(TextureGrid.renderGrid(texturePath, tileAmount));

At this point I have 9x9 grid that im generating when i enter in a combat area and is taking almost 10 seconds to load in my asus transformer, I want to be able to generate a 20x20 or a 30x30 faster.
I want to know if there are other options available.
Sorry if im not being clear, English is not my main language.

renderGrid() shouldn’t be taking 10 seconds. Only way to know for sure is to time it.

And what normen was talking about… why not just draw the black lines on the image in your split() method instead of messing with expensive projective texturing?

Just throwing this in here. To me it looks kinda ugly having a grid like that combined with bumpy terrain. I was thinking if it where me I would possibly use a single plane with the grid texture, clone and re-position as many times as necessary then batch, and then use some trickery to make it not be depth checked against the terrain (I would probably use two view ports to do this)

Obviously if there was a massive hill you’d want to split the grid. In my mind that would look nicer than having the distortions caused by bumpy terrain.

@JESTERRRRRR do tou have an example of what are tou talking about?
Are you talking about sending the player to another screen only with the grid and return after the combat to the original map?

@pspeed i think its a good idea and i will give it a shot next, should be a lot more faster.

No. I’m talking about rendering a flat grid that isn’t projected onto the terrain - it’s a separate object. The only requirement would be that it shows up on top of the terrain regardless of its height (so it’s never hidden by the terrain). I only suggest this because I do not like the distortion of the grid as it flows with the terrain but this is just a personal preference and how I would do it.

It would end up looking like:

(Imagine that terrain is slightly bumpy like yours, though I don’t know exactly what you are trying to achieve/for what purpose like Pee said so maybe I am barking up the wrong tree!)

@JESTERRRRRR that solution is perfect!
I have multiple quads i use for debug the grid i have in memory but it’s not transparent and each tile is a different mesh(it’s only for debug purposes).

How i achive a grid like that?
It’s only one quad with a transparent material and a png texture all over it?
Keep in mind that the amount of tiles and the size of the tiles must be configurable.

Uh, well.

I was thinking… and this isnt’t tested in anyway, if I was doing it…

Create a plane of the appropriate shape (in code or in your modeling program - the size of 1 square not the whole thing!), make it transparent and so on
Create a material for the plane that has the semi transparent texture and turn off depth testing
Set the planes render bucket to the transparent one so its drawn last (in this viewport… explained later)
Clone it and make the number you want, position them as you go (fixed offset per tile)
When they’re all positioned, batch them

Create a pre-viewport that is rendered before the main one. This would only have the floor and the grid of planes attached to it (anything you want to appear under the grid would be in here - in the scene in the screenshot it’s just the floor). Rest of your scene is rendered normally in the main viewport.

I imagine this would work? It’s the sort of thing you’d need to play around with. I’d like to note there is probably just easier way to get the same result with shaders but I tend to avoid them since I am a massive shader noob so this is the route I’d go down personally.

If you wanted you could later add functionality to move selected grid squares up or down based on terrain height or other objects in the scene like in the screenshot at the top.

Yes I’m a shaders noob too lol
I will try your solution but i don’t know how to do the batch and viewport part but i will search.
If i acomplish my goal i will post here the solution.
Thanks a lot!

Not sure why it needs to be more complicated than one big quad with a repeating texture.

Can you do that and have a dynamic tile size?

Dynamic in what sense?

You can change the amount and size of the tiles on the fly?
Here is what i achieve so far:

online photo storage

It is rather quick and I’m not doing the batch yet.
It’s a 21x21 grid, I believe i will stick with this solution, I can change the size and tile amount on the fly.
Can someone tell me a good tutorial about batching?
When i finish all the grid code i will share it.
I still have to do some code to show the spell and melee range on the grid tiles.

I don’t know if he means change the number of total tiles, or the individual tiles size, but yeah both would work with a big quad majc.

The reason I suggested not using 1 big quad is so he could easily do bits like the truck at the top of this one:

Maybe I am jumping the gun a bit.

As far as batching goes, I have never used it but I think it’s as simple as:

BatchNode myBatchNode = new BatchNode();

myBatchNode.attachChild(child1);
myBatchNode.attachChild(child2);
myBatchNode.attachChild(and so on);

myBatchNode.batch();

myRootNodeOrWhatever.attachChild(myBatchNode);

They all need to be the same material. So if you end up with multiple grids with different materials they’ll each need their own batch node. This is purely a performance thing but it’s worth doing.

After use that code for batching all the texture transparency disappeared.

free image upload

This is the code I’m using:

public void debug()
{
BatchNode myBatchNode = new BatchNode();

    if(grid.length != 0)
    {
        if(graphicalGrid[0][0] == null )
        {
            for(int y = 0; y < GRID_Y; y++)
            {
                for(int x = 0; x < GRID_X; x++)
                {

                    /*if(x == middleTile && y == middleTile)
                        continue;*/
                    Mesh mesh = Quad.createMesh(side);
                    Geometry geo = new Geometry("TileMesh", mesh); // using our custom mesh object
                    Material mat = new Material(gm.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
                    mat.setTexture("ColorMap", gm.getAssetManager().loadTexture("Textures/Grid2.png"));
                    mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
                    geo.setQueueBucket(Bucket.Transparent);
                    geo.setMaterial(mat);
                    geo.setLocalTranslation(grid[x][y].getMidPoint());
                    geo.rotate(FastMath.DEG_TO_RAD * 180, 0, 0);
                    myBatchNode.attachChild(geo);
                    graphicalGrid[x][y] = geo;
                }
            }
            myBatchNode.batch();
            gm.getRootNode().attachChild(myBatchNode);
        }
        else
        {
            for(int y = 0; y < GRID_Y; y++)
            {
                for(int x = 0; x < GRID_X; x++)
                {
                    /*if(x == middleTile && y == middleTile)
                        continue;*/

                    graphicalGrid[x][y].setLocalTranslation(grid[x][y].getMidPoint());
                }
            }
        }
    }
}