복셀 엔진에서 조명을 어떻게 구현할 수 있습니까?


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;

그리고 마지막으로 여기에 모든 것을 계산하는 알고리즘이 있습니다.

    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는 조명을 수행하는 방식이 아닙니다. 마인 크래프트 조명은 기존 광원보다 셀룰러 오토마타입니다.

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을 초과하지 않도록하십시오. 나는 15 대신 16으로 나눠서 항상 여분의 공간을 확보했습니다. 그런 다음 그 여분을베이스로 옮겼으므로 항상 약간의 질감이 있고 순수한 검은 색이 아닙니다.

그런 다음 (이펙트 파일과 유사한) 셰이더에서 텍스처의 조각 색상을 얻고 위에서 만든 조명 색상으로 곱합니다. 즉, 전체 밝기는 텍스처가 생성 된대로 제공됩니다. 매우 낮은 밝기는 텍스처를 매우 어둡게합니다 (하지만 기본 색상으로 인해 검은 색이 아님).

편집하다

얼굴의 빛을 얻으려면 얼굴의 법선 방향으로 큐브를 봅니다. 예를 들어 큐브의 윗면은 위의 큐브에서 라이트 데이터를 가져옵니다.

편집 2

귀하의 질문 중 일부를 해결하려고 노력할 것입니다.

그래서 내가 할 일은이 경우 재귀와 같은 것입니까?

재귀 알고리즘 또는 반복을 사용할 수 있습니다. 구현 방법은 당신에게 달려 있습니다. 어떤 큐브가 이미 추가되었는지 추적하고 있는지 확인하십시오. 그렇지 않으면 계속 진행됩니다.

또한 알고리즘은 어떻게 "하향 조명"입니까?

햇빛에 대해 이야기하고 있다면 햇빛의 밝기가 감소하고 싶지 않기 때문에 햇빛이 약간 다릅니다. 큐브 데이터에는 SKY 비트가 포함되어 있습니다. 큐브가 스카이 (SKY)로 표시되면 큐브 위의 열린 하늘에 명확하게 액세스 할 수 있음을 의미합니다. SKY 큐브는 항상 전체 조명에서 어둠 수준을 뺀 것입니다. 그런 다음 동굴 입구 나 돌출부와 같이 하늘이 아닌 하늘 옆의 큐브는 일반적인 조명 절차를 대신합니다. 포인트 라이트가 아래로 비추는 것에 대해 이야기하고 있다면 다른 방향과 동일합니다.

단일면에만 조명을 지정하려면 어떻게합니까?

단일면에만 조명을 지정하지 않습니다. 각 투명 큐브는면을 공유하는 모든 솔리드 큐브의 조명을 지정합니다. 얼굴의 빛을 얻으려면 터치하는 투명한 큐브의 빛 값을 확인하십시오. 투명 큐브에 닿지 않으면 어쨌든 렌더링되지 않습니다.

코드 샘플?

아니.


@ Byte56이 알고리즘을 "청크"에서 작동하도록 변환하는 방법을 알고 싶습니다.
gopgop

나는 이웃에 따라 청크의 각면을 업데이트 한 다음 변경된 블록을 모두 변경할 블록 목록에 추가하려고하지만 작동하지 않는 것 같습니다.
gopgop

@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) 중 가장 큰 구성 요소를 찾아 해당 값으로 나눕니다. 이 방법은 색상의 원래 속성을 조금 더 잘 유지합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.