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.
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[i] = 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
↧