Come posso implementare l'illuminazione in un motore voxel?


15

Sto creando l'MC come un motore di terreno, e ho pensato che l'illuminazione avrebbe reso un aspetto molto più bello. Il problema è che i blocchi non vengono illuminati correttamente quando viene posizionato un blocco che emette luce (vedere gli screenshot in basso sulla pagina.

Finora voglio implementare l'illuminazione "a blocchi" di Minecraft. Quindi ho creato un 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;
        }
    }

Immagino che se voglio implementare l'illuminazione devo specificare una luce per ciascun vertice ... E ora nel mio file degli effetti voglio essere in grado di prendere quel valore e illuminare il vertice di conseguenza:

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

E questo è un esempio di come applico l'illuminazione:

            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;

E l'ultimo di tutti qui è l'algorythim che calcola tutto:

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

Qualsiasi suggerimento e aiuto sarebbe molto apprezzato

SCREENSHOTS Notare i manufatti in alto sul terreno e come solo la parte sinistra è parzialmente illuminata ... Tentativo di accensione 1 Per qualche ragione si accendono solo alcuni lati del cubo e non illumina il terreno Tentativo di accensione 2

Un altro esempio del precedente

Ho capito il mio problema! Non stavo controllando se quel blocco era già acceso e, in tal caso, fino a che punto (se è luce più bassa è più alta)

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

Nonostante quanto sopra funziona, qualcuno saprebbe come lo renderei più performace efficace?



Una bella domanda, ma è un duplicato di molte domande esistenti ... gamedev.stackexchange.com/questions/6507/... gamedev.stackexchange.com/questions/19207/...
Tim Holt

Risposte:


17

Ho implementato qualcosa di simile a questo. Ho scritto un post a riguardo sul mio blog: byte56.com/2011/06/a-light-post . Ma entrerò in qualche dettaglio in più qui.

Mentre l'articolo di codeflow collegato in un'altra risposta è piuttosto interessante. Da quello che ho capito, non è come Minecraft fa la sua illuminazione. L'illuminazione di Minecraft è più automi cellulari della fonte di luce tradizionale.

Presumo che tu abbia familiarità con il flusso d'acqua in MC. L'illuminazione in MC è essenzialmente la stessa cosa. Ti guiderò attraverso un semplice esempio.

Ecco alcune cose da tenere a mente.

  • Terremo un elenco di cubi che richiedono il controllo dei valori di illuminazione
  • Solo i cubi trasparenti e quelli a emissione di luce hanno valori di illuminazione

Il primo cubo che aggiungiamo è la fonte di luce. Una fonte è un caso speciale. Il valore della luce è impostato in base al tipo di sorgente luminosa (ad esempio le torce ottengono un valore più luminoso della lava). Se un cubo ha il suo valore di luce impostato sopra 0, aggiungeremo tutti i cubi trasparenti adiacenti a quel cubo all'elenco. Per ogni cubo nell'elenco, impostiamo il valore della sua luce sul vicino più luminoso meno uno. Ciò significa che tutti i cubi trasparenti (incluso "aria") accanto alla sorgente luminosa ottengono un valore di luce di 15. Continuiamo a camminare sui cubi attorno alla sorgente luminosa, aggiungendo cubi che devono essere controllati e togliendo i cubi illuminati dall'elenco , non ne abbiamo più da aggiungere. Ciò significa che tutti gli ultimi valori impostati sono stati impostati su 0, il che significa che abbiamo raggiunto la fine della nostra luce.

Questa è una spiegazione abbastanza semplice dell'illuminazione. Ho fatto qualcosa di un po 'più avanzato, ma ho iniziato con lo stesso principio di base. Questo è un esempio di ciò che produce:

inserisci qui la descrizione dell'immagine

Ora che hai tutti i tuoi set di dati di luce. Quando si creano i valori di colore per i vertici, è possibile fare riferimento a questi dati di luminosità. Potresti fare qualcosa del genere (dove la luce ha un valore int compreso tra 0 e 15):

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

Fondamentalmente sto portando il valore della luce da 0 a 1 alla potenza di 1.4f. Questo mi dà un buio leggermente più scuro di una funzione lineare. Assicurati che il tuo valore di colore non superi mai 1. L'ho fatto dividendo per 16, invece di 15, quindi avrei sempre un po 'di spazio in più. Quindi ho spostato quel extra sulla base in modo da avere sempre un po 'di consistenza e non pura oscurità.

Quindi nel mio shader (simile a un file di effetti), ottengo il colore del frammento per la trama e lo moltiplico per il colore di illuminazione che creo sopra. Ciò significa che la luminosità completa conferisce alla trama come è stata creata. Una luminosità molto bassa conferisce alla trama un aspetto molto scuro (ma non nero a causa del colore di base).

MODIFICARE

Per ottenere la luce per una faccia, guardi il cubo nella direzione della normale della faccia. Ad esempio, la faccia superiore del cubo ottiene i dati sulla luce dal cubo sopra.

MODIFICA 2

Cercherò di rispondere ad alcune delle tue domande.

Quindi quello che vorrei fare è qualcosa come la ricorsione in questo caso?

È possibile utilizzare un algoritmo ricorsivo o iterativo. Spetta a te come vuoi implementarlo. Assicurati solo di tenere traccia di quali cubi sono già stati aggiunti, altrimenti continuerai per sempre.

Inoltre, come l'algoritmo "si illumina verso il basso"?

Se stai parlando della luce solare, la luce solare è leggermente diversa, poiché non vogliamo che diminuisca di luminosità. I miei dati del cubo includono un bit SKY. Se un cubo è contrassegnato come SKY, significa che ha un chiaro accesso al cielo aperto sopra di esso. I cubi SKY ottengono sempre un'illuminazione completa meno il livello di oscurità. Quindi i cubi vicino al cielo, che non sono il cielo, come gli ingressi delle caverne o sporgenze, prende il sopravvento sulla normale procedura di illuminazione. Se stai solo parlando di un punto luminoso che brilla ... è lo stesso di qualsiasi altra direzione.

Come specificare la luce solo per una singola faccia?

Non specifichi la luce per una sola faccia. Ogni cubo trasparente specifica la luce per tutti i cubi solidi che condividono una faccia con esso. Se vuoi ottenere la luce per una faccia, controlla semplicemente il valore della luce per il cubo trasparente che sta toccando. Se non tocca un cubo trasparente, non lo renderesti comunque.

Esempi di codice?

No.


@ Byte56 Mi piacerebbe sapere come tradurre questo algoritmo per lavorare su "blocchi".
gopgop,

Ho pensato di aggiornare solo ciascun lato del blocco in base al vicino e quindi aggiungere tutti i blocchi modificati all'elenco dei blocchi da modificare, ma non sembra funzionare
gopgop,

@gopgop I commenti non sono il luogo per la discussione. Puoi trovarmi in chat qualche volta. O discuterne con le altre persone lì.
MichaelHouse

4

Leggi il seguente articolo in quanto dovrebbe darti molte informazioni su ciò che cerchi. Esistono molte sezioni sull'illuminazione, ma in particolare leggi le sezioni sull'occlusione ambientale, la luce dell'osservatore e la raccolta della luce:

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

Ma cercando di rispondere al nocciolo della tua domanda:

  • Se vuoi scurire un colore, moltiplicalo per un altro colore (o semplicemente per un float tra 0 e 1 se vuoi scurire tutti i componenti allo stesso modo), dove 1 in un componente sta per piena intensità mentre 0 sta per piena oscurità.
  • Se vuoi schiarire un colore, aggiungi un altro colore e blocca il risultato. Ma fai attenzione a non scegliere valori così alti da rendere il risultato saturo di bianco puro. Scegli i colori scuri, con un pizzico della tonalità che stai cercando, come un arancio scuro (# 886600).

    o...

    Dopo aver aggiunto il colore, anziché bloccare il risultato (ovvero bloccare ogni componente del colore tra 0 e 1), ridimensiona invece il risultato: trova quale dei tre componenti (RGB) è il più grande e dividi tutti per quel valore. Questo metodo conserva un po 'meglio le proprietà originali del colore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.