My lights are messed up

Started by phlux, September 01, 2015, 04:45:56 PM

Previous topic - Next topic

phlux

Hello everyone!

Working on my little 3D demo which is basically an old school FPS dungeon crawler lookalike. I got the basic rendering stuff done and I started to add light sources (torches) but I encountered some strange behaviour:





I strongly suspect my code which builds the blocks, that messes with the lighting as I read somewhere on the board that too many Object3Ds mess with the lighting.
I'll give you a quick rundown how I build my level:

Dungeon layout is defined in a txt file

######
#....#
#....#
#....#
######


Where # is a wall block and . is a block with only a floor and a ceiling.
I iterate over the layout and create an Object3D for each block. Because I'm a lazy slob, a non wall block only has the floor and ceiling which I inverted then during creation (so you basically stand inside the cube and can see the walls from inside).

After adding wall and floor tiles to a separate array (walls and tiles) I call Object3D.mergeAll with the arrays as parameter and add the result to the world.
When I add a red light for testing it results in the images shown above.

I'm also unsure on how the compileAndStrip() function works... If I call it on the wall and tile Object3Ds lighting gets removed completely. I added some fog via setFogging() and it gets removed too when I call compileAndStrip().

So, how can I fix my lights? I can add some code if anything needs clarification.

Cheers!

EgonOlsen

#1
That's caused by the vertex normals, which are used to calculate a light's intensity. They will be calculated when calling build() on an object and the basic algorithm is: For each vertex in the mesh, find all adjacent faces, calculate the face normals for these and average them. For a cube, this looks like this:



(with these lines being the vertex normals). If you have two of these cubes close to each other so that they actually form a kind of wall, it looks like this:



As you can see, the normals on the adjacent corners point in different directions. If you calculate the angle between the vector from the light to the vertex and the normal, you'll get this:



The blue angle is very small, so the light is bright on that vertex. But the orange angle is rather large, so it's darker. But they basically describe the same point on a wall, so this looks strange. And that's basically what you are seeing in your case.

Here's some example code:


import org.lwjgl.opengl.Display;

import com.threed.jpct.FrameBuffer;
import com.threed.jpct.IRenderer;
import com.threed.jpct.Object3D;
import com.threed.jpct.World;
import com.threed.jpct.util.ExtendedPrimitives;
import com.threed.jpct.util.Light;

public class DungeonTest {

public static void main(String[] args) throws Exception {

FrameBuffer fb = new FrameBuffer(800, 600, FrameBuffer.SAMPLINGMODE_HARDWARE_ONLY);
fb.disableRenderer(IRenderer.RENDERER_SOFTWARE);
fb.enableRenderer(IRenderer.RENDERER_OPENGL);

World world = new World();

Object3D all = new Object3D(0);

float offset = 10.1f;  // Faulty lighting
// float offset = 10f; // Correct lighting

for (int i = 0; i < 10; i++) {
Object3D cube = ExtendedPrimitives.createCube(10);
cube.translate(((i & 1) == 0) ? -10 : 10, 0, (i / 2) * offset + 10);
cube.translateMesh();
all = Object3D.mergeObjects(all, cube);
}

Light light = new Light(world);
light.setIntensity(2255, 0, 0);
light.setAttenuation(-1);

all.compile();
all.build();
world.addObject(all);

world.setFogging(World.FOGGING_ENABLED);
world.setFoggingMode(World.FOGGING_PER_PIXEL);
world.setFogParameters(60, 0, 255, 0);

while (!Display.isCloseRequested()) {
fb.clear();
world.renderScene(fb);
world.draw(fb);
fb.update();
fb.displayGLOnly();
Thread.sleep(10);
}
}
}



If you run it as it is, it will look wrong. If you change the value of "offset" from 10.1f to 10f, jPCT can merge the vertices and it will look fine.

So in your case, the best solution would be to create walls as walls and not as part of cubes. That will use less memory and can be rendered faster anyway. Because, as you can see, the algorithm for calculating the normals will create different normals for the vertices of a plane (i.e. wall) than for a cube.
Another solution would be to make sure that your edges of the generated cubes actually match exactly, so that they can be merged when doing the normal calculations. For some reason, this doesn't seem to be the case.

About the compileAndStrip: Compiling an object reduces it's light intensity as documented in the compile() method:

Quote
The lighting is always calculated with an implicit lightMul of 1, so it's advised to change Config.lightMul to 1 when mixing compiled with uncompiled objects (this method already does this for you).

So your light is still there, just not as intense. Just increase the light's intensity by 10 and it should look the same. For fogging, compiled objects need this in addition:


world.setFoggingMode(World.FOGGING_PER_PIXEL);



phlux

Thanks for the in-depth explanation. I think I might understand what the problem is. Because of the objects not aligning correctly with each other the vertex normals cause the light to look strange.

After testing your code and understanding the problem I tweaked my code a little bit.
The dimensions I defined for a dungeon block were, width = 1.5 and height = 1. Changed them to w = 6 and h = 4.
I noticed that you translate your primitive to the desired location whereas I create the sides of the cube at the desired position. Does this make a difference?
Changing these values didn't solve the problem.

This is my code for creating my cubes. So as you can see I already used single panes.


    private Object3D createWalkableTile(int x, int y) {
        SimpleVector upperLeftFront = new SimpleVector(x * TILE_WIDTH, 0, -y * TILE_WIDTH);
        SimpleVector upperRightFront = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, 0, -y * TILE_WIDTH);
        SimpleVector lowerLeftFront = new SimpleVector(x * TILE_WIDTH, TILE_HEIGHT, -y * TILE_WIDTH);
        SimpleVector lowerRightFront = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, TILE_HEIGHT, -y * TILE_WIDTH);

        SimpleVector upperLeftBack = new SimpleVector(x * TILE_WIDTH, 0, -(y * TILE_WIDTH) + TILE_WIDTH);
        SimpleVector upperRightBack = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, 0, -(y * TILE_WIDTH) + TILE_WIDTH);
        SimpleVector lowerLeftBack = new SimpleVector(x * TILE_WIDTH, TILE_HEIGHT, -(y * TILE_WIDTH) + TILE_WIDTH);
        SimpleVector lowerRightBack = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, TILE_HEIGHT, -(y * TILE_WIDTH) + TILE_WIDTH);

        Object3D tile = new Object3D(4);
        tile.addTriangle(lowerLeftBack, 0, 0, lowerRightBack, 1, 0, lowerLeftFront, 0, 1, textureManager.getTextureID(DUNGEON_FLOOR));
        tile.addTriangle(lowerRightBack, 1, 0, lowerRightFront, 1, 1, lowerLeftFront, 0, 1, textureManager.getTextureID(DUNGEON_FLOOR));

        tile.addTriangle(upperLeftBack, 1, 0, upperLeftFront, 1, 1, upperRightBack, 0, 0, textureManager.getTextureID(DUNGEON_CEILING));
        tile.addTriangle(upperRightBack, 0, 0, upperLeftFront, 1, 1, upperRightFront, 0, 1, textureManager.getTextureID(DUNGEON_CEILING));
        tile.invert();

//        tile.compile();
//        tile.strip();
        tile.build();


        return tile;
    }

    private Object3D createWall(int x, int y) {
        SimpleVector upperLeftFront = new SimpleVector(x * TILE_WIDTH, 0, -y * TILE_WIDTH);
        SimpleVector upperRightFront = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, 0, -y * TILE_WIDTH);
        SimpleVector lowerLeftFront = new SimpleVector(x * TILE_WIDTH, TILE_HEIGHT, -y * TILE_WIDTH);
        SimpleVector lowerRightFront = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, TILE_HEIGHT, -y * TILE_WIDTH);

        SimpleVector upperLeftBack = new SimpleVector(x * TILE_WIDTH, 0, -(y * TILE_WIDTH) + TILE_WIDTH);
        SimpleVector upperRightBack = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, 0, -(y * TILE_WIDTH) + TILE_WIDTH);
        SimpleVector lowerLeftBack = new SimpleVector(x * TILE_WIDTH, TILE_HEIGHT, -(y * TILE_WIDTH) + TILE_WIDTH);
        SimpleVector lowerRightBack = new SimpleVector((x * TILE_WIDTH) + TILE_WIDTH, TILE_HEIGHT, -(y * TILE_WIDTH) + TILE_WIDTH);

        Object3D tile = new Object3D(8);

        // Left
        tile.addTriangle(upperLeftFront, 0, 0, upperLeftBack, TILE_WIDTH, 0, lowerLeftFront, 0, TILE_HEIGHT, textureManager.getTextureID(DUNGEON_WALL_1));
        tile.addTriangle(upperLeftBack, TILE_WIDTH, 0, lowerLeftBack, TILE_WIDTH, TILE_HEIGHT, lowerLeftFront, 0, TILE_HEIGHT, textureManager.getTextureID(DUNGEON_WALL_1));

        // Right
        tile.addTriangle(upperRightFront, TILE_WIDTH, 0, lowerRightFront, TILE_WIDTH, TILE_HEIGHT, upperRightBack, 0, 0, textureManager.getTextureID(DUNGEON_WALL_1));
        tile.addTriangle(upperRightBack, 0, 0, lowerRightFront, TILE_WIDTH, TILE_HEIGHT, lowerRightBack, 0, TILE_HEIGHT, textureManager.getTextureID(DUNGEON_WALL_1));

        // Front
        tile.addTriangle(upperLeftFront, TILE_WIDTH, 0, lowerLeftFront, TILE_WIDTH, TILE_HEIGHT, upperRightFront, 0, 0, textureManager.getTextureID(DUNGEON_WALL_1));
        tile.addTriangle(upperRightFront, 0, 0, lowerLeftFront, TILE_WIDTH, TILE_HEIGHT, lowerRightFront, 0, TILE_HEIGHT, textureManager.getTextureID(DUNGEON_WALL_1));

        // Back
        tile.addTriangle(upperLeftBack, 0, 0, upperRightBack, TILE_WIDTH, 0, lowerLeftBack, 0, TILE_HEIGHT, textureManager.getTextureID(DUNGEON_WALL_1));
        tile.addTriangle(upperRightBack, TILE_WIDTH, 0, lowerRightBack, TILE_WIDTH, TILE_HEIGHT, lowerLeftBack, 0, TILE_HEIGHT, textureManager.getTextureID(DUNGEON_WALL_1));

//        tile.compile();
//        tile.strip();
        tile.build();

        return tile;
    }


I debugged the code for the generation and I didn't noticed any slight offset in the vectors during generation, I only saw whole numbers.
So I would say the edges of the cube match exactly.
As I'm blindly generating cube aside cube do the overlapping sides may cause problems? I suppose then I'll have to rethink the whole wall generation process of the dungeon...

EgonOlsen

It looks reasonable IMHO. I don't really know why you cubes don't align perfectly. Maybe you aren't calling build() again on the merged object or something like that?

phlux

Quote
Maybe you aren't calling build() again on the merged object or something like that?

I checked my code. I did call build() on everything. However I did change my code from creating block aside block aside block to only draw the walls and floor/ceiling parts I really needed and voila smooth lighting!  8)



So I think because of the overlapping Object3Ds everything was messed up.

But I discovered some more oddities. Maybe you can shed some light on them (no pun intended).
So when I turn away from the light source, I get some strangly lit walls (note: I have fogging enabled, changed to per_pixel as you suggested. No ambient light explicitly set). This is a 180° rotation away from the light source:


The next thing is, the light is somehow shining through my walls. Imagine following layout


#####
#..V#
#.#.#
#.#.#
#L#?#
#####


Whereas L is the light V is the player position looking south and ? is the position the light shines through.




Is this normal? (I hope not) How can I fix this?

Thanks in advance!

EgonOlsen

I fail to see the issue with the first (that 180° turn) screen shot. It looks perfectly fine to me. The lighting calculation takes the vertex normals at each vertex, calculates the angle between that normal and the vector from the light to that vertex and the smaller this angle is, the brighter the surface appears. That's the standard way of doing it. In your case, the normals of that brighter surface point directly towards the light while the ones from the other walls as well as floor and ceiling are poiting downwards/upwards. So they appear darker. That's how it's supposed to be, all is fine with it.
The other issue (lights passing walls) is normal as well. The lighting is calculated per surface. This calculation doesn't care about other surfaces. It's a lighting calculation, not a shadow calculation. You can work against this to a degree by playing around with light's attenuation and discard distance, but it won't go away completely. Personally, I don't think that it's an issue. My game has the same "issue" and I actually use it to dimly light parts of the dungeon that would otherwise be dark. When textured with actual textures, nobody won't be able to tell, if the lighting is 100% physically correct or not. Especially not if the player itself emits some light as well.

Examples:






phlux

Hi Egon,

thanks again. I will play with the light settings a little bit more ;)

Your screenshots look awesome btw. Is this the same game from the trailers on the homepage? You should update them. Really neat stuff you achieved there.

Cheers, Chris