1
\$\begingroup\$

This is using the default shader and depth shader from LibGdx. It creates lines at the blocks intersections.

from afar zoomed

This is what I use to create the blocks models for now:

private Model createModel(Color color) {
    Model model = modelBuilder.createBox(
            cellSize, cellSize, cellSize, 
            new Material(ColorAttribute.createDiffuse(color)), 
            Usage.Position | Usage.Normal);
    return model;
}
...
for(int x = 0; x < data.models.length; x++) {
    for(int y = 0; y < data.models[x].length; y++) {
        int z = data.getElevation(x, y);
        ModelInstance instance = new ModelInstance(model);
        instance.transform.setTranslation(new Vector3(cellSize * x + cellSize/2, cellSize * y + cellSize/2, z * cellSize - cellSize/2));
        getWorld().addToCache(instance);
    }
}

This sets the environment lights:

getEnvironment().set(new ColorAttribute(ColorAttribute.AmbientLight, getAmbiantBrightness(), getAmbiantBrightness(), getAmbiantBrightness() , 1f));
getEnvironment().set(new ColorAttribute(ColorAttribute.Specular, getAmbiantBrightness(), getAmbiantBrightness(), getAmbiantBrightness() , 1f));
getEnvironment().add((shadowLight = new DirectionalShadowLight(1024*4, 1024*4, 600f, 600f, 0.1f, 100f)).set(db, db, db, dx, dy, dz)); 
getEnvironment().shadowMap = shadowLight; 

This is how I render:

@Override
public void render(float delta) {
    getWorld().buildCache();
    clearScreen();
    renderShadows(delta);
    controls.update(delta);
    renderWorld(delta);
    renderHud(delta);
}
public void clearScreen() {
    Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    Gdx.gl.glClearColor(getBGColor().r, getBGColor().g, getBGColor().b, getBGColor().a);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);
}

protected void renderShadows(float delta) {
    shadowLight.begin(Vector3.Zero, getCam().direction);
    shadowBatch.begin(shadowLight.getCamera());
    shadowBatch.render(world.cache, env);
    getWorld().tempModels.forEach(m -> shadowBatch.render(m, env));
    shadowBatch.end();
    shadowLight.end();
}

protected void renderWorld(float delta) {
    getBatch().begin(getCam());
    // render the cached models
    getBatch().render(getWorld().cache, getEnvironment());
    // render the temp models
    getWorld().tempModels.forEach(m -> getBatch().render(m, getEnvironment()));
    getBatch().end();
}

I tried to copy the default fragment and vertex shader and edit the PCFoffset with random values, but the result wasn't better.

float getShadow()
{

    float pcfOffset = 0.0f;
    return (//getShadowness(vec2(0,0)) +
            getShadowness(vec2(pcfOffset, pcfOffset)) +
            getShadowness(vec2(-pcfOffset, pcfOffset)) +
            getShadowness(vec2(pcfOffset, -pcfOffset)) +
            getShadowness(vec2(-pcfOffset, -pcfOffset))) * 0.25;
}

Should I code the lights/shadows myself using Lwjgl instead of using LibGdx defaults? Seems like a lot of work for something I expected to be included.

\$\endgroup\$
  • \$\begingroup\$ I went to look up the docs on DirectionalShadowLight and noticed they're all marked "deprecated - do not use" - so it might be that this implementation has known issues that are unsuited to production use. \$\endgroup\$ – DMGregory Dec 29 '18 at 18:24
1
\$\begingroup\$

Ok, there is a simple fix to shadow acne that I found here https://lwjglgamedev.gitbooks.io/3d-game-development-with-lwjgl/content/chapter18/chapter18.html

float bias = 0.05;
if ( projCoords.z - bias < texture(shadowMap, projCoords.xy).r ) 
{
    // Current fragment is not in shade
    shadowFactor = 0;
}

And I found how to apply it to LibGdx's default fragment shader :

float getShadowness(vec2 offset)
{
    const vec4 bitShifts = vec4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0);

    // EDIT
    float bias = 0.0005f;

    return step(v_shadowMapUv.z - bias, dot(texture2D(u_shadowTexture, v_shadowMapUv.xy + offset), bitShifts));//+(1.0/255.0));
}

This gets rid of the shadow acne very cleanly.

Unfortunately, I still have some problems with white/black lines showing on intersections because, as I just realized, the wrong vertex gets rendered on top of the other. I.e. if I look in the same direction as the directional light, I get white lines and if I look in the opposite direction (towards the light source), I get black lines. Perhaps I can fix this with a noise filter.

enter image description here

Edit :

Final solution : I found out that the seams indeed came from the unculled side faces of the blocks which can ocasionnaly render on top of the "top" faces of the blocks. The problem is hard to explain by text, but the solution is simple. I found out about greedy meshing, which merges all possible quads. Here are the resources I used :

I ended up not using Robert O'Leary's implementation as I had problems with winding/indexing with it. So I implemented Mikola Lysenko's code in java myself which also gave me a better understanding of the algorithm and let me arrive to this result. Extremely clean mesh which produces no seams : GL_LINE_STRIP GL_TRIANGLES

The only problem with Mikola Lysenko's original code is that it doesn't include the actual quad mesh generation and vertex winding. So one has to add backface management by himself to know if a face's normal must be positive or negative, and adjust the winding accordingly.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.