如何在体素引擎中实现照明?


15

我正在创建类似于地形引擎的MC,我曾想过照明会使其看起来更好,但问题是当放置发光块时,这些块未正确照明(请参见底部的屏幕截图)在页面上。

到目前为止,我想实现我的世界的“块状”照明。所以我创建了一个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;

最后,这是计算所有内容的algorythim:

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

通过以上所有工作,有谁知道我将如何使其更加高效?



Answers:


17

我已经实现了类似的东西。我在我的博客上写了一篇关于它的文章:byte56.com/2011/06/a-light-post。但我将在这里进一步详细介绍。

虽然在另一个答案中链接的代码流文章非常有趣。据我了解,这不是Minecraft进行照明的方式。Minecraft照明比传统光源更像是蜂窝自动机。

我假设您熟悉MC中的水流。MC中的照明本质上是一回事。我将向您介绍一个简单的示例。

这里有几件事要牢记。

  • 我们将保留需要检查其照明值的多维数据集的列表
  • 仅透明立方体和发光立方体具有照明值

我们添加的第一个立方体是光源。来源是一种特殊情况。它的光值根据光源类型进行相应设置(例如,火炬比熔岩更亮)。如果一个多维数据集的光照值设置为大于0,则将与该多维数据集相邻的所有透明多维数据集添加到列表中。对于列表中的每个多维数据集,我们将其亮度值设置为其最亮的邻居减去1。这意味着光源旁边的所有透明多维数据集(包括“空气”)的光值均为15。我们继续在光源周围移动多维数据集,添加需要检查的多维数据集,然后从列表中删除点亮的多维数据集。 ,直到我们不再需要添加任何东西。这意味着所有设置的最新值都已设置为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。我通过除以16而不是15来做到这一点,所以我总是会有一点多余的空间。然后将多余的东西移到底部,这样我总是会有一点质感而不是纯黑色。

然后在我的着色器(类似于效果文件)中,获得纹理的片段颜色,然后将其乘以上面创建的照明颜色。这意味着完全的亮度可以使纹理产生。极低的亮度会使纹理非常暗(但由于基色而不是黑色)。

编辑

要获得脸部的光线,请沿脸部法线的方向查看立方体。例如,立方体的顶面从上方的立方体获取光数据。

编辑2

我将尝试解决您的一些问题。

所以在这种情况下,我会做类似递归的事情吗?

您可以使用递归算法或迭代算法。取决于您如何实现它。只需确保跟踪已添加了哪些多维数据集,否则您将永远继续前进。

算法也将“向下发光”吗?

如果您谈论的是阳光,那么阳光会有所不同,因为我们不希望它的亮度降低。我的多维数据集数据包含一个SKY位。如果将多维数据集标记为“天空”,则表示可以清楚地访问其上方的天空。天空立方体总是获得完整的照明,减去黑暗水平。然后,不是天空的立方体(例如不是洞穴的入口或悬挑)就在天空旁边,正常的照明程序将接管工作。如果您只是在谈论一个点灯向下发光...它与任何其他方向相同。

我如何只指定一张脸的光?

您不仅要为单个面孔指定灯光。每个透明立方体均指定与之共享一个面的所有实体立方体的光。如果要获取面部的光线,只需检查其触摸的透明立方体的光线值即可。如果它没有碰到透明的立方体,那么无论如何您都不会渲染它。

代码样本?

不。


@ Byte56很想知道如何翻译此算法以跨“块”工作。
gopgop

我想到只是根据邻居更新块的每一面,然后将所有已更改的块添加到要更改的块列表中,但这似乎不起作用
gopgop 2014年

@gopgop评论不是讨论的地方。您可以在聊天中找到我。或与那里的其他人讨论。
MichaelHouse

4

通读下面的文章,因为它应该为您提供许多有关您要寻找的信息。有许多关于照明的部分,但特别要阅读有关环境光遮挡,观察者光和光聚集的部分:

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

但是,尝试回答您问题的核心:

  • 如果要使颜色变暗,则将其乘以另一种颜色(如果要使所有组件均等地变暗,则可以简单地乘以 0到1之间的浮点数),其中组件中的1代表全亮度,而0代表全暗。
  • 如果要淡化一种颜色,请向其添加另一种颜色并夹紧结果。但是请注意不要选择太高的值,以免使结果饱和到纯白色。选择深色,并带有所需的色相,例如深橙色(#886600)。

    要么...

    添加颜色后,无需钳位结果(即将颜色的每个分量钳位在0和1之间),而是缩放结果-查找三个分量(RGB)中最大的分量,然后将所有值除以该值。此方法可以更好地保留颜色的原始属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.