Wie kann ich die Beleuchtung in einer Voxel-Engine implementieren?

15

Ich erstelle die MC-ähnliche Geländemaschine, und ich habe gedacht, dass die Beleuchtung sie viel schöner aussehen lässt. Das Problem ist, dass die Blöcke nicht richtig beleuchtet werden, wenn ein Licht emittierender Block platziert wird (siehe die Screenshots unten) auf der Seite.

Bisher möchte ich die "blockige" Beleuchtung von minecraft implementieren. Also habe ich ein VertexFormat erstellt:

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

Ich denke, wenn ich Beleuchtung implementieren möchte, muss ich für jeden Scheitelpunkt ein Licht angeben ... Und jetzt möchte ich in meiner Effektdatei diesen Wert nehmen und den Scheitelpunkt entsprechend beleuchten können:

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

Und dies ist ein Beispiel, wie ich Beleuchtung anwende:

            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;

Und zu guter Letzt ist hier der Algorit, der alles berechnet:

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

Anregungen und Hilfe wäre sehr dankbar

SCREENSHOTS Beachten Sie die Artefakte oben im Gelände und wie nur der linke Teil teilweise beleuchtet wird ... Beleuchtungsversuch 1 Aus irgendeinem Grund werden nur bestimmte Seiten des Würfels beleuchtet und der Boden wird nicht beleuchtet Beleuchtungsversuch 2

Ein weiteres Beispiel der vorherigen

Mein Problem herausgefunden! Ich habe nicht überprüft, ob dieser Block bereits beleuchtet ist und wenn ja, in welchem ​​Maße (wenn er niedriger ist, ist er höher).

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

Obgleich das oben genannte funktioniert, würde jemand wissen, wie ich es leistungsfähiger machen würde?

Darestium
quelle

Antworten:

17

Ich habe etwas Ähnliches implementiert. Ich habe einen Post darüber in meinem Blog geschrieben: byte56.com/2011/06/a-light-post . Aber ich werde hier ein wenig näher darauf eingehen.

Während der in einer anderen Antwort verlinkte Codeflow-Artikel ziemlich interessant ist. Soweit ich weiß, ist es nicht so, wie Minecraft seine Beleuchtung macht. Minecraft-Beleuchtung ist mehr zellulare Automaten als herkömmliche Lichtquellen.

Ich gehe davon aus, dass Sie mit dem Wasserfluss in MC vertraut sind. Die Beleuchtung in MC ist im Wesentlichen dasselbe. Ich werde Sie durch ein einfaches Beispiel führen.

Hier sind ein paar Dinge zu beachten.

  • Wir werden eine Liste der Würfel führen, deren Beleuchtungswerte überprüft werden müssen
  • Nur transparente Würfel und Licht emittierende Würfel haben Beleuchtungswerte

Der erste Würfel, den wir hinzufügen, ist die Lichtquelle. Eine Quelle ist ein Sonderfall. Sein Lichtwert wird entsprechend dem Lichtquellentyp eingestellt (zum Beispiel erhalten Taschenlampen einen helleren Wert als Lava). Wenn für einen Würfel der Lichtwert über 0 festgelegt ist, fügen wir der Liste alle transparenten Würfel hinzu, die diesem Würfel benachbart sind. Für jeden Würfel in der Liste setzen wir den Lichtwert auf den hellsten Nachbarn minus eins. Dies bedeutet, dass alle transparenten Würfel (einschließlich "Luft") neben der Lichtquelle einen Lichtwert von 15 erhalten. Wir gehen weiter mit den Würfeln um die Lichtquelle herum, fügen zu überprüfende Würfel hinzu und entfernen beleuchtete Würfel aus der Liste , util müssen wir nichts mehr hinzufügen. Das bedeutet, dass alle zuletzt eingestellten Werte auf 0 gesetzt wurden, was bedeutet, dass wir das Ende unseres Lichts erreicht haben.

Das ist eine ziemlich einfache Erklärung für die Beleuchtung. Ich habe etwas fortgeschritteneres gemacht, aber ich habe mit dem gleichen Grundprinzip angefangen. Dies ist ein Beispiel für das, was es produziert:

Bildbeschreibung hier eingeben

Jetzt haben Sie Ihren gesamten Lichtdatensatz. Wenn Sie die Farbwerte für Ihre Scheitelpunkte erstellen, können Sie auf diese Helligkeitsdaten verweisen. Sie könnten so etwas tun (wobei light ein int-Wert zwischen 0 und 15 ist):

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

Grundsätzlich nehme ich den Lichtwert von 0 auf 1 hoch 1,4f. Dies gibt mir eine etwas dunklere Dunkelheit als eine lineare Funktion. Stellen Sie sicher, dass Ihr Farbwert niemals über 1 liegt. Ich habe dies durch Teilen durch 16 anstelle von 15 getan, damit ich immer etwas mehr Platz hätte. Dann bewegte ich das Extra auf die Basis, damit ich immer eine kleine Textur und keine reine Schwärze hatte.

Dann erhalte ich in meinem Shader (ähnlich einer Effektdatei) die Fragmentfarbe für die Textur und multipliziere diese mit der oben erstellten Beleuchtungsfarbe. Dies bedeutet, dass die volle Helligkeit die Textur ergibt, wie sie erzeugt wurde. Bei sehr geringer Helligkeit ist die Textur sehr dunkel (aufgrund der Grundfarbe jedoch nicht schwarz).

BEARBEITEN

Um das Licht für ein Gesicht zu erhalten, schauen Sie auf den Würfel in Richtung der Normalen des Gesichts. Auf der Oberseite des Würfels werden beispielsweise die Lichtdaten vom darüber liegenden Würfel abgerufen.

BEARBEITEN 2

Ich werde versuchen, einige Ihrer Fragen zu beantworten.

Also würde ich in diesem Fall so etwas wie eine Rekursion tun?

Sie können einen rekursiven oder iterativen Algorithmus verwenden. Es liegt an Ihnen, wie Sie es umsetzen möchten. Stellen Sie einfach sicher, dass Sie nachverfolgen, welche Cubes bereits hinzugefügt wurden.

Auch wie würde der Algorithmus "nach unten leuchten"?

Wenn Sie über Sonnenlicht sprechen, ist Sonnenlicht ein bisschen anders, da wir nicht möchten, dass es an Helligkeit verliert. Meine Cube-Daten enthalten ein SKY-Bit. Wenn ein Würfel als HIMMEL markiert ist, bedeutet dies, dass er freien Zugang zum Himmel darüber hat. SKY Cubes erhalten immer volle Beleuchtung, abzüglich der Dunkelheit. Dann Würfel neben Himmel, die nicht Himmel sind, wie Höhleneingänge oder Überhänge, übernimmt der normale Beleuchtungsvorgang. Wenn Sie nur von einem Punktlicht sprechen, das nach unten scheint, ist es die gleiche wie in jede andere Richtung.

Wie würde ich das Licht nur für ein einziges Gesicht festlegen?

Sie geben das Licht nicht nur für ein einzelnes Gesicht an. Jeder transparente Würfel gibt das Licht für alle festen Würfel an, die ein Gesicht mit ihm teilen. Wenn Sie das Licht für ein Gesicht erhalten möchten, überprüfen Sie einfach den Lichtwert für den transparenten Würfel, den er berührt. Wenn es keinen transparenten Würfel berührt, würden Sie ihn sowieso nicht rendern.

Codebeispiele?

Nee.

MichaelHouse
quelle
@ Byte56 Würde gerne wissen, wie man diesen Algorithmus übersetzt, um über "Chunks" hinweg zu arbeiten.
Gopgop
Ich dachte nur daran, jede Seite des Blocks dem Nachbarn entsprechend zu aktualisieren und dann alle geänderten Blöcke zur Liste der zu
ändernden
@gopgop Die Kommentare sind nicht der richtige Ort für Diskussionen. Sie können mich einige Zeit im Chat finden. Oder besprechen Sie es mit den anderen Leuten dort.
MichaelHouse
4

Lesen Sie den folgenden Artikel durch, da er Ihnen viele Informationen über das, was Sie suchen, geben sollte. Es gibt viele Abschnitte zum Thema Beleuchtung. Lesen Sie jedoch insbesondere die Abschnitte zu Umgebungsbedeckung, Beobachterlicht und Lichtsammlung:

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

Aber versuchen Sie, den Kern Ihrer Frage zu beantworten:

  • Wenn Sie eine Farbe abdunkeln möchten , multiplizieren Sie sie mit einer anderen Farbe (oder einfach mit einem Gleitkomma zwischen 0 und 1, wenn Sie alle Komponenten gleichermaßen abdunkeln möchten), wobei 1 in einer Komponente für volle Intensität steht, während 0 für volle Dunkelheit steht.
  • Wenn Sie eine Farbe aufhellen möchten , fügen Sie ihr eine weitere Farbe hinzu und klemmen Sie das Ergebnis. Achten Sie jedoch darauf, keine Werte zu wählen, die so hoch sind, dass das Ergebnis zu reinem Weiß gesättigt wird. Wählen Sie dunkle Farben mit einem Hauch des gewünschten Farbtons, z. B. Dunkelorange (Nr. 886600).

    oder...

    Nach der Farb Zugabe statt Klemm das Ergebnis (dh Klemm jeder Komponente der Farbe zwischen 0 und 1), skalieren das Ergebnis statt - finden , die von den drei Komponenten (RGB) ist die größte und teilen sie alle durch diesen Wert. Diese Methode bewahrt die ursprünglichen Eigenschaften der Farbe etwas besser.

David Gouveia
quelle