Take the 2-minute tour ×
Game Development Stack Exchange is a question and answer site for professional and independent game developers. It's 100% free, no registration required.

I am creating the MC like terrain engine, and I have thought that lighting would make it look a whole lot nicer.The problem is that the blocks are not being lit properly when a block which emits light is placed (see the screenshots at the bottom on the page.

So far I want to implement minecraft's "blocky" lighting. So I created a VertexFormat:

 struct VertexPositionTextureLight
    {
        Vector3 position;
        Vector2 textureCoordinates;
        float light;

        public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
        (
            new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
            new VertexElement(sizeof(float) * 3, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
            new VertexElement(sizeof(float) * 5, VertexElementFormat.Single, VertexElementUsage.TextureCoordinate, 1)
        );

        public VertexPositionTextureLight(Vector3 position, Vector3 normal, Vector2 textureCoordinate, float light)
        {
            // I don't know why I included normal data :)
            this.position = position;
            this.textureCoordinates = textureCoordinate;
            this.light = light;
        }
    }

I guess if I want to implement lighting I have to specify a light for each vertex... And now in my effect file I want to be able to take that value and light up the vertex accordingly:

float4x4 World;
float4x4 Projection;
float4x4 View;

Texture Texture;

sampler2D textureSampler = sampler_state  {
    Texture = <Texture>;
    MipFilter = Point;
    MagFilter = Point;
    MinFilter = Point;
    AddressU = Wrap;
    AddressV = Wrap;
};

struct VertexToPixel  {
    float4 Position     : POSITION;
    float4 TexCoords    : TEXCOORD0;
    float4 Light        : TEXCOORD01;
};

struct PixelToFrame  {
    float4 Color        : COLOR0;
};

VertexToPixel VertexShaderFunction(float4 inPosition : POSITION, float4 inTexCoords : TEXCOORD0, float4 light : TEXCOORD01)  {
    VertexToPixel Output = (VertexToPixel)0;

    float4 worldPos = mul(inPosition, World);
    float4 viewPos = mul(worldPos, View);

    Output.Position = mul(viewPos, Projection);
    Output.TexCoords = inTexCoords;
    Output.Light = light;

    return Output;
}

PixelToFrame PixelShaderFunction(VertexToPixel PSIn)  {
    PixelToFrame Output = (PixelToFrame)0;

    float4 baseColor = 0.086f;
    float4 textureColor = tex2D(textureSampler, PSIn.TexCoords);
    float4 colorValue = pow(PSIn.Light / 16.0f, 1.4f) + baseColor;

    Output.Color = textureColor;

    Output.Color.r *= colorValue;
    Output.Color.g *= colorValue;
    Output.Color.b *= colorValue;
    Output.Color.a = 1;

    return Output;
}

technique Block  {
    pass Pass0  {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

VertexToPixel VertexShaderBasic(float4 inPosition : POSITION, float4 inTexCoords : TEXCOORD0)  {
    VertexToPixel Output = (VertexToPixel)0;

    float4 worldPos = mul(inPosition, World);
    float4 viewPos = mul(worldPos, View);

    Output.Position = mul(viewPos, Projection);
    Output.TexCoords = inTexCoords;

    return Output;
}

PixelToFrame PixelShaderBasic(VertexToPixel PSIn)  {
    PixelToFrame Output = (PixelToFrame)0;

    Output.Color = tex2D(textureSampler, PSIn.TexCoords);

    return Output;
}


technique Basic  {
    pass Pass0  {
        VertexShader = compile vs_2_0 VertexShaderBasic();
        PixelShader = compile ps_2_0 PixelShaderBasic();
    }
}

And this is an example on how i apply lighting:

            case BlockFaceDirection.ZDecreasing:
                light = world.GetLight((int)(backNormal.X + pos.X), (int)(backNormal.Y + pos.Y), (int)(backNormal.Z + pos.Z));

                SolidVertices.Add(new VertexPositionTextureLight(bottomRightBack, backNormal, bottomLeft, light));
                SolidVertices.Add(new VertexPositionTextureLight(bottomLeftBack, backNormal, bottomRight, light));
                SolidVertices.Add(new VertexPositionTextureLight(topRightBack, backNormal, topLeft, light));
                SolidVertices.Add(new VertexPositionTextureLight(topLeftBack, backNormal, topRight, light));
                AddIndices(0, 2, 3, 3, 1, 0);
                break;

And last of all here is the algorythim that calculates it all:

    public void AddCubes(Vector3 location, float light)
    {
        AddAdjacentCubes(location, light);
        Blocks = new List<Vector3>();
    }

    public void Update(World world)
    {
        this.world = world;
    }

    public void AddAdjacentCubes(Vector3 location, float light)
    {
        if (light > 0 && !CubeAdded(location))
        {
            world.SetLight((int)location.X, (int)location.Y, (int)location.Z, (int)light);
            Blocks.Add(location);

            // Check ajacent cubes
            for (int x = -1; x <= 1; x++)
            {
                for (int y = -1; y <= 1; y++)
                {
                    for (int z = -1; z <= 1; z++)
                    {
                        // Make sure the cube checked it not the centre one
                        if (!(x == 0 && y == 0 && z == 0))
                        {
                            Vector3 abs_location = new Vector3((int)location.X + x, (int)location.Y + y, (int)location.Z + z);

                            // Light travels on transparent block ie not solid
                            if (!world.GetBlock((int)location.X + x, (int)location.Y + y, (int)location.Z + z).IsSolid)
                            {
                                AddAdjacentCubes(abs_location, light - 1);
                            }
                        }
                    }
                }
            }

        }
    }

    public bool CubeAdded(Vector3 location)
    {
        for (int i = 0; i < Blocks.Count; i++)
        {
            if (location.X == Blocks[i].X &&
                location.Y == Blocks[i].Y &&
                location.Z == Blocks[i].Z)
            {
                return true;
            }
        }

        return false;
    }

Any suggestions and help would be much appreciated

SCREENSHOTS Notice the artifacts on the top on the terrain and how only the left part is partically lit... Attempt at lighting 1 For some reason only certain sides of the cube is being lit and it doesn't light the ground Attempt at lighting 2

Another example of the previous

Figured out my problem! I was not checking if that block was already lit and if so to what degree (if it is lower light it higher)

    public void DoLight(int x, int y, int z, float light)
    {
        Vector3 xDecreasing = new Vector3(x - 1, y, z);
        Vector3 xIncreasing = new Vector3(x + 1, y, z);
        Vector3 yDecreasing = new Vector3(x, y - 1, z);
        Vector3 yIncreasing = new Vector3(x, y + 1, z);
        Vector3 zDecreasing = new Vector3(x, y, z - 1);
        Vector3 zIncreasing = new Vector3(x, y, z + 1);

        if (light > 0)
        {
            light--;

            world.SetLight(x, y, z, (int)light);
            Blocks.Add(new Vector3(x, y, z));

            if (world.GetLight((int)yDecreasing.X, (int)yDecreasing.Y, (int)yDecreasing.Z) < light &&
                world.GetBlock((int)yDecreasing.X, (int)yDecreasing.Y, (int)yDecreasing.Z).BlockType == BlockType.none)
                DoLight(x, y - 1, z, light);
            if (world.GetLight((int)yIncreasing.X, (int)yIncreasing.Y, (int)yIncreasing.Z) < light &&
                world.GetBlock((int)yIncreasing.X, (int)yIncreasing.Y, (int)yIncreasing.Z).BlockType == BlockType.none)
                DoLight(x, y + 1, z, light);
            if (world.GetLight((int)xDecreasing.X, (int)xDecreasing.Y, (int)xDecreasing.Z) < light &&
                world.GetBlock((int)xDecreasing.X, (int)xDecreasing.Y, (int)xDecreasing.Z).BlockType == BlockType.none)
                DoLight(x - 1, y, z, light);
            if (world.GetLight((int)xIncreasing.X, (int)xIncreasing.Y, (int)xIncreasing.Z) < light &&
                world.GetBlock((int)xIncreasing.X, (int)xIncreasing.Y, (int)xIncreasing.Z).BlockType == BlockType.none)
                DoLight(x + 1, y, z, light);
            if (world.GetLight((int)zDecreasing.X, (int)zDecreasing.Y, (int)zDecreasing.Z) < light &&
                world.GetBlock((int)zDecreasing.X, (int)zDecreasing.Y, (int)zDecreasing.Z).BlockType == BlockType.none)
                DoLight(x, y, z - 1, light);
            if (world.GetLight((int)zIncreasing.X, (int)zIncreasing.Y, (int)zIncreasing.Z) < light &&
                world.GetBlock((int)zIncreasing.X, (int)zIncreasing.Y, (int)zIncreasing.Z).BlockType == BlockType.none)
                DoLight(x, y, z + 1, light);
        }
    }

Altough the above works, would anyone know how I would make it more performace efficent?

share|improve this question

migrated from stackoverflow.com Dec 18 '11 at 13:53

This question came from our site for professional and enthusiast programmers.

2  
    
A fine question, but it's a duplicate of several existing questions... gamedev.stackexchange.com/questions/6507/… gamedev.stackexchange.com/questions/19207/… –  Tim Holt Dec 19 '11 at 0:53

2 Answers 2

up vote 15 down vote accepted

I've implemented something similar to this. I wrote up a post about it on my blog: byte56.com/2011/06/a-light-post. But I'll go into a little more detail here.

While the codeflow article linked in another answer is pretty interesting. From what I understand, it's not how Minecraft does its lighting. Minecraft lighting is more cellular automata then traditional light source.

I assume you're familiar with water flow in MC. Lighting in MC is essentially the same thing. I'll walk you through a simple example.

Here are a few things to keep in mind.

  • We're going to keep a list of cubes that need their lighting values checked
  • Only transparent cubes and light emitting cubes have lighting values

The first cube we add is the light source. A source is a special case. It's light value is set accordingly to the light source type (for example torches get a brighter value than lava). If a cube has its light value set above 0, we add all the transparent cubes adjacent to that cube to the list. For each cube on the list, we set its light value to its brightest neighbor minus one. This means that all the transparent cubes (this includes "air") next to the light source get a light value of 15. We continue walking the cubes around the light source, adding cubes that need to be checked and taking lit cubes off the list, util we no longer have any to add. That means that all the latest values set have been set to 0, which means we've reached the end of our light.

That's a fairly simple explanation of lighting. I've done something a little more advanced, but I started with the same basic principle. This is an example of what it produces:

enter image description here

Now that you have all your light data set. When you're building the color values for your vertices, you can reference this brightness data. You could do something like this (where light is an int value between 0 and 15):

float baseColor = .086f;
float colorValue = (float) (Math.pow(light / 16f, 1.4f) + baseColor );
return new Color(colorValue, colorValue, colorValue, 1);

Basically I'm taking the light value from 0 to 1 to the power of 1.4f. This gives me a slightly darker darks than a linear function. Ensure that your color value never goes above 1. I did that by dividing by 16, instead of 15 so I'd always have a little extra room. Then moved that extra to the base so I'd always have a little texture and not pure blackness.

Then in my shader (similar to an effects file), I get the fragment color for the texture and multiply that by the lighting color I create above. This means full brightness gives the texture as it was created. Very low brightness gives the texture as very dark (but not black because of the base color).

EDIT

To get the light for a face, you look at the cube in the direction of the normal of the face. For example, top face of cube gets the light data from the cube above.

EDIT 2

I'll attempt to address some of your questions.

So what I would do is something like recursion in this case?

You can use a recursive algorithm or iterative. It's up you you how you want to implement it. Just ensure you're keeping track of which cubes have been added already, otherwise you'll keep going forever.

Also how would the algorithm "light downwards"?

If you're talking about sunlight, sunlight is a bit different, since we don't want it to decrease in brightness. My cube data includes a SKY bit. If a cube is marked as SKY that means it has clear access to the open sky above it. SKY cubes always get full lighting minus the darkness level. Then cubes next to sky, that are not sky, like cave entrances or overhangs, the normal lighting procedure takes over. If you're just talking about a point light shining down... it's the same as any other direction.

How would I specify the light for a single face only?

You don't specify light for a single face only. Each transparent cube specifies the light for all the solid cubes that share a face with it. If you want to get the light for a face, simply check the light value for the transparent cube it's touching. If it's not touching a transparent cube, then you wouldn't be rendering it anyway.

Code samples?

Nope.

share|improve this answer
    
@Byte56 Would love to know how to translate this algorithm to work across "chunks". –  gopgop Aug 28 at 17:59
    
I thought of just updating each side of the chunk according to the neighbor and then adding all the changed blocks to the list of the blocks to change but it doesn't seem to work –  gopgop Aug 28 at 19:23
    
@gopgop The comments aren't the place for discussion. You can find me in chat some time. Or discuss it with the other people there. –  Byte56 Aug 28 at 21:27

Read through the following article as it should give you a lot of information on what you seek. There are many sections about lighting, but in particular read the sections about Ambient Occlusion, Observer Light and Light Gathering:

http://codeflow.org/entries/2010/dec/09/minecraft-like-rendering-experiments-in-opengl-4/

But trying to answer the core of your question:

  • If you want to darken a color, multiply it by another color (or simply by a float between 0 and 1 if you want to darken all components equally), where 1 in a component stands for full intensity while 0 stands for full darkness.
  • If you want to lighten a color, add another color to it and clamp the result. But be careful not to choose values that are so high that it would make the result saturate to pure white. Choose dark colors, with an hint of the hue that you're looking for, such as a dark orange (#886600).

    or...

    After adding the color, instead of clamping the result (i.e. clamping every component of the color between 0 and 1), scale the result instead - find which of the three components (RGB) is the largest and divide all of them by that value. This method preserves the original properties of the color a bit better.

share|improve this answer
    
Many Thanks! :) –  Darestium Dec 19 '11 at 22:06

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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