javaapi3dlibgdxshadows

libGDX 3D shadow artifacts


I am having some troubles with libgdx 3d shadows. In my game I implemented the experimental DirectionalShadowLight. And everything works great on desktop however when I run it on android there are a lot of artifacts on the ground.

Picture (left-android, right-desktop):

I took the rendering code almost directly from tests in libgdx's github repositories.

    Gdx.gl.glClearColor(ExtendedEnvironment.FarBackgroundColor.r,ExtendedEnvironment.FarBackgroundColor.g,ExtendedEnvironment.FarBackgroundColor.b,1);
    Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

    terrain.prepareForShadows();

    environment.shadowLight.begin(new Vector3(cam.position.x+10,0,0), cam.direction);
    shadowBatch.begin(environment.shadowLight.getCamera());

    ball.draw(shadowBatch, null);
    terrain.draw(shadowBatch, null);

    shadowBatch.end();
    environment.shadowLight.end();

    terrain.recoverFromShadows(ball.getPosition().x);

Theres not much to it. Also considering that it works on desktop I would think that theres something wrong with shadow implementation itself. Is there anything I can do to fix this? Considering that I had never touched a shader in my life. Some simple hack maybe? If not maybe someone could recommend other working shadow implementation for libgdx?

Thank you.

EDIT: additional code:

BlendingAttribute  blendAttribute = new BlendingAttribute(1f)
IntAttribute intAttribute = IntAttribute.createCullFace(GL20.GL_FRONT);


 public void prepareForShadows(){

    batchedCubesInstance.materials.first().remove(blendAttribute.type);
    batchedCubesInstance.materials.first().remove(intAttribute.type);


}

public void recoverFromShadows(float posX){

    batchedCubesInstance.materials.first().set(blendAttribute);
    batchedCubesInstance.materials.first().set(intAttribute);

}

    //creating the batchedMesh:

    ModelBuilder builder = new ModelBuilder();
    builder.begin();
    MeshPartBuilder mpb = builder.part("cubes", GL20.GL_TRIANGLES, (Usage.Position | Usage.Normal | Usage.Color), new Material(
            IntAttribute.createCullFace(GL20.GL_FRONT),//For some reason, libgdx ModelBuilder makes boxes with faces wound in reverse, so cull FRONT
            blendAttribute = new BlendingAttribute(1f), //opaque since multiplied by vertex color
            new DepthTestAttribute(true), //don't want depth mask or rear cubes might not show through
            ColorAttribute.createDiffuse(Color.WHITE) //white since multiplied by vertex color
            ));

    for (int i=0; i < NUMCUBES; i++){

        mpb.box(1, 1, 1); 

    }
    batchedCubes = builder.end();
    batchedCubesInstance = new ModelInstance(batchedCubes);

enter image description here


Solution

  • The problem you're experiencing is called shadow acne (in contrast to peter panning), which is caused by (floating point) precision errors. These are more noticeable on mobile than on desktop because the precision on desktop is most commonly better. There are several ways to avoid shadow acne. One of which is to make sure that the near and far plane of the camera are as close to each other as possible. So don't use a very small cam.near and very high cam.far value. The DepthShader (which is used to create the depth buffer) tries to avoid shadow acne, by only having back faces cast shadows. For this it uses front face culling.

    However, you're also front face culling for the normal rendering (you're only rendering the back side of the models). This causes the visible faces and faces used for generating the depth buffer to be the same. Hence the shadow acne.

    You could solve this by using back face culling while generating the shadow map. However, this will over-complicate your code and might cause other (future) problems as well. Instead you should try to keep the front faces the visible faces, thus removing IntAttribute.createCullFace(GL20.GL_FRONT). Note that by default back faces are culled, you don't have to specify that.

    Removing the cull face attribute might cause other problems in your normal rendering (otherwise you wouldn't have it in there in the first place). This is most likely because the vertex winding of your models is incorrect. You say that you create these using:

    for (int i=0; i < NUMCUBES; i++){
        mpb.box(1, 1, 1); 
    }
    

    It is highly unlikely that this is actually the code you're using to create the model in the image (this just creates an undefined number of boxes with the same size at the same location). Depending on the method that you used to create the scene, you can easily correct the vertex winding by swapping either the horizontal or vertical coordinates, but only one of those.

    If, for some reason, you don't want to correct the vertex winding, then you can call Gdx.gl.glFrontFace​(GL20.GL_CW); to swap the vertex winding used from counterclockwise to clockwise. Note, however, that this might cause other (future) problems, because most commonly the default (counterclockwise) is assumed.