Add new PreIntegratedGF with 80% smaller but more accurate data

Update old generation code, use R16G16_UNorm format instead of R11G11B10, skip mips and reduce Y axis to 32 pixels.

#1492
This commit is contained in:
Wojtek Figat
2025-09-25 17:35:10 +02:00
parent d57eec3403
commit 41e851298d
5 changed files with 110 additions and 198 deletions

View File

@@ -25,219 +25,123 @@ GPU_CB_STRUCT(Data {
// https://blog.selfshadow.com/publications/s2015-shading-course/
// https://blog.selfshadow.com/publications/s2012-shading-course/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define _USE_MATH_DEFINES
#include <math.h>
#define COMPILE_WITH_ASSETS_IMPORTER 1
#define COMPILE_WITH_TEXTURE_TOOL 1
#include "Engine/Engine/Globals.h"
#include "Engine/Core/Math/Math.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/ContentImporters/ImportTexture.h"
#include "Engine/ContentImporters/AssetsImportingManager.h"
namespace PreIntegratedGF
{
static const int Resolution = 128;
static const int NumSamples = 512;
static const int ResolutionX = 128;
static const int ResolutionY = 32;
static const int NumSamples = 256;
struct vec2 {
double x, y;
vec2(double _x, double _y) :x(_x), y(_y) { };
Float2 Hammersley(int32 i, int32 sampleCount)
{
float E1 = (float)i / (float)sampleCount;
float E2 = (float)((double)ReverseBits((uint32)i) * 2.3283064365386963e-10);
return Float2(E1, E2);
}
vec2& operator /=(const double& b)
Float3 ImportanceSampleGGX(Float2 E, float roughness)
{
float m = roughness * roughness;
float m2 = m * m;
float phi = 2 * PI * E.X;
float cosTheta = sqrtf((1 - E.Y) / (1 + (m2 - 1) * E.Y));
float sinTheta = sqrtf(1 - cosTheta * cosTheta);
return Float3(sinTheta * cosf(phi), sinTheta * sinf(phi), cosTheta);
}
float Vis_SmithJointApprox(float roughness, float NoV, float NoL)
{
float a = roughness * roughness;
float Vis_SmithV = NoL * (NoV * (1 - a) + a);
float Vis_SmithL = NoV * (NoL * (1 - a) + a);
return 0.5f / (Vis_SmithV + Vis_SmithL);
}
Float2 IntegrateBRDF(float roughness, float NoV)
{
if (roughness < 0.04f) roughness = 0.04f;
Float3 V(sqrtf(1 - NoV * NoV), 0, NoV);
float a = 0, b = 0;
for (int32 i = 0; i < NumSamples; i++)
{
x /= b;
y /= b;
return *this;
}
};
Float2 E = Hammersley(i, NumSamples);
Float3 H = ImportanceSampleGGX(E, roughness);
Float3 L = 2 * Float3::Dot(V, H) * H - V;
struct ivec2 {
int x, y;
};
struct vec3 {
double x, y, z;
vec3(double _x, double _y, double _z) :x(_x), y(_y), z(_z) { };
double dot(const vec3& b)
{
return x*b.x + y*b.y + z*b.z;
}
};
vec3 operator*(const double& a, const vec3& b)
{
return vec3(b.x * a, b.y * a, b.z * a);
}
vec3 operator-(const vec3& a, const vec3& b)
{
return vec3(a.x - b.x, a.y - b.y, a.z - b.z);
}
inline double saturate(double x)
{
if (x < 0) x = 0;
if (x > 1) x = 1;
return x;
}
unsigned int ReverseBits32(unsigned int bits)
{
bits = (bits << 16) | (bits >> 16);
bits = ((bits & 0x00ff00ff) << 8) | ((bits & 0xff00ff00) >> 8);
bits = ((bits & 0x0f0f0f0f) << 4) | ((bits & 0xf0f0f0f0) >> 4);
bits = ((bits & 0x33333333) << 2) | ((bits & 0xcccccccc) >> 2);
bits = ((bits & 0x55555555) << 1) | ((bits & 0xaaaaaaaa) >> 1);
return bits;
}
inline double rand_0_1()
{
return 1.0 * rand() / RAND_MAX;
}
inline unsigned int rand_32bit()
{
unsigned int x = rand() & 0xff;
x |= (rand() & 0xff) << 8;
x |= (rand() & 0xff) << 16;
x |= (rand() & 0xff) << 24;
return x;
}
// using uniform randomness :(
double t1 = rand_0_1();
unsigned int t2 = rand_32bit();
vec2 Hammersley(int Index, int NumSamples)
{
double E1 = 1.0 * Index / NumSamples + t1;
E1 = E1 - int(E1);
double E2 = double(ReverseBits32(Index) ^ t2) * 2.3283064365386963e-10;
return vec2(E1, E2);
}
vec3 ImportanceSampleGGX(vec2 E, double Roughness)
{
double m = Roughness * Roughness;
double m2 = m * m;
double phi = 2 * PI * E.x;
double cosTheta = sqrt((1 - E.y) / (1 + (m2 - 1) * E.y));
double sinTheta = sqrt(1 - cosTheta * cosTheta);
vec3 H(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
double d = (cosTheta * m2 - cosTheta) * cosTheta + 1;
double D = m2 / (M_PI*d*d);
double PDF = D * cosTheta;
return H;
}
double Vis_SmithJointApprox(double Roughness, double NoV, double NoL)
{
double a = Roughness * Roughness;
double Vis_SmithV = NoL * (NoV * (1 - a) + a);
double Vis_SmithL = NoV * (NoL * (1 - a) + a);
return 0.5 / (Vis_SmithV + Vis_SmithL);
}
vec2 IntegrateBRDF(double Roughness, double NoV)
{
if (Roughness < 0.04) Roughness = 0.04;
vec3 V(sqrt(1 - NoV*NoV), 0, NoV);
double A = 0, B = 0;
for (int i = 0; i < NumSamples; i++)
{
vec2 E = Hammersley(i, NumSamples);
vec3 H = ImportanceSampleGGX(E, Roughness);
vec3 L = 2 * V.dot(H) * H - V;
double NoL = saturate(L.z);
double NoH = saturate(H.z);
double VoH = saturate(V.dot(H));
float NoL = Math::Saturate(L.Z);
float NoH = Math::Saturate(H.Z);
float VoH = Math::Saturate(Float3::Dot(V, H));
if (NoL > 0)
{
double Vis = Vis_SmithJointApprox(Roughness, NoV, NoL);
float Vis = Vis_SmithJointApprox(roughness, NoV, NoL);
float NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH);
double a = Roughness * Roughness;
double a2 = a*a;
double Vis_SmithV = NoL * sqrt(NoV * (NoV - NoV * a2) + a2);
double Vis_SmithL = NoV * sqrt(NoL * (NoL - NoL * a2) + a2);
double NoL_Vis_PDF = NoL * Vis * (4 * VoH / NoH);
double Fc = pow(1 - VoH, 5);
A += (1 - Fc) * NoL_Vis_PDF;
B += Fc * NoL_Vis_PDF;
float Fc = powf(1 - VoH, 5);
a += NoL_Vis_PDF * (1 - Fc);
b += NoL_Vis_PDF * Fc;
}
}
vec2 res(A, B);
res /= NumSamples;
return res;
return Float2(a, b) / (float)NumSamples;
}
bool OnGenerate(TextureData& image)
{
// Setup image
image.Width = ResolutionX;
image.Height = ResolutionY;
image.Depth = 1;
image.Format = PixelFormat::R16G16_UNorm;
image.Items.Resize(1);
image.Items[0].Mips.Resize(1);
auto& mip = image.Items[0].Mips[0];
mip.RowPitch = 4 * image.Width;
mip.DepthPitch = mip.RowPitch * image.Height;
mip.Lines = image.Height;
mip.Data.Allocate(mip.DepthPitch);
// Generate GF pairs to be sampled in [NoV, roughness] space
auto pos = (uint16*)mip.Data.Get();
for (int32 y = 0; y < image.Height; y++)
{
float roughness = ((float)y + 0.5f) / (float)image.Height;
for (int32 x = 0; x < image.Width; x++)
{
float NoV = ((float)x + 0.5f) / (float)image.Width;
Float2 brdf = IntegrateBRDF(roughness, NoV);
*pos++ = (uint16)(Math::Saturate(brdf.X) * MAX_uint16 + 0.5f);
*pos++ = (uint16)(Math::Saturate(brdf.Y) * MAX_uint16 + 0.5f);
}
}
return false;
}
void Generate()
{
String path = Globals::TemporaryFolder / TEXT("PreIntegratedGF.bmp");
FILE* pFile = fopen(path.ToSTD().c_str(), "wb");
byte data[Resolution * 3 * Resolution];
int c = 0;
for (int x = 0; x < Resolution; x++)
{
for (int y = 0; y < Resolution; y++)
{
vec2 brdf = IntegrateBRDF(1 - 1.0 * x / (Resolution - 1), 1.0 * y / (Resolution - 1));
data[c + 2] = byte(brdf.x * 255);
data[c + 1] = byte(brdf.y * 255);
data[c + 0] = 0;
c += 3;
}
}
BITMAPINFOHEADER BMIH;
BMIH.biSize = sizeof(BITMAPINFOHEADER);
BMIH.biSizeImage = Resolution * Resolution * 3;
BMIH.biSize = sizeof(BITMAPINFOHEADER);
BMIH.biWidth = Resolution;
BMIH.biHeight = Resolution;
BMIH.biPlanes = 1;
BMIH.biBitCount = 24;
BMIH.biCompression = BI_RGB;
BMIH.biSizeImage = Resolution * Resolution * 3;
BITMAPFILEHEADER bmfh;
int nBitsOffset = sizeof(BITMAPFILEHEADER) + BMIH.biSize;
LONG lImageSize = BMIH.biSizeImage;
LONG lFileSize = nBitsOffset + lImageSize;
bmfh.bfType = 'B' + ('M' << 8);
bmfh.bfOffBits = nBitsOffset;
bmfh.bfSize = lFileSize;
bmfh.bfReserved1 = bmfh.bfReserved2 = 0;
//Write the bitmap file header
UINT nWrittenFileHeaderSize = fwrite(&bmfh, 1, sizeof(BITMAPFILEHEADER), pFile);
//And then the bitmap info header
UINT nWrittenInfoHeaderSize = fwrite(&BMIH, 1, sizeof(BITMAPINFOHEADER), pFile);
//Finally, write the image data itself
//-- the data represents our drawing
UINT nWrittenDIBDataSize = fwrite(data, 1, lImageSize, pFile);
fclose(pFile);
Guid id;
Importers::TextureImportArgument arg;
arg.Options.Type = FormatType::HdrRGB;
arg.Options.IndependentChannels = true;
arg.Options.IsAtlas = false;
arg.Options.IsSRGB = false;
arg.Options.NeverStream = true;
Content::Import(path, Globals:... + PRE_INTEGRATED_GF_ASSET_NAME, &id, &arg);
Guid id = Guid::Empty;
ImportTexture::Options options;
options.Type = TextureFormatType::HdrRGB;
options.InternalFormat = PixelFormat::R16G16_UNorm;
options.IndependentChannels = true;
options.IsAtlas = false;
options.sRGB = false;
options.NeverStream = true;
options.GenerateMipMaps = false;
options.Compress = false;
options.InternalLoad.Bind(&OnGenerate);
const String path = Globals::EngineContentFolder / PRE_INTEGRATED_GF_ASSET_NAME + ASSET_FILES_EXTENSION_WITH_DOT;
AssetsImportingManager::Create(AssetsImportingManager::CreateTextureTag, path, id, &options);
}
};