Home       About Me       Contact       Downloads       Part 34    Part 36   

Part 35: Minecraft Lighting

October 3, 2011

I have several different things I could be working on. Now that the bricks look good in the Minecraft Viewer, I could move all that code into Crafty (which has block editing and distant landscapes) and SeaOfMemes (which has the solar system background), and release those pieces of code as well. However, I'd really like to get export working in McrView first, so that people can import their existing structures into my world.

I could work on my old GUI, so that you can have file dialogs in McrView and the other programs. Without that, I could certainly write an export/import, but it would have to take file names in some kludgy way. Block editing needs some kind of UI for the palette of bricks, which isn't there. But before the GUI is integrated, I need to get 2D text working on Linux and Mac, which I still haven't done.

Fig 1: "BlockLight" Minecraft data
And I could produce a nicer looking landscape, instead of the spiky mountains I did in the landscape demo. For SeaOfMemes, I need landscape for the planet and moon. This might seem like something I can leave until later, but if people start to build against the current landscape, they are going to be annoyed if all the mountains move around!

Finally, I need to do lighting. This is more graphics programming and I wasn't looking forward to it.

I dithered around for a couple of days, fixing bugs and staring at the ToDo list, until I remembered that Minecraft actually records the light values in the chunk files, so I don't have to figure them out. I decided that would be a simple thing to add to the viewer.

Sure enough, there are two arrays in the chunk file, "SkyLight" which records the amount of sun, and "BlockLight" which records light from torches/lava. The values are per block, not per face of a block, so I figure I'm supposed to use the light of the adjacent block when displaying a face. I implement this and it seems to be right. See Figure 1.

Before Beta 1.8, Minecraft pretty obviously recalculated each chunk as the sun rose and set. I was planning on doing the same thing, but the 1.8 version has smooth changes in lighting, so that raises the bar!

Unless Notch has some super-fast lighting code now, I think he must be keeping three values in each vertex and combining them in the shader. The values are the sky lighting for that vertex, the block lighting, and something for his "smooth" lighting.

Ambient Occlusion

The Minecraft wiki describes "smooth lighting" as "ambient occlusion", but I'm not sure that's what it really is. In the writeups I've seen, such as this one, you cast rays away from points on the model. The ones that hit sky increase brightness of that vertex. Where fewer rays hit sky, a vertex would be darker. Florian Bösch also has a writeup here.

This seems like an expensive technique to use in real time, but I guess it depends on how far you cast the rays. If Notch is keeping them very short, then only close blocks would affect the lighting. Playing with Minecraft, this seems to be the case. The "SkyLight" and "BlockLight" values dominate the lighting, and the "smooth" lighting only responds to nearby blocks.

I'm also confused about which directions rays are cast. If you wanted to see the effect of light from above, you would only cast rays in that direction. But that would seem to produce the wrong value when a torch is added and light is cast from below. Does it calculate two values, one for sky light and one for block light?

I build a snow house and look at lighting by the sky and by a torch, and decide that he must trace rays in all directions and use just one lighting pattern for both block and sky light. The top and bottom corners are lit similarly in each case, and the pattern is similar for torch or daylight. See Figure 2.

Fig 2: Minecraft lighting

To implement Ambient Occlusion, I follow Florian's lead and pre-generate the cells for a set of rays. I make them 8 units long. You can see the pattern in Figure 3. These are the 12 rays (out of 24) that would be cast by the top of a block.

I step from the starting cell along each ray until I hit a block that isn't air. Total up all the rays that are uninterrupted, and this determines the brightness of a vertex. Using this on a landscape, I get Figure 4. This does have shadows at the bottom edge of blocks, but doesn't look quite like Minecraft. And inside my snow house, everything is black.

Fig 3: Occlusion testing rays
Fig 4: First test, ambient only

I puzzle over that until I realize that at 8 blocks long, my ambient rays must be much longer than those used in Minecraft. The snow house isn't even 8 blocks wide, so of course none of the rays is uninterrupted, and the interior is black. Playing around with Minecraft some more, I think perhaps ambient lighting is only looking at the nearest block, or perhaps the nearest two.

If I'm going to look at only the small area around each brick, I shouldn't bother with casting rays. Instead, I can just count up the neighboring blocks. Figure 5 shows my test pattern with the neighboring 2 cells counted. Figure 6 shows the snow house. I also try it with a single neighbor considered. Figures 7 and 8 show the results of that version.

Fig 5: Counting two nearest cells
Fig 6: Snow house with 2-cells ambient
 
Fig 2: Minecraft lighting
Fig 7: Counting only nearest cell
Fig 8: Snow house with 1-cell ambient

Compare Figures 6 and 8 with the Minecraft version in Figure 2. Ignore the brightness and the lines around the edges of the cubes. I have the brightness turned all the way up, and we are only looking at ambient light here, not sun or torch light. Also, my snow texture is pure white, not a pattern.

In Minecraft, the center two cells in the opposite wall have the same intensity. The four center cells of the floor also have the same intensity. In Figure 6, with two cells considered, the entire wall is shaded, and the center cells fade off towards the edges. In Figure 8, with only one cell considered, it's the same as Minecraft.

So after all the fussing with ambient lighting and ray casting, it looks as if each vertex is lit by counting the number of neighbors, 0 to 4!

Still To Do

I still need to combine ambient light, torch light and sky light in the shader, which means changing the interface to my compressed-vertex cube shader. You won't see the effect of that without a day-night cycle, which also has to be added. And it needs a decent sunrise/sunset. Fog and clouds would complete the look.

I also have to decide what to do with the complex shapes I added in the last part. I could just shade the whole table or chest based on the simple Minecraft rules. Or I could calculate a real ambient occlusion lighting value for all the hundreds of vertexes of those shapes.

I won't want to do that for each instance of a rail-track section, but I could do a quality pass for the shape when I load it, then apply some overall lighting using the Minecraft rules. I'll have to see how hard that is and whether it looks right.

Crafty and SeaOfMemes

These ambient light rules are fine for the Minecraft viewer, and I'm using the sky and torch lighting values I have from the chunk files. But in the other two programs, that's not going to be true.

In Crafty, I have mountains and other distant landscape. I don't know whether to keep the simple lit-from-above rules Notch uses, or try to do something more ambitious.

One problem is the fact that the landscape is in chunks. If a distant mountain is only generated when you get close, should it suddenly extend a shadow over the landscape? Similarly, a tall building could pop into view and need a shadow.

In SeaOfMemes, I have curved landscapes, and the sunrise/sunset and fog calculations get more complex. I want you to be able to lift off the surface of the planet, see it curve, and see the atmosphere recede below. There are other games that do this in the shaders, so I know it's possible.

No Demo

It will take a bit more work to get the Minecraft viewer doing lighting right, so there's no demo or video this week. I've been trying to do a part each week, so you get an incomplete one this time.

Ordinarily, I'd put in another day and get some lighting working in the demo, but this weekend, I broke a tooth. So I have to find a dentist... shudder.

My Ugly iPad

On a completely different subject, you may remember that back in July I bought an iPad, with the idea of porting my code to that platform. I read through some of the documentation and downloaded a few demos to make sure I could compile and run code there, and then I dropped it. I just wasn't in the mood for another port of my code.

I still haven't gotten back to it, but I did want to use the iPad. Unfortunately, I'm kind of worried about it being stolen. Sticking one of these in my backpack feels like flashing $500 while I'm paying for lunch. So I decided to disguise it.

The iPad is almost exactly the size of a school composition book, so I bought one and cut the covers off. To get a bit of extra padding, I cut up some foam board and put it over the white Apple frame. It's all held together with double-sided tape, which should come off with a bit of work if I want to sell it.

It's pretty ugly though. Sorry, Apple fans!

Now I can use it.

Home       About Me       Contact       Downloads       Part 34    Part 36   

blog comments powered by Disqus