Comment puis-je implémenter l'éclairage dans un moteur voxel?


15

Je crée le moteur de terrain de type MC, et j'ai pensé que l'éclairage le rendrait beaucoup plus agréable Le problème est que les blocs ne sont pas allumés correctement lorsqu'un bloc émettant de la lumière est placé (voir les captures d'écran en bas) sur la page.

Jusqu'à présent, je veux mettre en œuvre l'éclairage "en bloc" de minecraft. J'ai donc créé 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;
        }
    }

Je suppose que si je veux implémenter l'éclairage, je dois spécifier une lumière pour chaque sommet ... Et maintenant, dans mon fichier d'effet, je veux pouvoir prendre cette valeur et éclairer le sommet en conséquence:

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

Et ceci est un exemple sur la façon dont j'applique l'éclairage:

            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;

Et enfin, voici l'algorythim qui calcule tout:

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

Toute suggestion et aide serait très appréciée

CAPTURES D'ÉCRAN Remarquez les artefacts en haut sur le terrain et comment seule la partie gauche est éclairée en particulier ... Tentative d'éclairage 1 Pour une raison quelconque, seuls certains côtés du cube sont éclairés et il n'éclaire pas le sol Tentative d'éclairage 2

Un autre exemple de la précédente

Compris mon problème! Je ne vérifiais pas si ce bloc était déjà allumé et si oui dans quelle mesure (s'il est plus faible, il est plus haut)

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

A travers les travaux ci-dessus, quelqu'un pourrait-il savoir comment je le rendrais plus performant?



Une bonne question, mais c'est un double de plusieurs questions existantes ... gamedev.stackexchange.com/questions/6507/… gamedev.stackexchange.com/questions/19207/…
Tim Holt

Réponses:


17

J'ai implémenté quelque chose de similaire à cela. J'ai écrit un article à ce sujet sur mon blog: byte56.com/2011/06/a-light-post . Mais je vais entrer dans un peu plus de détails ici.

Alors que l'article de flux de code lié dans une autre réponse est assez intéressant. D'après ce que je comprends, ce n'est pas comme ça que Minecraft fait son éclairage. L'éclairage Minecraft est plus un automate cellulaire qu'une source de lumière traditionnelle.

Je suppose que vous connaissez le débit d'eau dans MC. L'éclairage dans MC est essentiellement la même chose. Je vais vous expliquer un exemple simple.

Voici quelques éléments à garder à l'esprit.

  • Nous allons garder une liste de cubes dont la valeur d'éclairage doit être vérifiée
  • Seuls les cubes transparents et les cubes électroluminescents ont des valeurs d'éclairage

Le premier cube que nous ajoutons est la source lumineuse. Une source est un cas particulier. Sa valeur lumineuse est définie en fonction du type de source de lumière (par exemple, les torches obtiennent une valeur plus lumineuse que la lave). Si un cube a sa valeur de lumière définie au-dessus de 0, nous ajoutons tous les cubes transparents adjacents à ce cube à la liste. Pour chaque cube de la liste, nous définissons sa valeur lumineuse sur son voisin le plus brillant moins un. Cela signifie que tous les cubes transparents (cela inclut "l'air") à côté de la source lumineuse obtiennent une valeur lumineuse de 15. Nous continuons à marcher les cubes autour de la source lumineuse, en ajoutant des cubes qui doivent être vérifiés et en retirant les cubes allumés de la liste. , utilisez nous n'avons plus rien à ajouter. Cela signifie que toutes les dernières valeurs définies ont été définies sur 0, ce qui signifie que nous avons atteint la fin de notre lumière.

C'est une explication assez simple de l'éclairage. J'ai fait quelque chose d'un peu plus avancé, mais j'ai commencé avec le même principe de base. Voici un exemple de ce qu'il produit:

entrez la description de l'image ici

Maintenant que vous avez tout votre jeu de données d'éclairage. Lorsque vous créez les valeurs de couleur pour vos sommets, vous pouvez référencer ces données de luminosité. Vous pouvez faire quelque chose comme ça (où la lumière est une valeur int entre 0 et 15):

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

Fondamentalement, je prends la valeur lumineuse de 0 à 1 à la puissance de 1,4f. Cela me donne une obscurité légèrement plus foncée qu'une fonction linéaire. Assurez-vous que votre valeur de couleur ne dépasse jamais 1. J'ai fait cela en divisant par 16, au lieu de 15, donc j'aurais toujours un peu d'espace supplémentaire. Ensuite, j'ai déplacé cet extra vers la base pour avoir toujours un peu de texture et pas une pure noirceur.

Ensuite, dans mon shader (similaire à un fichier d'effets), j'obtiens la couleur du fragment pour la texture et la multiplie par la couleur d'éclairage que je crée ci-dessus. Cela signifie que la pleine luminosité donne la texture telle qu'elle a été créée. Une très faible luminosité donne une texture très sombre (mais pas noire à cause de la couleur de base).

ÉDITER

Pour obtenir la lumière d'un visage, vous regardez le cube dans le sens de la normale du visage. Par exemple, la face supérieure du cube obtient les données d'éclairage du cube ci-dessus.

EDIT 2

Je vais essayer de répondre à certaines de vos questions.

Donc, ce que je ferais, c'est quelque chose comme la récursivité dans ce cas?

Vous pouvez utiliser un algorithme récursif ou itératif. C'est à vous de voir comment vous voulez l'implémenter. Assurez-vous simplement de savoir quels cubes ont déjà été ajoutés, sinon vous continuerez pour toujours.

De plus, comment l'algorithme "s'éclairerait-il"?

Si vous parlez de la lumière du soleil, la lumière du soleil est un peu différente, car nous ne voulons pas qu'elle diminue de luminosité. Mes données de cube incluent un bit SKY. Si un cube est marqué comme CIEL, cela signifie qu'il a un accès clair au ciel ouvert au-dessus de lui. Les cubes SKY obtiennent toujours un éclairage complet moins le niveau d'obscurité. Ensuite, les cubes à côté du ciel, qui ne sont pas du ciel, comme les entrées de grottes ou les surplombs, la procédure d'éclairage normale prend le relais. Si vous parlez simplement d'un point lumineux qui brille ... c'est la même chose que n'importe quelle autre direction.

Comment spécifier la lumière pour une seule face uniquement?

Vous ne spécifiez pas la lumière pour une seule face. Chaque cube transparent spécifie la lumière pour tous les cubes solides qui partagent un visage avec lui. Si vous souhaitez obtenir la lumière d'un visage, vérifiez simplement la valeur de la lumière pour le cube transparent qu'il touche. S'il ne touche pas un cube transparent, vous ne le rendriez pas de toute façon.

Échantillons de code?

Nan.


@ Byte56 J'adorerais savoir comment traduire cet algorithme pour qu'il fonctionne sur des "morceaux".
gopgop

J'ai pensé à simplement mettre à jour chaque côté du morceau en fonction du voisin, puis à ajouter tous les blocs modifiés à la liste des blocs à modifier, mais cela ne semble pas fonctionner
gopgop

@gopgop Les commentaires ne sont pas le lieu de discussion. Vous pourrez me retrouver dans le chat quelque temps. Ou discutez-en avec les autres personnes présentes.
MichaelHouse

4

Lisez l'article suivant car il devrait vous donner beaucoup d'informations sur ce que vous cherchez. Il existe de nombreuses sections sur l'éclairage, mais en particulier, lisez les sections sur l'occlusion ambiante, la lumière de l'observateur et la collecte de lumière:

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

Mais en essayant de répondre au cœur de votre question:

  • Si vous voulez assombrir une couleur, multipliez -la par une autre couleur (ou simplement par un flottant entre 0 et 1 si vous voulez assombrir tous les composants de la même manière), où 1 dans un composant représente l'intensité totale tandis que 0 signifie l'obscurité totale.
  • Si vous souhaitez éclaircir une couleur, ajoutez-en une autre et fixez le résultat. Mais attention à ne pas choisir des valeurs si élevées que le résultat serait saturé de blanc pur. Choisissez des couleurs sombres, avec un soupçon de la teinte que vous recherchez, comme un orange foncé (# 886600).

    ou...

    Après avoir ajouté la couleur, au lieu de pincer le résultat (c'est-à-dire de serrer chaque composant de la couleur entre 0 et 1), redimensionnez le résultat à la place - trouvez lequel des trois composants (RVB) est le plus grand et divisez-les tous par cette valeur. Cette méthode préserve un peu mieux les propriétés d'origine de la couleur.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.