Bagaimana saya bisa menerapkan pencahayaan di mesin voxel?

15

Saya membuat MC seperti mesin medan, dan saya berpikir bahwa pencahayaan akan membuatnya tampak jauh lebih baik. Masalahnya adalah bahwa blok tidak menyala dengan benar ketika blok yang memancarkan cahaya ditempatkan (lihat screenshot di bagian bawah di halaman.

Sejauh ini saya ingin menerapkan pencahayaan "blocky" minecraft. Jadi saya membuat 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;
        }
    }

Saya kira jika saya ingin menerapkan pencahayaan, saya harus menentukan cahaya untuk setiap titik ... Dan sekarang dalam file efek saya, saya ingin dapat mengambil nilai itu dan menerangi titik tersebut sesuai:

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

Dan ini adalah contoh bagaimana saya menerapkan pencahayaan:

            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;

Dan yang terakhir di sini adalah algorythim yang menghitung semuanya:

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

Setiap saran dan bantuan akan sangat dihargai

SCREENSHOTS Perhatikan artefak di atas di medan dan bagaimana hanya bagian kiri yang dinyalakan secara parsial ... Mencoba pencahayaan 1 Untuk beberapa alasan, hanya sisi-sisi tertentu dari kubus yang dinyalakan dan tidak menerangi tanah Mencoba pencahayaan 2

Contoh lain dari sebelumnya

Menemukan masalah saya! Saya tidak memeriksa apakah blok itu sudah menyala dan jika demikian sampai tingkat berapa (jika lebih rendah cahaya lebih tinggi)

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

Meskipun bekerja di atas, apakah ada yang tahu bagaimana saya membuatnya lebih efisien?

Darestium
sumber
Sebuah pertanyaan yang bagus, tetapi merupakan duplikat dari beberapa pertanyaan yang ada ... gamedev.stackexchange.com/questions/6507/… gamedev.stackexchange.com/questions/19207/…
Tim Holt

Jawaban:

17

Saya sudah mengimplementasikan sesuatu yang mirip dengan ini. Saya menulis posting tentang itu di blog saya: byte56.com/2011/06/a-light-post . Tapi saya akan membahas sedikit lebih detail di sini.

Sementara artikel codeflow yang ditautkan dalam jawaban lain cukup menarik. Dari apa yang saya mengerti, bukan bagaimana Minecraft melakukan pencahayaannya. Pencahayaan Minecraft lebih merupakan automata seluler daripada sumber cahaya tradisional.

Saya menganggap Anda terbiasa dengan aliran air di MC. Pencahayaan pada MC pada dasarnya adalah hal yang sama. Saya akan memandu Anda melalui contoh sederhana.

Berikut adalah beberapa hal yang perlu diingat.

  • Kami akan menyimpan daftar kubus yang perlu diperiksa nilai pencahayaannya
  • Hanya kubus transparan dan kubus pemancar cahaya yang memiliki nilai pencahayaan

Kubus pertama yang kita tambahkan adalah sumber cahaya. Sumber adalah kasus khusus. Nilai cahayanya diatur sesuai dengan jenis sumber cahayanya (misalnya obor mendapatkan nilai lebih terang daripada lava). Jika sebuah kubus memiliki nilai cahayanya ditetapkan di atas 0, kami menambahkan semua kubus transparan yang berdekatan dengan kubus itu ke daftar. Untuk setiap kubus pada daftar, kami menetapkan nilai cahayanya ke tetangganya yang paling terang minus satu. Ini berarti bahwa semua kubus transparan (ini termasuk "udara") di sebelah sumber cahaya mendapatkan nilai cahaya 15. Kami terus berjalan kubus di sekitar sumber cahaya, menambahkan kubus yang perlu diperiksa dan mengambil kubus menyala dari daftar , util kami tidak lagi memiliki untuk menambahkan. Itu berarti bahwa semua nilai terbaru yang ditetapkan telah ditetapkan ke 0, yang berarti kita telah mencapai akhir dari cahaya kita.

Itu penjelasan pencahayaan yang cukup sederhana. Saya telah melakukan sesuatu yang sedikit lebih maju, tetapi saya mulai dengan prinsip dasar yang sama. Ini adalah contoh dari apa yang dihasilkannya:

masukkan deskripsi gambar di sini

Sekarang Anda memiliki semua kumpulan data ringan Anda. Saat Anda membangun nilai warna untuk simpul Anda, Anda dapat referensi data kecerahan ini. Anda bisa melakukan sesuatu seperti ini (di mana cahaya adalah nilai int antara 0 dan 15):

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

Pada dasarnya saya mengambil nilai cahaya dari 0 hingga 1 hingga kekuatan 1,4f. Ini memberi saya sedikit lebih gelap dari fungsi linear. Pastikan nilai warna Anda tidak pernah melampaui 1. Saya melakukannya dengan membaginya dengan 16, bukan 15 jadi saya selalu punya sedikit ruang ekstra. Kemudian pindah ekstra ke pangkalan sehingga saya selalu memiliki sedikit tekstur dan bukan kehitaman murni.

Kemudian di shader saya (mirip dengan file efek), saya mendapatkan warna fragmen untuk tekstur dan mengalikannya dengan warna pencahayaan yang saya buat di atas. Ini berarti kecerahan penuh memberikan tekstur seperti yang telah dibuat. Kecerahan yang sangat rendah memberi tekstur sangat gelap (tetapi bukan hitam karena warna dasarnya).

EDIT

Untuk mendapatkan cahaya untuk wajah, Anda melihat kubus ke arah normal wajah. Misalnya, permukaan atas kubus mendapatkan data ringan dari kubus di atas.

EDIT 2

Saya akan mencoba menjawab beberapa pertanyaan Anda.

Jadi yang akan saya lakukan adalah sesuatu seperti rekursi dalam kasus ini?

Anda dapat menggunakan algoritme rekursif atau berulang. Terserah Anda bagaimana Anda ingin mengimplementasikannya. Pastikan Anda sudah melacak kubus mana yang telah ditambahkan, jika tidak Anda akan terus berjalan selamanya.

Juga bagaimana algoritma "menyala ke bawah"?

Jika Anda berbicara tentang sinar matahari, sinar matahari sedikit berbeda, karena kami tidak ingin itu mengurangi kecerahan. Data kubus saya termasuk bit SKY. Jika sebuah kubus ditandai sebagai LANGIT itu berarti ia memiliki akses yang jelas ke langit terbuka di atasnya. Kubus SKY selalu mendapatkan pencahayaan penuh dikurangi tingkat kegelapan. Lalu kubus di sebelah langit, yang bukan langit, seperti pintu masuk gua atau overhang, prosedur pencahayaan normal mengambil alih. Jika Anda hanya berbicara tentang titik cahaya yang bersinar ... itu sama dengan arah lainnya.

Bagaimana saya menentukan cahaya hanya untuk satu wajah?

Anda tidak menentukan cahaya hanya untuk satu wajah. Setiap kubus transparan menentukan cahaya untuk semua kubus padat yang berbagi wajah dengannya. Jika Anda ingin mendapatkan cahaya untuk wajah, cukup periksa nilai cahaya untuk kubus transparan yang disentuhnya. Jika tidak menyentuh kubus transparan, berarti Anda tidak akan merendernya.

Contoh kode?

Nggak.

MichaelHouse
sumber
@ Byte56 Ingin tahu bagaimana menerjemahkan algoritma ini untuk bekerja di "potongan".
gopgop
Saya berpikir untuk memperbarui setiap sisi chunk sesuai dengan tetangga dan kemudian menambahkan semua blok yang berubah ke daftar blok untuk berubah tetapi tampaknya tidak berfungsi
gopgop
@ gopgop Komentar bukan tempat untuk diskusi. Anda dapat menemukan saya dalam obrolan beberapa saat. Atau diskusikan dengan orang lain di sana.
MichaelHouse
4

Baca artikel berikut karena seharusnya memberi Anda banyak informasi tentang apa yang Anda cari. Ada banyak bagian tentang pencahayaan, tetapi khususnya membaca bagian tentang Ambient Occlusion, Observer Light dan Light Gathering:

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

Tetapi mencoba menjawab inti pertanyaan Anda:

  • Jika Anda ingin menggelapkan suatu warna, gandakan dengan warna lain (atau hanya dengan melayang di antara 0 dan 1 jika Anda ingin menggelapkan semua komponen secara merata), di mana 1 dalam suatu komponen berarti intensitas penuh sedangkan 0 berarti kegelapan penuh.
  • Jika Anda ingin meringankan warna, tambahkan warna lain ke sana dan jepit hasilnya. Tapi hati-hati jangan sampai memilih nilai yang begitu tinggi sehingga akan membuat hasilnya jenuh hingga putih murni. Pilih warna gelap, dengan sedikit rona yang Anda cari, seperti oranye gelap (# 886600).

    atau...

    Setelah menambahkan warna, alih-alih menjepit hasilnya (yaitu menjepit setiap komponen warna antara 0 dan 1), skala hasilnya sebagai gantinya - cari yang mana dari tiga komponen (RGB) yang terbesar dan bagi semua dari mereka dengan nilai itu. Metode ini mempertahankan sifat asli warna sedikit lebih baik.

David Gouveia
sumber