Как я могу реализовать освещение в двигателе вокселя?


15

Я создаю MC, похожий на ландшафтный двигатель, и я подумал, что освещение сделает его намного приятнее. Проблема в том, что блоки не освещаются должным образом, когда размещается блок, излучающий свет (см. Скриншоты внизу). на странице.

Пока что я хочу реализовать «блочное» освещение Minecraft. Итак, я создал 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;
        }
    }

Я предполагаю, что если я хочу реализовать освещение, я должен указать источник света для каждой вершины ... И теперь в моем файле эффектов я хочу иметь возможность взять это значение и соответствующим образом осветить вершину:

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();
    }
}

И это пример того, как я применяю освещение:

            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;

И, наконец, вот алгоритм, который вычисляет все это:

    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;
    }

Любые предложения и помощь будут высоко ценится

СКРИНШОТЫ Обратите внимание на артефакты сверху на местности и то, как частично освещена только левая часть ... Попытка освещения 1 По какой-то причине освещаются только определенные стороны куба, и он не освещает землю Попытка освещения 2

Еще один пример предыдущего

Разобрался с моей проблемой! Я не проверял, был ли этот блок уже освещен, и если да, то в какой степени (если он ниже, то выше)

    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);
        }
    }

Хотя все вышеперечисленное работает, кто-нибудь знает, как я могу сделать его более эффективным?



Хороший вопрос, но это дубликат нескольких существующих вопросов ... gamedev.stackexchange.com/questions/6507/… gamedev.stackexchange.com/questions/19207/…
Тим Холт

Ответы:


17

Я реализовал нечто похожее на это. Я написал сообщение об этом в своем блоге: byte56.com/2011/06/a-light-post . Но здесь я расскажу немного подробнее.

Хотя статья о потоке кода, связанная в другом ответе, довольно интересна. Насколько я понимаю, это не то, как Minecraft делает свое освещение. Освещение Minecraft - это больше клеточный автомат, чем традиционный источник света.

Я полагаю, вы знакомы с потоком воды в MC. Освещение в MC - это одно и то же. Я расскажу вам простой пример.

Вот несколько вещей, которые нужно иметь в виду.

  • Мы собираемся сохранить список кубов, которые нуждаются в проверке их значений освещения.
  • Только прозрачные и светоизлучающие кубы имеют значения освещения

Первый куб, который мы добавим, это источник света. Источник - это особый случай. Это значение света устанавливается в соответствии с типом источника света (например, факелы получают более яркое значение, чем лава). Если для куба значение освещения установлено выше 0, мы добавляем в список все прозрачные кубы, смежные с этим кубом. Для каждого куба в списке мы устанавливаем его светлое значение для его самого яркого соседа минус один. Это означает, что все прозрачные кубы (включая «воздух») рядом с источником света получают значение освещенности 15. Мы продолжаем обходить кубики вокруг источника света, добавляя кубики, которые необходимо проверить, и убирая освещенные кубики из списка. Ути, нам больше не нужно ничего добавлять. Это означает, что все последние установленные значения были установлены в 0, что означает, что мы достигли конца нашего освещения.

Это довольно простое объяснение освещения. Я сделал кое-что более продвинутое, но я начал с того же основного принципа. Это пример того, что он производит:

введите описание изображения здесь

Теперь, когда у вас есть все ваши легкие данные. Когда вы строите значения цвета для своих вершин, вы можете ссылаться на эти данные яркости. Вы можете сделать что-то вроде этого (где light это значение типа int между 0 и 15):

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

В основном я беру значение света от 0 до 1 до мощности 1.4f. Это дает мне немного более темные тени, чем линейная функция. Убедитесь, что значение вашего цвета никогда не превышает 1. Я сделал это путем деления на 16 вместо 15, чтобы у меня всегда было немного больше места. Затем переместил это дополнение на базу, чтобы у меня всегда была небольшая текстура, а не чистая чернота.

Затем в моем шейдере (похожем на файл эффектов) я получаю цвет фрагмента для текстуры и умножаю его на цвет освещения, который я создал выше. Это означает, что полная яркость дает текстуру, как она была создана. Очень низкая яркость дает текстуру очень темной (но не черной из-за основного цвета).

РЕДАКТИРОВАТЬ

Чтобы получить свет для лица, вы смотрите на куб в направлении нормали к лицу. Например, верхняя грань куба получает данные о свете из куба выше.

РЕДАКТИРОВАТЬ 2

Я попытаюсь ответить на некоторые ваши вопросы.

Так что я хотел бы сделать, это что-то вроде рекурсии в этом случае?

Вы можете использовать рекурсивный алгоритм или итеративный. Это зависит от вас, как вы хотите реализовать это. Просто убедитесь, что вы отслеживаете, какие кубы уже были добавлены, иначе вы будете продолжать работать вечно.

Кроме того, как алгоритм "загорается вниз"?

Если вы говорите о солнечном свете, солнечный свет немного отличается, так как мы не хотим, чтобы он уменьшал яркость. Мои данные куба содержат бит SKY. Если куб помечен как НЕБО, это означает, что он имеет свободный доступ к открытому небу над ним. Кубики SKY всегда получают полное освещение минус уровень темноты. Затем кубы рядом с небом, которые не являются небом, как входы в пещеры или выступы, начинают обычную процедуру освещения. Если вы просто говорите о точечном свете, падающем вниз ... это то же самое, что и любое другое направление.

Как бы я указал свет только для одного лица?

Вы не указываете свет только для одного лица. Каждый прозрачный куб определяет свет для всех твердых кубов, которые имеют с ним грань. Если вы хотите получить свет для лица, просто проверьте значение света для прозрачного куба, которого он касается. Если он не касается прозрачного куба, то вы все равно не будете его рендерить.

Образцы кода?

Нет.


@ Byte56 Хотелось бы узнать, как перевести этот алгоритм для работы с "кусками".
gopgop

Я думал просто обновить каждую сторону чанка в соответствии с соседом, а затем добавить все измененные блоки в список блоков для изменения, но это, похоже, не работает
gopgop

@gopgop Комментарии не место для обсуждения. Вы можете найти меня в чате через некоторое время. Или обсудить это с другими людьми там.
MichaelHouse

4

Прочитайте следующую статью, так как она должна дать вам много информации о том, что вы ищете. Есть много разделов об освещении, но, в частности, прочитайте разделы об Окружающей Окклюзии, Наблюдении за Светом и Сбором света:

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

Но пытаемся ответить на суть вашего вопроса:

  • Если вы хотите затемнить цвет, умножьте его на другой цвет (или просто поплавком между 0 и 1, если вы хотите затемнить все компоненты одинаково), где 1 в компоненте означает полную интенсивность, а 0 означает полную темноту.
  • Если вы хотите осветлить цвет, добавьте в него другой цвет и закрепите результат. Но будьте осторожны, чтобы не выбрать значения, которые настолько высоки, что это приведет к тому, что результат будет насыщенным до чисто белого. Выберите темные цвета с оттенком искомого оттенка, например темно-оранжевый (# 886600).

    или...

    После добавления цвета вместо того, чтобы фиксировать результат (т. Е. Зажимать каждый компонент цвета от 0 до 1), вместо этого масштабируйте результат - найдите, какой из трех компонентов (RGB) является наибольшим, и разделите их все на это значение. Этот метод немного лучше сохраняет исходные свойства цвета.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.