ボクセルエンジンに照明を実装するにはどうすればよいですか?


15

テレインエンジンのようなMCを作成していますが、照明を使うと見栄えがずっと良くなると考えています。ページで。

これまでのところ、Minecraftの「ブロック」照明を実装したいと思います。そこで、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;
        }
    }

ライティングを実装する場合は、各頂点にライトを指定する必要があります...そして、エフェクトファイルでその値を取得し、それに応じて頂点を点灯できるようにしたいと思います。

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

そして、これは照明の適用方法の例です:

            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;

そして最後に、すべてを計算するアルゴリズムがあります:

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

提案やヘルプは大歓迎です

スクリーンショット 地形の上部にあるアーティファクトと、左部分のみが部分的に照らされていることに注意してください... 照明の試み1 何らかの理由で、キューブの特定の側面のみが照らされ、地面を照らしません 照明2の試み

前の別の例

私の問題を見つけました!そのブロックが既に点灯しているかどうか、また点灯している場合はどの程度かをチェックしていませんでした(低い場合は高い)

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

上記の作業はすべて完了しましたが、パフォーマンスをより効率的にする方法を誰もが知っていますか?



細かい質問が、それはいくつかの既存の質問の重複だ... gamedev.stackexchange.com/questions/6507/...の gamedev.stackexchange.com/questions/19207/...
ティム・ホルト

回答:


17

これに似たものを実装しました。私は私のブログにそれについての記事を書いた:byte56.com/2011/06/a-light-post。ただし、ここでもう少し詳しく説明します。

別の答えにリンクされているコードフローの記事は非常に興味深いものです。私が理解していることから、それはMinecraftがどのように照明を行うかではない。Minecraftの照明は、従来の光源よりもセルラーオートマトンです。

MCの水の流れに精通していると思います。MCの照明は基本的に同じものです。簡単な例を紹介します。

留意すべきいくつかの事項があります。

  • 照明値をチェックする必要があるキューブのリストを保持します
  • 透明なキューブと発光キューブのみに照明値があります

追加する最初のキューブは光源です。ソースは特殊なケースです。ライトの値は、光源のタイプに応じて設定されます(たとえば、トーチは溶岩よりも明るい値になります)。キューブのライト値が0より大きい場合、そのキューブに隣接するすべての透明キューブをリストに追加します。リスト上の各キューブについて、その明るい値を最も明るい隣のマイナス1に設定します。これは、光源の隣にあるすべての透明なキューブ(これには「空気」を含む)が15のライト値を取得することを意味します。 、utilは追加する必要がなくなりました。これは、すべての最新の値セットが0に設定されたことを意味します。これは、光の終わりに到達したことを意味します。

これは照明のかなり簡単な説明です。私はもう少し高度なことをしましたが、同じ基本原則から始めました。これは、生成されるものの例です。

ここに画像の説明を入力してください

これで、すべてのライトデータセットができました。頂点のカラー値を作成するとき、この輝度データを参照できます。次のようなことができます(lightは0〜15のint値です)。

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

基本的には、0から1の1.4f乗のライト値を使用しています。これにより、線形関数よりもわずかに暗い暗さが得られます。カラー値が1を超えないようにしてください。15ではなく16で割ることで、常に少し余裕を持たせています。次に、その余分な部分をベースに移動したので、純粋な黒さではなく、常に少しテクスチャがあります。

次に、シェーダー(エフェクトファイルに似ています)で、テクスチャのフラグメントカラーを取得し、それを上記で作成したライティングカラーで乗算します。これは、作成されたときの完全な明るさがテクスチャを与えることを意味します。非常に低い輝度は、テクスチャを非常に暗くします(ただし、ベースカラーのために黒ではありません)。

編集

顔の光を取得するには、立方体を顔の法線の方向に見ます。たとえば、立方体の上面は上の立方体からライトデータを取得します。

編集2

私はあなたの質問のいくつかに対処しようとします。

だから私はこの場合の再帰のようなものですか?

再帰アルゴリズムまたは反復アルゴリズムを使用できます。どのように実装するかはあなた次第です。どのキューブがすでに追加されているかを追跡していることを確認してください。そうでない場合は、永遠に継続します。

また、アルゴリズムはどのように「下向きに光る」でしょうか?

日光について話している場合、日光が明るさを落とさないようにするため、日光は少し異なります。キューブデータにSKYビットが含まれています。キューブがSKYとしてマークされている場合、そのキューブはその上の空に明確にアクセスできます。SKYキューブは常に完全な照明から暗闇のレベルを引いたものになります。次に、洞窟の入り口や張り出しなど、空ではない空の隣の立方体が、通常の照明手順に引き継がれます。あなたがただ下に輝いている点の光について話しているなら...それは他の方向と同じです。

単一の顔にのみライトを指定するにはどうすればよいですか?

1つの面だけにライトを指定することはありません。各透明キューブは、面を共有するすべてのソリッドキューブのライトを指定します。顔のライトを取得する場合は、触れている透明な立方体のライト値を確認するだけです。透明な立方体に触れていない場合は、とにかくレンダリングしません。

コードサンプル?

いや。


@ Byte56このアルゴリズムを「チャンク」間で機能するように変換する方法を知りたいです。
gopgop 14

私はちょうど隣に応じてチャンクの各側面を更新し、変更するブロックのリストに変更されたすべてのブロックを追加するのではと思ったが、それは動作するようには思えない
gopgop

@gopgopコメントは議論の場ではありません。いつかチャットで私を見つけることができます。または、そこにいる他の人と話し合ってください。
マイケルハウス

4

次の記事を読むと、探しているものに関する多くの情報が得られるはずです。ライティングに関するセクションは多数ありますが、特にアンビエントオクルージョン、オブザーバーライト、ライトギャザリングに関するセクションをお読みください。

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

しかし、あなたの質問の中核に答えようとしています:

  • 色を暗くしたい場合は、別の色を掛けます(または、すべてのコンポーネントを均等に暗くしたい場合は0と1の間のフロートを掛けます)。コンポーネントの1は完全な強度を表し、0は完全な暗さを表します。
  • 色を明るくしたい場合は、別の色を追加して固定しますて結果します。ただし、結果が純粋な白に飽和するほど高い値を選択しないように注意してください。暗いオレンジ(#886600)など、探している色相のヒントを含む暗い色を選択します。

    または...

    色を追加した後、結果をクランプする代わりに(つまり、色のすべてのコンポーネントを0から1の間でクランプする)、代わりに結果をスケーリングします-3つのコンポーネント(RGB)のどれが最大であるかを見つけ、その値ですべてを分割します。このメソッドは、色の元のプロパティを少し改善します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.