Як я можу реалізувати освітлення у воксельному двигуні?


15

Я створюю двигун на зразок місцевого двигуна, і я подумав, що освітлення зробить це набагато приємніше. Проблема полягає в тому, що блоки не засвічуються належним чином, коли розміщується блок, який випромінює світло (див. Знімки екрана внизу на сторінці.

Поки що я хочу здійснити "блокове" освітлення 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, це означає, що ми дійшли до кінця нашого світла.

Це досить просте пояснення освітлення. Я зробив щось трохи більш досконале, але я почав з того самого основного принципу. Це приклад того, що він виробляє:

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

Тепер, коли у вас є всі ваші легкі набори даних. Коли ви будуєте значення кольорів для своїх вершин, ви можете посилатися на ці дані яскравості. Ви можете зробити щось подібне (де світло - це ціле значення між 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, щоб у мене завжди було трохи додаткового місця. Потім перемістив цю зайву частину до основи, щоб я завжди мав трохи текстури, а не чистої чорноти.

Потім у своєму шейдері (схожий на файл ефектів) я отримую колір фрагмента текстури та помножую його на колір освітлення, який я створюю вище. Це означає, що повна яскравість надає текстуру, як вона була створена. Дуже низька яскравість надає текстурі як дуже темну (але не чорну через основного кольору).

EDIT

Щоб отримати світло для обличчя, ви дивитесь на куб у напрямку до нормалі обличчя. Наприклад, верхня грань куба отримує світлові дані від куба вгорі.

EDIT 2

Я спробую вирішити деякі ваші запитання.

Отже, що я б робив - це щось на кшталт рекурсії в цьому випадку?

Можна використовувати рекурсивний алгоритм або ітеративний. Це ви самі, як ви хочете це реалізувати. Просто переконайтесь, що ви відстежуєте, які кубики вже додані, інакше ви продовжуватиметесь навіки.

Також як би алгоритм "світла вниз"?

Якщо ви говорите про сонячне світло, сонячне світло дещо відрізняється, оскільки ми не хочемо, щоб воно зменшувалося. Мої дані куба містять біт SKY. Якщо куб позначений як СКАЙ, це означає, що він має чіткий доступ до відкритого неба над ним. Кубики небі завжди отримують повне освітлення за вирахуванням рівня темряви. Тоді кубики поруч із небом, які не є небом, як печерні входи або нависання, нормальна процедура освітлення бере на себе. Якщо ви просто говорите про світло, що світиться в точці ... це те саме, що і будь-який інший напрямок.

Як я можу вказати світло лише для одного обличчя?

Ви не визначаєте світло лише для одного обличчя. Кожен прозорий куб визначає світло для всіх твердих кубів, які діляться з ним обличчям. Якщо ви хочете отримати світло для обличчя, просто перевірте значення світла для прозорого куба, який він торкається. Якщо він не торкається прозорого куба, то ви його все одно не будете робити.

Зразки коду?

Ні.


@ Byte56 Хочеться знати, як перекласти цей алгоритм для роботи на "шматках".
gopgop

Я подумав просто оновити кожну сторону шматка відповідно до сусіда, а потім додати всі змінені блоки до списку блоків, які потрібно змінити, але це, здається, не працює
gopgop

@gopgop Коментарі не є місцем для обговорення. Ви можете знайти мене в чаті деякий час. Або обговоріть це з іншими людьми там.
MichaelHouse

4

Прочитайте наступну статтю, оскільки вона повинна дати вам багато інформації про те, що ви шукаєте. Існує багато розділів про освітлення, але, зокрема, читайте розділи про Забруднення навколишнього середовища, Світло та Світловий збір:

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

Але намагаємось відповісти на суть вашого запитання:

  • Якщо ви хочете затемнити колір, помножте його на інший колір (або просто поплавком між 0 і 1, якщо ви хочете затемнити всі компоненти однаково), де 1 в компоненті означає повну інтенсивність, а 0 - на повну темряву.
  • Якщо ви хочете освітлити колір, додайте до нього інший колір і зафіксуйте результат. Але будьте обережні, не вибирайте настільки високі значення, щоб це призвело до насичення чистого білого. Вибирайте темні кольори з натяком на той відтінок, який ви шукаєте, наприклад, темно-помаранчевий (# 886600).

    або ...

    Після додавання кольору замість затискання результату (тобто затискання кожного компонента кольору між 0 і 1) масштабуйте результат замість цього - знайдіть, який із трьох компонентів (RGB) є найбільшим, і розділіть їх усі на це значення. Цей спосіб трохи краще зберігає оригінальні властивості кольору.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.