Prefiltered LightProbe discontinuous

Started by
1 comment, last by xiaoyu Wang 6 years, 8 months ago

Seams.thumb.png.a2190c9874109aede2f9db4f9a7ae83c.png

I was reworking on my LightProbe filter, and I wrote some code to generate the Reference Cubemap, but then I noticed some discontinuous on the border of each face.(Top:CPU implementaion, Bottom: GPU implementation, the contrast has been adjusted on the right side)

At first I think it maybe caused by the interpolation, but then I tried the same algorithm in 2D (like a slice in the normal light probe prefiltering) for better visualization, and the result really confused me.

See the attachments, the top half is the Prefiltered Color value, displayed per channel, it's upside down because I used the ColorValue directly as the y coordinate. 

59a3ec7a626ae_Roughness0.5.thumb.png.9f58ef3ec648dfee8c9fda0ab821ecce.png

59a3ec7b1a040_Roughness1.thumb.png.e8f96810505a36740b99e5b4224fe617.png

The bottom half is the differential of the color, it's very clearly there is a discontinuous, and the position is where the border should be. And as the roughness goes higher, the plot gets stranger .

So, I am kinda of stuck in here, what's happening and what to do to remove this artifact? Anybody have any idea? 

and here is my code


inline FVector2D Map(int32 FaceIndex, int32 i, int32 FaceSize, float& SolidAngle)
{
    float u = 2 * (i + 0.5) / (float)FaceSize - 1;

    FVector2D Return;
    switch (FaceIndex)
    {
    case 0: Return = FVector2D(-u, -1); break;
    case 1: Return = FVector2D(-1, u);  break;
    case 2: Return = FVector2D(u, 1); break;
    case 3: Return = FVector2D(1, -u); break;
    }

    SolidAngle = 1.0f / FMath::Pow(Return.SizeSquared(), 3.0f / 2.0f);
    return Return.SafeNormal();
}

void Test2D()
{
    const int32 Res = 256;
    const int32 MipLevel = 8;

    TArray<FLinearColor>    Source;
    TArray<FLinearColor>    Prefiltered;

    Source.AddZeroed(Res * 4);
    Prefiltered.AddZeroed(Res * 4);

    for (int32 i = 0; i < Res; ++i)
    {
        Source = FLinearColor(1, 0, 0);
        Source[Res + i] = FLinearColor(0, 1, 0);
        Source[Res * 2 + i] = FLinearColor(0, 0, 1);
        Source[Res * 3 + i] = FLinearColor(0, 0, 0);
    }

    const float Roughness = MipLevel / 8.0f;
    const float a = Roughness * Roughness;
    const float a2 = a * a;

    // Brute force sampling with GGX kernel
    for (int32 FaceIndex = 0; FaceIndex < 4; ++FaceIndex)
    {
        for (int32 i = 0; i < Res; ++i)
        {
            float SolidAngle = 0;
            FVector2D N = Map(FaceIndex, i, Res, SolidAngle);

            double TotalColor[3] = {};
            double TotalWeight = 0;
            for (int32 SampleFace = 0; SampleFace < 4; ++SampleFace)
            {
                for (int32 j = 0; j < Res; ++j)
                {
                    float SampleJacobian = 0;
                    FVector2D L = Map(SampleFace, j, Res, SampleJacobian);
                    const float NoL = (L | N);
                    if (NoL <= 0)
                        continue;

                    const FVector2D H = (N + L).SafeNormal();
                    const float NoH = (N | H);

                    float D = a2 * NoL * SampleJacobian / FMath::Pow(NoH*NoH * (a2 - 1) + 1, 2.0f) ;
                    TotalWeight += D;
                    FLinearColor Sample = Source[SampleFace * Res + j] * D;
                    TotalColor[0] += Sample.R;
                    TotalColor[1] += Sample.G;
                    TotalColor[2] += Sample.B;
                }
            }
            if (TotalWeight > 0)
            {
                Prefiltered[FaceIndex * Res + i] = FLinearColor(
                    TotalColor[0] / TotalWeight,
                    TotalColor[1] / TotalWeight,
                    TotalColor[2] / TotalWeight);
            }
        }
    }

    // Save to bmp
    const int32 Width = 4 * Res;
    const int32 Height = 768;

    TArray<FColor> Bitmap;
    Bitmap.SetNum(Width * Height);

    // Prefiltered Color curve per channel
    float MaxDelta = 0;
    for (int32 x = 0; x < Width; ++x)
    {
        FColor SourceColor = Source[x].ToFColor(false);

        Bitmap[x] = SourceColor;

        FColor Sample = Prefiltered[x].ToFColor(false);


        check(Sample.R < 256);
        check(Sample.G < 256);
        check(Sample.B < 256);
        Bitmap[Sample.R * Width + x] = FColor(255, 0, 0);
        Bitmap[Sample.G * Width + x] = FColor(0, 255, 0);
        Bitmap[Sample.B * Width + x] = FColor(0, 0, 255);

        if (x > 0)
        {
            const FLinearColor Delta = Prefiltered[x] - Prefiltered[x - 1];

            MaxDelta = FMath::Max(MaxDelta, FMath::Max3(FMath::Abs(Delta.R), FMath::Abs(Delta.G), FMath::Abs(Delta.B)));
        }
    }

    // Differential per channel
    const float Scale = 128 / MaxDelta;
    for (int32 x = 1; x < Width; ++x)
    {
        const FLinearColor Delta = Prefiltered[x] - Prefiltered[x - 1];

        Bitmap[int32(512 + Delta.R * Scale) * Width + x] = FColor(255, 0, 0);
        Bitmap[int32(512 + Delta.G * Scale) * Width + x] = FColor(0, 255, 0);
        Bitmap[int32(512 + Delta.B * Scale) * Width + x] = FColor(0, 0, 255);
    }

    FFileHelper::CreateBitmap(TEXT("Test"), Width, Height, Bitmap.GetData());
}

 

Roughness 0.5.bmp

Roughness 1.bmp

Advertisement

Well, the diff is NOT continuous. As we sample L/N by texcoordinate, if you write down the function, you'll notice the function is piecewise. It's different at each plane/line. So the discontinuity of diff is natural. 

This topic is closed to new replies.

Advertisement