Jak mogę zaimplementować oświetlenie w silniku wokselowym?


15

Tworzę silnik terenowy podobny do MC i pomyślałem, że oświetlenie sprawi, że będzie wyglądać o wiele ładniej. Problem polega na tym, że bloki nie są odpowiednio oświetlone, gdy umieszczony jest blok emitujący światło (patrz zrzuty ekranu na dole) na stronie.

Do tej pory chcę wdrożyć „blokowe” oświetlenie Minecrafta. Więc stworzyłem 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;
        }
    }

Myślę, że jeśli chcę zastosować oświetlenie, muszę określić światło dla każdego wierzchołka ... A teraz w moim pliku efektów chcę móc wziąć tę wartość i odpowiednio oświetlić wierzchołek:

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

A to jest przykład zastosowania oświetlenia:

            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;

I na koniec tutaj jest algorytm, który oblicza to wszystko:

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

Wszelkie sugestie i pomoc będą mile widziane

ZRZUTY EKRANU Zwróć uwagę na artefakty na górze w terenie i na to, jak szczególnie świeci tylko lewa część ... Próba oświetlenia 1 Z jakiegoś powodu oświetlone są tylko niektóre boki sześcianu i nie oświetla podłoża Próba oświetlenia 2

Kolejny przykład poprzedniego

Zrozumiałem mój problem! Nie sprawdzałem, czy ten blok był już oświetlony, a jeśli tak, to w jakim stopniu (jeśli jest to światło niższe, to wyżej)

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

Mimo że powyższe działa, czy ktokolwiek wiedziałby, jak zwiększyć wydajność?



Dobre pytanie, ale jest duplikatem kilku istniejących pytań ... gamedev.stackexchange.com/questions/6507/… gamedev.stackexchange.com/questions/19207/...
Tim Holt

Odpowiedzi:


17

Zaimplementowałem coś podobnego do tego. Napisałem o tym post na moim blogu: byte56.com/2011/06/a-light-post . Ale przejdę tutaj trochę bardziej szczegółowo.

Podczas gdy artykuł z kodeflowem w innej odpowiedzi jest dość interesujący. Z tego, co rozumiem, nie jest to, jak Minecraft robi swoje oświetlenie. Oświetlenie Minecraft to bardziej automaty komórkowe niż tradycyjne źródło światła.

Zakładam, że znasz przepływ wody w MC. Oświetlenie w MC to w zasadzie to samo. Przedstawię ci prosty przykład.

Oto kilka rzeczy, o których należy pamiętać.

  • Będziemy przechowywać listę kostek, które wymagają sprawdzenia ich wartości oświetlenia
  • Tylko przezroczyste kostki i kostki emitujące światło mają wartości oświetlenia

Pierwszym sześcianem, który dodajemy, jest źródło światła. Źródło to szczególny przypadek. Jego wartość światła jest ustawiana odpowiednio do rodzaju źródła światła (na przykład pochodnie mają jaśniejszą wartość niż lawa). Jeśli wartość jasności kostki jest ustawiona powyżej 0, dodajemy do listy wszystkie przezroczyste kostki sąsiadujące z tą kostką. Dla każdej kostki na liście ustawiamy jej wartość światła na jej najjaśniejszego sąsiada minus jeden. Oznacza to, że wszystkie przezroczyste kostki (w tym „powietrze”) obok źródła światła mają wartość światła 15. Kontynuujemy spacery kostkami wokół źródła światła, dodając kostki, które należy sprawdzić, i usuwając świecące kostki z listy , util nie musimy już dodawać. Oznacza to, że wszystkie najnowsze ustawione wartości zostały ustawione na 0, co oznacza, że ​​osiągnęliśmy koniec naszego światła.

To dość proste wyjaśnienie oświetlenia. Zrobiłem coś nieco bardziej zaawansowanego, ale zacząłem od tej samej podstawowej zasady. To jest przykład tego, co produkuje:

wprowadź opis zdjęcia tutaj

Teraz, gdy masz już wszystkie swoje dane świetlne. Podczas budowania wartości kolorów dla swoich wierzchołków możesz odwoływać się do tych danych jasności. Możesz zrobić coś takiego (gdzie światło jest liczbą całkowitą od 0 do 15):

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

Zasadniczo biorę wartość światła od 0 do 1 do mocy 1,4f. To daje mi nieco ciemniejsze ciemności niż funkcja liniowa. Upewnij się, że twoja wartość koloru nigdy nie przekracza 1. Zrobiłem to dzieląc przez 16 zamiast 15, więc zawsze miałem trochę więcej miejsca. Potem przeniosłem ten dodatek do bazy, więc zawsze będę miał trochę tekstury, a nie czystej czerni.

Następnie w moim module cieniującym (podobnym do pliku efektów) uzyskuję kolor fragmentu dla tekstury i mnożę go przez kolor oświetlenia, który tworzę powyżej. Oznacza to, że pełna jasność daje teksturę taką, jaka została stworzona. Bardzo niska jasność sprawia, że ​​tekstura jest bardzo ciemna (ale nie czarna ze względu na kolor podstawowy).

EDYTOWAĆ

Aby uzyskać światło dla twarzy, patrzysz na sześcian w kierunku normalnej twarzy. Na przykład górna ściana sześcianu pobiera jasne dane z powyższego sześcianu.

EDYCJA 2

Spróbuję odpowiedzieć na niektóre twoje pytania.

Więc co bym zrobił, to coś w rodzaju rekurencji w tym przypadku?

Możesz użyć algorytmu rekurencyjnego lub iteracyjnego. Od Ciebie zależy, jak chcesz to wdrożyć. Po prostu upewnij się, że śledzisz, które kostki zostały już dodane, w przeciwnym razie będziesz kontynuować na zawsze.

Również w jaki sposób algorytm „świeci w dół”?

Jeśli mówisz o świetle słonecznym, światło słoneczne jest nieco inne, ponieważ nie chcemy, aby zmniejszyło się jego jasność. Moje dane kostki zawierają bit SKY. Jeśli sześcian jest oznaczony jako SKY, oznacza to, że ma wyraźny dostęp do otwartego nieba nad nim. Kostki SKY zawsze mają pełne oświetlenie minus poziom ciemności. Następnie sześciany obok nieba, które nie są niebem, jak wejścia do jaskini lub nawisy, przejmują normalne procedury oświetleniowe. Jeśli mówisz tylko o świetle punktowym świecącym w dół ... jest tak samo jak w każdym innym kierunku.

Jak określić światło tylko dla jednej twarzy?

Nie określasz światła tylko dla jednej twarzy. Każdy przezroczysty sześcian określa światło dla wszystkich stałych kostek, które dzielą z nim powierzchnię. Jeśli chcesz uzyskać światło dla twarzy, po prostu sprawdź wartość światła dla przezroczystej kostki, z którą się styka. Jeśli nie dotyka przezroczystej kostki, i tak nie renderowałbyś jej.

Próbki kodu?

Nie.


@ Byte56 Chciałbym wiedzieć, jak przetłumaczyć ten algorytm na różne „fragmenty”.
gopgop

Myślałem o zaktualizowaniu każdej strony fragmentu według sąsiada, a następnie dodaniu wszystkich zmienionych bloków do listy bloków do zmiany, ale wydaje się, że to nie działa
gopgop

@ gopgop Komentarze nie są miejscem dyskusji. Możesz mnie znaleźć na czacie przez jakiś czas. Lub porozmawiaj o tym z innymi osobami.
MichaelHouse

4

Przeczytaj poniższy artykuł, ponieważ powinien on dostarczyć Ci wielu informacji na temat tego, czego szukasz. Istnieje wiele sekcji na temat oświetlenia, ale w szczególności przeczytaj sekcje dotyczące Okluzji Otoczenia, Światła Obserwatora i Zbieranie Światła:

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

Ale próbując odpowiedzieć na rdzeń twojego pytania:

  • Jeśli chcesz przyciemnić kolor, pomnóż go przez inny kolor (lub po prostu przez liczbę zmiennoprzecinkową między 0 a 1, jeśli chcesz przyciemnić wszystkie komponenty jednakowo), gdzie 1 w składniku oznacza pełną intensywność, a 0 oznacza całkowitą ciemność.
  • Jeśli chcesz rozjaśnić kolor, dodaj do niego inny kolor i zrób wynik. Uważaj jednak, aby nie wybierać tak wysokich wartości, aby nasycenie wyniku było czystą bielą. Wybierz ciemne kolory z odcieniem, którego szukasz, na przykład ciemnopomarańczowy (# 886600).

    lub...

    Po dodaniu koloru zamiast zaciśnięcia wyniku (tj. Zaciśnięcia każdego składnika koloru od 0 do 1), przeskaluj wynik zamiast tego - znajdź, który z trzech składników (RGB) jest największy i podziel je wszystkie przez tę wartość. Ta metoda zachowuje oryginalne właściwości koloru nieco lepiej.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.