BatchNode.batch() totally messing up the Material Transparency Quality

Hello Monkeys !

Right now I’m working on a really simple grass system with chunk. Im on the early stage of it and i’m facing a problem. I created a custom shader for my grass which is just random thing found on the internet until I finally reach something that was decent.

Here is the Shader Code (Warning : your eyes might turn red)

Fragment

vec3 minGrass = vec3(0, 0, 0);

void main(){
    outColor = texture2D(texture,texCoord);
    if(outColor.a < 0.2){
        discard;
    }
    //outColor.a = 0.5-(0.01*camDist);
    vec3 finalDiffuse = diffuseLight;
    finalDiffuse += vec3(1,1,1);
    finalDiffuse = vec3(mix(minGrass, finalDiffuse.rgb, texCoord.y));
    outColor *= vec4(finalDiffuse,1.0);
}

Vertex

uniform vec4 g_LightPosition;
uniform vec4 g_LightColor;
uniform vec4 g_LightDirection;

vec3 toDirection(vec4 direction){
    return vec3(direction.x, direction.y, direction.z);
}

void main(){ 
    projPosition = worldViewProjectionMatrix * vec4(modelPosition,1.0);
    camDist = projPosition.z;

   vec4 worldPos = worldMatrix*vec4(modelPosition,1.0);

   vec3 l = -toDirection(g_LightPosition);
   vec3 n = normalMatrix*modelNormal; 

    float cosTheta = clamp( dot( n,l ), 0,1 );
    diffuseLight = toDirection(g_LightColor) * cosTheta;
}

And here is the problem i’m facing after batching all this grass Quad

Without BatchNode.batch()

With BatchNode.batch()

Here is the Code used to create GrassQuad material

public void createGrassMaterial(AssetManager assetManager){
    grassMat = new Material(assetManager, "MatDefs/testmat.j3md");
    grassMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
    grassMat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
    grassMat.getAdditionalRenderState().setDepthWrite(true);
    grassMat.getAdditionalRenderState().setDepthTest(true);
    grassMat.getAdditionalRenderState().setAlphaFallOff(0.1f);
    grassMat.setTransparent(true);
    grassMat.setTextureParam("ColorMap", VarType.Texture2D, assetManager.loadTexture("Textures/grass.png"));
}

Why do the grass transparency is messed up after batching them ?

Like always ask me if you want further details or anything that would help.

Thanks, Stomrage ! :wink:

Search about transparency sorting, depth buffer alpha sorting, or things like that… you’ll find a million hits because this basic issue is brought up every three days or so.

Shortly: alphaFallOff does nothing without alphaTest = true… and it will break in 3.1 anyway because it’s not supported.

Change to using the alphaDiscardThreshold material parameter and expect the semi-transparent edges to have the same issue unless you per-frame depth sort your grass.

3 Likes

To explain further, before batching you have several geometries that are properly sorted and rendered back to front.
After batching you have one geometry, with no control on the render order.

3 Likes

Well, that’s not entirely true… it’s just that the render order is fixed in the mesh.

Edit: if you batch your own meshes then you can control that order and potentially re-sort as needed (I do it in my grass for IsoSurfaceDemo).

really you udate the buffers according to the camera view?

No. I update based on camera position… but only for the zone you are in. The other surrounding zones are sorted only when the zone changes (they are sorted facing the whole center zone, basically)

It’s the only way the grass will look right from all angles.

mhh ok, makes sense.
Though that prohibits the usage of built in batching facilities. You have to make it on your own.

Yes, but for grass that’s best anyway.

Batching a bunch of individual quad Geometries is a total waste of time/space/resources.

Ok I “think” I get it now. Alpha sorting is really a tricky things, I just started shader and opengl low level things a week ago so I don’t really know the vocabulary associated with it and couldn’t find the correct way to spell it to get the Google researches effective. So thanks for the reply guys !


I figured out how does it work by simply turning around my scene and one of the side (180° field of view) of it is well sorted. It might be because my generation algorithm do it in this way. So if I get it, I can’t do a “static” planting algorithm, I have to generate and sort it on the fly if I want a good alpha sorting.


But… What I don’t get is why does the grass look right on one side ? Should I just sort the GrassPatch (batched chunk of grass) or do I need to sort both GrassBlade (-|-) and GrassPatch ?

Or should I start over and create a brand new dynamic custom mesh with the generation order defined by the camera distance ?


@pspeed

This is my first contact with a grass generation system and I’m just testing different solution to the problem since I don’t really care about “time/space/ressource” but after looking at https://code.google.com/p/simsilica-tools/source/browse/trunk/IsoSurface/src/main/java/com/simsilica/iso/plot/GrassZone.java I don’t think I can make something that complex without going into severals steps. Anyway good job !


And thank you both for this tasty debate :smiley:

Well thanks to you guys I got something working ! I still got some weird transparency issue but nothing compare to what it was.

I now generate all my grass patch on the fly and update the Geometry order depending on the camera position.

Basically, I got something like this

The comparator that will check for the closest grass.

private float compareRealDistance(Vector3f camLocation, Vector3f location) {
    Vector2f cam2D = new Vector2f(camLocation.x, camLocation.z);
    Vector2f loc2D = new Vector2f(location.x, location.z);
    return cam2D.distance(loc2D);
}

    grassComp = new Comparator<Geometry>() {
        public int compare(Geometry o1, Geometry o2) {
            float dist1 = compareRealDistance(camPos, o1.getLocalTranslation());
            float dist2 = compareRealDistance(camPos, o2.getLocalTranslation());
            if (dist1 > dist2) {
                return -1;
            }
            if (dist1 == dist2) {
                return 0;
            }
            return 1;
        }
    };  

And the chunk generation algorithm

public void generate(final Vector3f camPos) {
    this.camPos = camPos;
    if (grassGeom != null) {
        this.detachChild(grassGeom);
    }

    Collections.sort(grassList, grassComp);
    Mesh m = new Mesh();
    GeometryBatchFactory.mergeGeometries(grassList, m);
    TangentBinormalGenerator.generate(m, true, true);
    grassGeom.setMesh(m);
    this.attachChild(grassGeom);
}

Now I’ll totally rewrite it and take a further look at IsoSurface/GrassZone :blush:

Note: you might as well sort on distanceSquared() and avoid the square roots all over the place.

Also, what is your billboarding technique? Oriented to the camera view or oriented to the camera location? (most seem to use the former but I always use the latter because I don’t like my billboards turning past each other as I turn my head.)

I am a little curious, the grass is it made of geometry? And then batched together?
I did read what you guys wrote but from my perspective the grass always add plenty of lag in my game… I even have a memory leak when i use GeometryBatchFactory.mergeGeometries() and it turn out i use over 4GO memory…
(I use the GeometryBatchFactory in concurency drawing everything from grass to trees)
If I generate 128x128 tile of grass and i merge all, I end up with intensive lag :frowning: and huge memory waste.
If I dont use the merge I have a huge frame drop.

nice work.
The grass looks pretty btw

@pspeed

Yeah ! I now use distanceSquared avoiding a square roots ! Hurrah for performance ! thanks :sunny:

The billboarding technique… Mmmm let’s say it’s a pretty custom to do it but it does what it need to do :smiley:
Take a look at this !

    for (Geometry g : grassList) {
        Vector3f tempPos = camPos.clone();
        tempPos.y = g.getLocalTranslation().y;
        if (g.getName().equals("Plane1")) {
            g.lookAt(tempPos, Vector3f.UNIT_Y);            
        }
        if (g.getName().equals("Plane2")) {
            g.lookAt(tempPos, Vector3f.UNIT_Y);
            Quaternion q = new Quaternion();
            q.fromAngleAxis(-FastMath.PI / 4, Vector3f.UNIT_Y);
            g.rotate(q);                
        }
        if (g.getName().equals("Plane3")) {
            g.lookAt(tempPos, Vector3f.UNIT_Y);
            Quaternion q = new Quaternion();
            q.fromAngleAxis(FastMath.PI / 4, Vector3f.UNIT_Y);
            g.rotate(q);                    
        }
    }

This is… Terrible I know but, I’ll try to find how to do it in vertex shader later on

@n3cr0

In the picture below you can see how I proceed to render the grass. I create a really big grid with all the information and geometry I need to render the grass (planting etc…) and then when the application is running I’m using this grass geometry and sorting them by distance to the camera to have the correct alpha sorting and draw them. If the patch is too far the process is totally ignored else it’ll look for smaller patch with correct drawing distance.

In it’s current state this is not really usable I got 30 fps but still no memory leak or anything else just some weird stuff to fix and many things to optimize. Or even the whole project to redo.

@nehon

Thanks :smile:
For me it look decent but still not what I want to do :frowning:

For batched grass, waaaaaaaaay better to do billboarding in the shader. Depending on your technique, you can either sort only when you move or sort when you turn. But billboarding should be done in shader.

For keeping track of the grass, I recommend just keeping positions around and recreated the batch as needed. A Geometry + Mesh + Quad for each grass object is HUGELY wasteful if you are just going to batch them all up.

1 Like

Do you think it would be possible to make an already implemented grass generator in the Engine? Exemple: You provide a texture and an array of height to draw the grass at the correct height?(and the camera object)

Which of the 500 different techniques should the engine use?

It’s a very complicated thing with as many trade offs as there are ways to do it. Not something that goes well in an engine.

JJajajajajajja !!!

It’s working, after hours of FloatBuffer, IntBuffer I got something and the perfomance are really good ! Now the final step ! The billboarding… I have no idea of what to do :smile: and since I only have the position to generate the mesh I can’t do it without a vertex shader since I have now no control on the grass blade rotation (Or maybe with some weird stuff).

I tried to take a look on the internet for it but… I think I need some more knownledge before I can do something. Should I open a new subject on this matter ? Or should I just simply use your grass material @pspeed ? :smiley:

You could use mine… or just look at it. You have to have the mesh buffers setup in a certain way to use it… and I don’t remember if mine is hard-coded to work with triangles versus quads or not. I might have commented it… I don’t have time to look at the moment.

Basic gist: include attributes with the vertex enough to billboard them in the .vert shader. Essentially: position and corner information.

1 Like

Hey ! I’m back. I finally manage to understand the magic behind camera position billboarding. It is really not optimized but it does what it need to do :smiley:.

texCoord2.y is the size information, the sign of it allow me to displace both y aligned vertices at once

(-size)|-------|(+size)

Here is the code :

bool toDo = true;
vec4 modelSpacePos = vec4(modelPosition,1.0);
vec3 cameraOffset = (cameraPosition - modelPosition);
if(cameraPosition.z - modelPosition.z > 0){
    if((cameraPosition.x - modelPosition.x < texCoord2.y && cameraPosition.x - modelPosition.x > -texCoord2.y)){
        toDo = false;        
    }
}else{
    if((cameraPosition.x - modelPosition.x > texCoord2.y && cameraPosition.x - modelPosition.x < -texCoord2.y)){
        toDo = false;        
    }
}

if(texCoord2.y > 0 && cameraOffset.x > 0 && toDo){        
    vec3 toAdd = vec3(0,0,0);
    cameraOffset = normalize(cameraOffset);
    float angle = atan(cameraOffset.z, cameraOffset.x);        
    toAdd.z += -cos(angle) * abs(texCoord2.y);
    toAdd.x += sin(angle) * abs(texCoord2.y);
    toAdd.x += abs(texCoord2.y);
    modelSpacePos.xyz += toAdd/2;
}
if(texCoord2.y > 0 && cameraOffset.x < 0 && toDo){
    vec3 toAdd = vec3(0,0,0);
    cameraOffset = normalize(cameraOffset);
    float angle = atan(cameraOffset.z, cameraOffset.x);
    toAdd.z += -cos(angle) * abs(texCoord2.y);
    toAdd.x += sin(angle) * abs(texCoord2.y);
    toAdd.x += -abs(texCoord2.y);
    modelSpacePos.xyz += -toAdd/2;
}
if(texCoord2.y < 0 && cameraOffset.x > 0 && toDo){
    vec3 toAdd = vec3(0,0,0);
    cameraOffset = normalize(cameraOffset);
    float angle = atan(cameraOffset.z, cameraOffset.x);        
    toAdd.z += -cos(angle) * abs(texCoord2.y);
    toAdd.x += sin(angle) * abs(texCoord2.y);
    toAdd.x += abs(texCoord2.y);
    modelSpacePos.xyz += -toAdd/2;
}
if(texCoord2.y < 0 && cameraOffset.x < 0 && toDo){
    vec3 toAdd = vec3(0,0,0);
    cameraOffset = normalize(cameraOffset);
    float angle = atan(cameraOffset.z, cameraOffset.x);
    toAdd.z += -cos(angle) * abs(texCoord2.y);
    toAdd.x += sin(angle) * abs(texCoord2.y);
    toAdd.x += -abs(texCoord2.y);
    modelSpacePos.xyz += toAdd/2;
}
projPosition = projectionMatrix * viewMatrix * modelSpacePos;

This is really not the best but it’s fine for me.

Now it’s time to work on graphics :rainbow: :sunflower::rainbow: