Minecraft Alpha 1.1.2_01 Lighting

2023-03-20

I used apitrace with MultiMC, following this tutorial to rip world meshes out of the game. Then I whipped up a quick program to render those meshes with their light values on each face. This version of Minecraft uses legacy vertex arrays (glVertexPointer and friends) for each chunklet. A chunklet is a 16x16x16 piece of the world. Each chunklet gets its own legacy vertex array. Each chunklet is baked into a Display List that will be used to draw it later:

glNewList(some_val,GL_COMPILE);
...
glTexCoordPointer(2,GL_FLOAT,32,tcptr);
glColorPointer(4,GL_UNSIGNED_BYTE,32,cptr);
glVertexPointer(3,GL_FLOAT,32,vptr);
glDrawArrays(GL_TRIANGLES,0,vCount);
...
glEndList();

The stride is 32 bytes, but the total vertex data only takes up 24 bytes, so the last 8 bytes of every vertex are just padding. Interestingly it uses a full 4 byte color for each vertex, meaning the game could easily support colored lights if someone wanted to mod that in.

Anyways I ripped these chunklet meshes out of the game and rendered the byte light value on each face. In the following images we can see the light propagation for each cube normal when exposed to zero sunlight:

-x wide shot    -x close shot
+x wide shot    +x close shot
-y wide shot    -y close shot
+y wide shot    +y close shot
-z wide shot    -z close shot
+z wide shot    +z close shot

Compare +yclose to +xclose. You'll see that the highest block face value (non-torch) for +y is 201, whereas that of +x is 120. So, clearly the face normal effects the final brightness of the face. You could call it "ambient light". If you dig into the source of the game with Mod Coder Pack, you'll find a function called func_1243_a in RenderBlocks.java which multiplies the brightness of blockfaces by ambient light. Its coefficients for each cube normal are as follows:

-x and +x: 0.6f
-y: 0.5f
+y: 1.0f
-z and +z: 0.8f

Since the coefficient of +y is 1.0, the +y values we see in the wide shot are the base brightness values, which come from an array generated on startup by the following code:

field_1042_i = new float[16];
float f = 0.05F;
for(int i = 0; i <= 15; i++)
{
    float f1 = 1.0F - (float)i / 15F;
    field_1042_i[i] = ((1.0F - f1) / (f1 * 3F + 1.0F)) * (1.0F - f) + f;
}

This produces the following array:

0.050000
0.066667
0.085185
0.105882
0.129167
0.155556
0.185714
0.220513
0.261111
0.309091
0.366667
0.437037
0.525000
0.638095
0.788889
1.000000
Multiplying these values by 255 and rounding down gives our base light values:
12
16
21
26
32
39
47
56
66
78
93
111
133
162
201
255
Let's graph those coefficients:


Let's graph the function continuously:




Here you can see the *0.95 + 0.05 is an adjustment to make the light value at x=0 nonzero. The ratio between light levels varies, but it is close to the 80% value reported by the Minecraft Wiki.

During the day, skylight is 255, and affects block faces. This makes it effectively 1 light level stronger than torches. Skylight stays the same when moving down, but decreases by 1 for every block moved up or horizontally. Pictured here:



Leaves decrease skylight by 1 (not a great example image because light leaks through from thinner layers of the tree):


Water decreases skylight by 3:


At dusk, skylight is decreased by 1 light level about every 5 seconds (not sure on the exact timing) until it reaches light level 4 (byte value 32). Each time, the game recalculates the lighting for every chunk, giving that choppy day-to-night transition the alpha versions are known for.

Here you can see how a torch supersedes night time direct skylight:


That's everything. For fun here's some screenshots I took with MCMeshViewer:

Screenshot
Screenshot
Screenshot
Screenshot
Screenshot