Bir voksel motorunda aydınlatmayı nasıl uygulayabilirim?


15

MC'yi arazi motoru gibi oluşturuyorum ve aydınlatmanın onu çok daha güzel göstereceğini düşündüm. Sorun, ışık yayan bir blok yerleştirildiğinde blokların düzgün şekilde aydınlatılmamasıdır (alttaki ekran görüntülerine bakın) sayfada.

Şimdiye kadar minecraft'ın "bloklu" aydınlatmasını uygulamak istiyorum. Bu yüzden bir VertexFormat oluşturdum:

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

Aydınlatma uygulamak istersem her köşe için bir ışık belirtmeliyim sanırım ... Ve şimdi efekt dosyamda bu değeri alıp tepe noktasını buna göre aydınlatmak istiyorum:

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

Ve bu, aydınlatmayı nasıl uyguladığım üzerine bir örnek:

            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;

Ve her şeyden önce, hepsini hesaplayan algoritma:

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

Herhangi bir öneri ve yardım çok takdir edilecektir

EKRAN GÖRÜNTÜLERİ Arazinin üstündeki yapılara ve sadece sol parçanın kısmen nasıl aydınlatıldığına dikkat edin ... Aydınlatma girişimi 1 Nedense küpün sadece belirli tarafları yanıyor ve zemini aydınlatmıyor Aydınlatma girişimi 2

Önceki bir başka örnek

Sorunumu çözdüm! Bu bloğun zaten aydınlatılmış olup olmadığını kontrol etmiyordum ve eğer öyleyse (daha düşük ışık ise daha yüksek)

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

Yukarıdaki çalışmalara rağmen, nasıl daha verimli performans sağlayacağımı bilen var mı?



Yanıtlar:


17

Buna benzer bir şey uyguladım. Blogumda bununla ilgili bir yazı yazdım: blogumda byte56.com/2011/06/a-light-post . Ama burada biraz daha ayrıntıya gireceğim.

Başka bir cevaba bağlı kod akışı makalesi oldukça ilginç olsa da. Anladığım kadarıyla Minecraft'ın aydınlatmasını böyle yapmıyor. Minecraft aydınlatma, geleneksel ışık kaynağından daha hücresel otomatlardır.

MC'deki su akışına aşina olduğunuzu varsayıyorum. MC'de aydınlatma aslında aynı şeydir. Basit bir örnekle size yol göstereceğim.

Akılda tutulması gereken birkaç şey var.

  • Aydınlatma değerlerinin kontrol edilmesi gereken küplerin bir listesini tutacağız
  • Sadece şeffaf küpler ve ışık yayan küpler aydınlatma değerlerine sahiptir

Eklediğimiz ilk küp ışık kaynağı. Kaynak özel bir durumdur. Işık değeri ışık kaynağı tipine göre ayarlanır (örneğin meşaleler lavdan daha parlak bir değer alır). Bir küpün ışık değeri 0'ın üzerine ayarlanmışsa, o küpün yanındaki tüm şeffaf küpleri listeye ekleriz. Listedeki her küp için ışık değerini en parlak komşusu eksi bir olarak ayarladık. Bu, ışık kaynağının yanındaki tüm saydam küplerin (bu "hava" içerir) 15'lik bir ışık değeri elde ettiği anlamına gelir. Küpleri ışık kaynağının etrafında yürümeye, kontrol edilmesi gereken küpleri eklemeye ve yanan küpleri listeden çıkarmaya devam ediyoruz. , ekleyecek artık yok. Bu, en son ayarlanan tüm değerlerin 0 olarak ayarlandığı, yani ışığımızın sonuna ulaştığımız anlamına gelir.

Bu, aydınlatmanın oldukça basit bir açıklaması. Biraz daha gelişmiş bir şey yaptım ama aynı temel prensiple başladım. Bu, ürettiklerinin bir örneğidir:

resim açıklamasını buraya girin

Artık tüm ışık verilerinize sahipsiniz. Köşeleriniz için renk değerlerini oluştururken bu parlaklık verilerine başvurabilirsiniz. Böyle bir şey yapabilirsiniz (burada ışık 0 ile 15 arasında bir int değeridir):

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

Temelde ışık değerini 0'dan 1'e 1.4f gücüne alıyorum. Bu bana lineer bir fonksiyondan biraz daha koyu koyular veriyor. Renk değerinin asla 1'in üzerine çıkmadığından emin olun. Bunu 15 yerine 16'ya bölerek yaptım, böylece her zaman biraz daha fazla yerim olurdu. Sonra üsse taşıdım, bu yüzden her zaman saf bir karanlık değil, biraz dokuya sahip olurdum.

Sonra gölgelendiricimde (efekt dosyasına benzer), doku için parça rengini alıyorum ve yukarıda yarattığım ışık rengiyle çarpıyorum. Bu, tam parlaklığın, dokuyu oluşturulduğu gibi verdiği anlamına gelir. Çok düşük parlaklık, dokuya çok koyu (ancak temel renk nedeniyle siyah değil) verir.

DÜZENLE

Bir yüzün ışığını elde etmek için, küpü yüzün normaline doğru bakarsınız. Örneğin, küpün üst yüzü ışık verilerini yukarıdaki küpten alır.

DÜZENLEME 2

Sorularınızdan bazılarını ele almaya çalışacağım.

Peki, bu durumda özyineleme gibi bir şey olur mu?

Özyinelemeli bir algoritma veya yinelemeli kullanabilirsiniz. Nasıl uygulamak istediğiniz size kalmış. Sadece hangi küplerin eklendiğini takip ettiğinizden emin olun, aksi takdirde sonsuza kadar devam edersiniz.

Ayrıca algoritma nasıl "aşağıya doğru" yanar?

Güneş ışığından bahsediyorsanız, güneş ışığı biraz farklıdır, çünkü parlaklığın azalmasını istemiyoruz. Küp verilerim SKY biti içeriyor. Bir küp SKY olarak işaretlenirse, üstündeki açık gökyüzüne açık erişimi vardır. SKY küpleri her zaman tam aydınlatma eksi karanlık seviyesinden elde edilir. Sonra gökyüzünün yanındaki, mağara girişleri veya çıkıntıları gibi gökyüzü olmayan küpler normal aydınlatma prosedürünü üstlenir. Sadece ışığın parladığı bir noktadan bahsediyorsanız ... diğer yönlerle aynı.

Işığı yalnızca tek bir yüz için nasıl belirleyebilirim?

Yalnızca tek bir yüz için ışık belirtmezsiniz. Her şeffaf küp, bir yüzü paylaşan tüm katı küpler için ışığı belirtir. Bir yüzün ışığını almak istiyorsanız, dokunduğu şeffaf küpün ışık değerini kontrol etmeniz yeterlidir. Şeffaf bir küpe dokunmuyorsa, yine de oluşturmazsınız.

Kod örnekleri?

Hayır!


@ Byte56 Bu algoritmayı "parçalar" üzerinde çalışmak üzere nasıl çevireceğinizi bilmek isteriz.
gopgop

Ben sadece komşusuna göre yığın her tarafını güncellemek ve daha sonra değiştirmek için bloklar listesine tüm değişen blokları ekleyerek düşündüm ama işe yaramaz gibi görünüyor
gopgop

@gopgop Yorumlar tartışma konusu değil. Beni biraz sohbette bulabilirsin. Ya da oradaki diğer insanlarla tartışın.
MichaelHouse

4

Aşağıdaki makaleyi okuyun, çünkü aradığınız şey hakkında size çok fazla bilgi vermelidir. Aydınlatma ile ilgili birçok bölüm vardır, ancak özellikle Ortam Tıkanıklığı, Gözlemci Işık ve Işık Toplama ile ilgili bölümleri okuyun:

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

Ama sorunuzun özünü cevaplamaya çalışmak:

  • Bir rengi koyulaştırmak istiyorsanız çarpın başka bir renkle (veya tüm bileşenleri eşit olarak koyulaştırmak istiyorsanız, 0 ile 1 arasında bir kayan nokta ile) ; burada bir bileşendeki 1 tam yoğunluk, 0 ise tam karanlık anlamına gelir.
  • Bir rengi açıklaştırmak istiyorsanız , ona bir renk daha ekleyin ve kelepçeleyin ve sonucu . Ancak, sonucu saf beyaza doygun hale getirecek kadar yüksek değerleri seçmemeye dikkat edin. Koyu turuncu (aradığınız renk tonu gibi koyu bir renk seçin, örneğin koyu turuncu (# 886600).

    veya...

    Rengi ekledikten sonra , sonucu sıkıştırmak yerine (yani rengin her bileşenini 0 ile 1 arasında sıkıştırmak yerine ) sonucu ölçeklendirin - üç bileşenden hangisinin (RGB) en büyük olduğunu bulun ve hepsini bu değere bölün. Bu yöntem rengin orijinal özelliklerini biraz daha iyi korur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.