Skip to content

Commit

Permalink
Compute IBL Irradiance and Specular Reflection cubemaps
Browse files Browse the repository at this point in the history
  • Loading branch information
Benualdo committed Nov 11, 2024
1 parent 1140856 commit 2babf18
Show file tree
Hide file tree
Showing 18 changed files with 373 additions and 106 deletions.
207 changes: 191 additions & 16 deletions data/Shaders/lighting/PrecomputeIBL.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
#include "system/bindless.hlsli"
#include "system/samplers.hlsli"
#include "system/pbr.hlsli"

static const uint samples = 1024;
#include "system/cubemap.hlsli"
#include "system/pbr.hlsli"

//--------------------------------------------------------------------------------------
float3 ImportanceSampleGGX(float2 Xi, float roughness, float3 N)
Expand All @@ -24,19 +24,19 @@ float3 ImportanceSampleGGX(float2 Xi, float roughness, float3 N)
}

//--------------------------------------------------------------------------------------
float2 IntegrateBRDF(float NdotV, float roughness)
float2 IntegrateBRDF(float _NdotV, float _roughness, uint _samples)
{
float3 V = float3(sqrt(1.0 - NdotV * NdotV), 0, NdotV);
float3 V = float3(sqrt(1.0 - _NdotV * _NdotV), 0, _NdotV);

float A = 0.0;
float B = 0.0;

float3 N = float3(0, 0, 1);

for (unsigned int i = 0u; i < samples; ++i)
for (unsigned int i = 0u; i < _samples; ++i)
{
float2 Xi = sampleHammersley(i, samples);
float3 H = ImportanceSampleGGX(Xi, roughness, N);
float2 Xi = sampleHammersley(i, _samples);
float3 H = ImportanceSampleGGX(Xi, _roughness, N);
float3 L = normalize(2.0f * dot(V, H) * H - V);

float NoL = max(L.z, 0.0f);
Expand All @@ -46,8 +46,8 @@ float2 IntegrateBRDF(float NdotV, float roughness)

if (NoL > 0.0)
{
float G = GeometrySmith(roughness, NoV, NoL);
//float G = gaSchlickGGX(NoV, NoL, roughness);
float G = GeometrySmith(_roughness, NoV, NoL);
//float G = gaSchlickGGX(NoV, NoL, _roughness);

float G_Vis = (G * VoH) / (NoH * NoV);
float Fc = pow(1.0 - VoH, 5.0);
Expand All @@ -57,39 +57,214 @@ float2 IntegrateBRDF(float NdotV, float roughness)
}
}

return float2(A, B) / (float)samples;
return float2(A, B) / (float)_samples;
}

//--------------------------------------------------------------------------------------
[numthreads(PRECOMPUTE_IBL_THREADGROUP_SIZE_X, PRECOMPUTE_IBL_THREADGROUP_SIZE_Y, PRECOMPUTE_IBL_THREADGROUP_SIZE_Z)]
void CS_GenerateSpecularBRDF(int2 dispatchThreadID : SV_DispatchThreadID)
{
const int2 size = precomputeIBLConstants.getDestinationSize();
const uint samples = 1024;
RWTexture2D<float2> dest = getRWTexture2D_float2(precomputeIBLConstants.getDestination());
uint width, height;
dest.GetDimensions(width, height);
const uint2 size = uint2(width, height);

float cosLo = (float)(dispatchThreadID.x) / (float)(size.x-1);
float roughness = (float)(size.y-dispatchThreadID.y-1) / (float)(size.y-1);

// clamp cosLo to avoid NaN
cosLo = max(cosLo, 0.001);

getRWTexture2D_float2(precomputeIBLConstants.getDestination())[dispatchThreadID.xy] = IntegrateBRDF(cosLo, roughness);
dest[dispatchThreadID.xy] = IntegrateBRDF(cosLo, roughness, samples);
}

//--------------------------------------------------------------------------------------
float3 getUVW(float2 _uv, CubemapFace _face)
{
float2 uv = 2.0 * float2(_uv.x, _uv.y) - 1.0;

float3 uvw;
switch(_face)
{
case CubemapFace::PositiveX:
uvw = float3(1, uv.y, -uv.x);
break;

case CubemapFace::NegativeX:
uvw = float3(-1.0, uv.y, uv.x);
break;
case CubemapFace::PositiveY:
uvw = float3(uv.x, 1.0, -uv.y);
break;
case CubemapFace::NegativeY:
uvw = float3(uv.x, -1.0, uv.y);
break;
case CubemapFace::PositiveZ:
uvw = float3(uv.x, uv.y, 1.0);
break;
case CubemapFace::NegativeZ:
uvw = float3(-uv.x, uv.y, -1.0);
break;
}
return normalize(uvw);
}

//--------------------------------------------------------------------------------------
float3 sampleHemisphere(float u1, float u2)
{
const float u1p = sqrt(max(0.0, 1.0 - u1*u1));
return float3(cos(2.0*PI*u2) * u1p, sin(2.0*PI*u2) * u1p, u1);
}

//--------------------------------------------------------------------------------------
void computeBasisVectors(const float3 N, out float3 S, out float3 T)
{
T = cross(N, float3(0.0, 1.0, 0.0));
T = lerp(cross(N, float3(1.0, 0.0, 0.0)), T, step(Epsilon, dot(T, T)));

T = normalize(T);
S = normalize(cross(N, T));
}

//--------------------------------------------------------------------------------------
float3 tangentToWorld(const float3 v, const float3 N, const float3 S, const float3 T)
{
return S * v.x + T * v.y + N * v.z;
}

//--------------------------------------------------------------------------------------
float3 SampleDirectionHemisphere(float u1, float u2)
{
float z = u1;
float r = sqrt(max(0.0f, 1.0f - z * z));
float phi = 2 * PI * u2;
float x = r * cos(phi);
float y = r * sin(phi);

return float3(x, y, z);
}

//--------------------------------------------------------------------------------------
[numthreads(PRECOMPUTE_IBL_THREADGROUP_SIZE_X, PRECOMPUTE_IBL_THREADGROUP_SIZE_Y, PRECOMPUTE_IBL_THREADGROUP_SIZE_Z)]
void CS_GenerateIrradianceCubemap(int3 dispatchThreadID : SV_DispatchThreadID)
{
const int2 size = precomputeIBLConstants.getDestinationSize();
const uint samples = 16384;

getRWTexture2DArray(precomputeIBLConstants.getDestination())[dispatchThreadID.xyz] = float4(1,0,1,1);
RWTexture2DArray<float4> cubemapDest = getRWTexture2DArray(precomputeIBLConstants.getDestination());
uint width, height, elements;
cubemapDest.GetDimensions(width, height, elements);
const uint2 size = uint2(width, height);

float2 uv = float2((float)(dispatchThreadID.x+0.5) / (float)(size.x), (float)(dispatchThreadID.y+0.5) / (float)(size.y));
uv.y = 1 - uv.y;
const CubemapFace face = (CubemapFace)dispatchThreadID.z;
const float3 N = getUVW(uv, face);
float3 S, T;
computeBasisVectors(N, S, T);

TextureCube cubemapSource = getTextureCube(precomputeIBLConstants.getSource());
uint srcMip = precomputeIBLConstants.getSourceMipLevel();

float3 irradiance = 0;
for(uint i = 0; i < samples; ++i)
{
float2 u = sampleHammersley(i, samples);
float3 Li = tangentToWorld(sampleHemisphere(u.x, u.y), N, S, T);
float cosTheta = max(0.0, dot(Li, N));

irradiance += 2.0 * min(cubemapSource.SampleLevel(linearClamp, Li, srcMip+1).rgb, HDRMax) * cosTheta;
}
irradiance /= float(samples);

if (0)
{
uint width, height, mipLevels;
cubemapSource.GetDimensions(0, width, height, mipLevels);
irradiance = cubemapSource.SampleLevel(linearClamp, N, srcMip).rgb;
}

cubemapDest[dispatchThreadID.xyz] = float4(irradiance,1);
}

//--------------------------------------------------------------------------------------
[numthreads(PRECOMPUTE_IBL_THREADGROUP_SIZE_X, PRECOMPUTE_IBL_THREADGROUP_SIZE_Y, PRECOMPUTE_IBL_THREADGROUP_SIZE_Z)]
void CS_GenerateSpecularReflectionCubemap(int3 dispatchThreadID : SV_DispatchThreadID)
{
const int2 size = precomputeIBLConstants.getDestinationSize();
RWTexture2DArray<float4> cubemapDest = getRWTexture2DArray(precomputeIBLConstants.getDestination());

uint samples, srcMipLevel;
const uint dstMipLevel = precomputeIBLConstants.getDestinationMipLevel();
switch(dstMipLevel)
{
case 0:
samples = 1;
srcMipLevel = 0;
break;

case 1:
samples = 65536;
srcMipLevel = dstMipLevel;
break;

case 2:
samples = 65536;
srcMipLevel = dstMipLevel;
break;

case 3:
samples = 16384;
srcMipLevel = dstMipLevel+1;
break;

default:
samples = 16384;
srcMipLevel = dstMipLevel;
break;
}

uint dstWidth, dstHeight, dstElements;
cubemapDest.GetDimensions(dstWidth, dstHeight, dstElements);
const uint2 size = uint2(dstWidth, dstHeight);

float2 uv = float2((float)(dispatchThreadID.x+0.5) / (float)(size.x), (float)(dispatchThreadID.y+0.5) / (float)(size.y));
uv.y = 1 - uv.y;
const CubemapFace face = (CubemapFace)dispatchThreadID.z;
const float3 N = getUVW(uv, face);
float3 S, T;
computeBasisVectors(N, S, T);

TextureCube cubemapSource = getTextureCube(precomputeIBLConstants.getSource());
uint srcWidth, srcHeight, srcMipLevels;
cubemapSource.GetDimensions(0, srcWidth, srcHeight, srcMipLevels);
float wt = 4.0 * PI / (6.0 * (float)srcWidth * (float)srcHeight);

const float roughness = (float)dstMipLevel / (precomputeIBLConstants.getDestinationMipLevelCount()-1);

float3 color = 0;
float weight = 0;

for(uint i = 0; i < samples; ++i)
{
float2 u = sampleHammersley(i, samples);
float3 Lh = tangentToWorld(sampleGGX(u.x, u.y, roughness), N, S, T);

float3 Li = 2.0 * dot(N, Lh) * Lh - N;

float cosLi = dot(N, Li);
if(cosLi > 0.0)
{
float cosLh = max(dot(N, Lh), 0.0);
float pdf = ndfGGX(cosLh, roughness) * 0.25;
float ws = 1.0 / (samples * pdf);
float mipLevel = max(0.5 * log2(ws / wt) + 1.0, 0.0);

color += min(cubemapSource.SampleLevel(linearClamp, Li, srcMipLevel).rgb * cosLi, HDRMax);
weight += cosLi;
}
}
color /= weight;

getRWTexture2DArray(precomputeIBLConstants.getDestination())[dispatchThreadID.xyz] = float4(size / 256.0, (float)dispatchThreadID.z / 6.0f, 1);
cubemapDest[dispatchThreadID.xyz] = float4(color, 1);
}

32 changes: 21 additions & 11 deletions data/Shaders/lighting/PrecomputeIBL.hlsli
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#define PRECOMPUTE_IBL_THREADGROUP_SIZE_X 16
#define PRECOMPUTE_IBL_THREADGROUP_SIZE_Y 16
#define PRECOMPUTE_IBL_THREADGROUP_SIZE_X 8
#define PRECOMPUTE_IBL_THREADGROUP_SIZE_Y 8
#define PRECOMPUTE_IBL_THREADGROUP_SIZE_Z 1

#include "system/constants.hlsli"
Expand All @@ -12,21 +12,31 @@ struct PrecomputeIBLConstants
#ifdef __cplusplus
PrecomputeIBLConstants()
{
src_dst = 0x0;
handle = 0;
mips = 0;
}
#endif

void setSource (uint _tex2D) { src_dst = packUint16low(src_dst, _tex2D); }
uint getSource () { return unpackUint16low(src_dst); }
void setSource (uint _tex2D) { handle = packUint16low(handle, _tex2D); }
uint getSource () { return unpackUint16low(handle); }

void setDestination (uint _rwtex2D) { src_dst = packUint16high(src_dst, _rwtex2D); }
uint getDestination () { return unpackUint16high(src_dst); }
void setSourceMipLevel (uint _mipLevel) { mips = packR8(mips, _mipLevel); }
uint getSourceMipLevel () { return unpackR8(mips); }

void setSourceMipLevelCount (uint _count) { mips = packG8(mips, _count); }
uint getSourceMipLevelCount () { return unpackG8(mips); }

void setDestinationSize (uint2 _size) { size = _size.x | (_size.y << 16); }
uint2 getDestinationSize () { return uint2(size & 0xFFFF, size >> 16); }
void setDestination (uint _rwtex2D) { handle = packUint16high(handle, _rwtex2D); }
uint getDestination () { return unpackUint16high(handle); }

uint src_dst;
uint size;
void setDestinationMipLevel (uint _mipLevel) { mips = packB8(mips, _mipLevel); }
uint getDestinationMipLevel () { return unpackB8(mips); }

void setDestinationMipLevelCount (uint _count) { mips = packA8(mips, _count); }
uint getDestinationMipLevelCount () { return unpackA8(mips); }

uint handle;
uint mips;
};

#define PrecomputeIBLConstantsCount sizeof(PrecomputeIBLConstants)/sizeof(u32)
Expand Down
3 changes: 3 additions & 0 deletions data/Shaders/lighting/deferredLighting.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ float3 shadeSample(GBufferSample _gbuffer, DepthStencilSample _depthStencil, flo
case DisplayMode::Lighting_Diffuse:
case DisplayMode::Lighting_Specular:
case DisplayMode::Lighting_RayCount:
case DisplayMode::Environment_Cubemap:
case DisplayMode::Environment_IrradianceCubemap:
case DisplayMode::Environment_SpecularReflectionCubemap:
break;

case DisplayMode::Forward_SurfaceType:
Expand Down
15 changes: 15 additions & 0 deletions data/Shaders/system/cubemap.hlsli
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef _CUBEMAP__HLSLI_
#define _CUBEMAP__HLSLI_

#include "types.hlsli"

vg_enum_class(CubemapFace, uint,
PositiveX = 0,
NegativeX,
PositiveY,
NegativeY,
PositiveZ,
NegativeZ
);

#endif // _CUBEMAP__HLSLI_
6 changes: 5 additions & 1 deletion data/Shaders/system/displaymodes.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ vg_enum_class(DisplayMode, uint,
Forward_SurfaceType,
Forward_WorldPosition,
Forward_WorldNormal,
Forward_ScreenPos,
Forward_ScreenPos,

Environment_Cubemap,
Environment_IrradianceCubemap,
Environment_SpecularReflectionCubemap,

Deferred_Albedo,
Deferred_AlbedoAlpha,
Expand Down
6 changes: 3 additions & 3 deletions data/Shaders/system/environment.hlsli
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@

float3 getEnvironmentBackgroundColor(float2 _uv, ViewConstants _viewConstants)
{
uint irradianceCubemapHandle = _viewConstants.getIrradianceCubemap();
uint envCubemapHandle = _viewConstants.getEnvironmentCubemap();

if (ReservedSlot::InvalidTextureCube != (ReservedSlot)irradianceCubemapHandle)
if (ReservedSlot::InvalidTextureCube != (ReservedSlot)envCubemapHandle)
{
float4 clipPos = float4(_uv*2-1, 1,1);
float4 viewPos = mul(clipPos, _viewConstants.getProjInv());
viewPos.xz *= -1;
float4 worldPos = mul(viewPos, _viewConstants.getViewInv());
worldPos.xyz *= -1;

TextureCube cubemap = getTextureCube(irradianceCubemapHandle);
TextureCube cubemap = getTextureCube(envCubemapHandle);
return cubemap.SampleLevel(linearClamp, normalize(worldPos.xyz), 0).rgb;
}
else
Expand Down
Loading

0 comments on commit 2babf18

Please sign in to comment.