• FEATURED
• FEATURED
• FEATURED
• FEATURED
• FEATURED

View more

View more

View more

### Image of the Day Submit

IOTD | Top Screenshots

### The latest, straight to your Inbox.

Subscribe to GameDev.net Direct to receive the latest updates and exclusive content.

# Specular convolution problem.

3 replies to this topic

### #1xxxkxxx  Members

Posted 21 April 2017 - 07:17 AM

Hello guys! Another IBL / convolution topic here.

I was doing specular-ibl-ggx-convolution and had encountered following artefact. It happens on +Z and -Z faces.

This is -Z cubemap face viewed in Renderdoc (resolutions are 256x256, 128x128, 64x64, cubemap is little bit washed out because it is in float16 hdr format, so I had changed range to better visualize artefact):

In final rendered image looks like that:

Looks like it "swirls" at poles along Z axis. Diameter of the "dot" in the middle depends on 0.999 number from:

float3 tangentY = ((abs(N.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0));


Code for convolution is pretty standard:

uint ReverseBits(in uint bits)
{
bits = (bits << 16u) | (bits >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);

return bits;
}

float2 Hammersley(in uint i, in uint N)
{
return float2(float(i) / float(N), float(ReverseBits(i)) * 2.3283064365386963e-10);
}

float3 ImportanceSampleGGX(in float roughness, in float2 eta)
{
float phi = 2.0 * PI * eta.x;
float cosTheta = sqrt((1.0 - eta.y) / ((roughness * roughness - 1.0) * eta.y + 1.0));
float sinTheta = sqrt(1.0 - cosTheta * cosTheta);

float3 H = float3(sinTheta * cos(phi),  sinTheta * sin(phi), cosTheta);

return H;
}

float TrowbridgeReitzGGX(in float roughness, in float NdotH)
{
float a2 = roughness * roughness;
float denominator = (a2 * NdotH - NdotH) * NdotH + 1.0;

return a2 / (PI * denominator * denominator);
}

float3 ConvolveEnvironmentMap(in float linearRoughness,
in float3 V,
in SamplerState trilinearSampler,
in TextureCube<float4> environmentMap,
in float currentResolution,
in float activeMipsCountZeroBased,
in uint currentMip)
{
float3 N = V;

float3 tangentY = ((abs(N.z) < 0.999) ? float3(0.0, 0.0, 1.0) : float3(1.0, 0.0, 0.0));
float3 tangentX = normalize(cross(tangentY, N));
tangentY = cross(N, tangentX);

float3x3 W = float3x3(tangentX, tangentY, N);

float roughness = linearRoughness * linearRoughness;

float3 Integral = float3(0.0, 0.0, 0.0);
float Weight = 0.0;

const uint samplesCount = 32;
for (uint i = 0; i < samplesCount; i++)
{
float2 eta = Hammersley(i, samplesCount);
float3 H = mul(ImportanceSampleGGX(roughness, eta), W);
float3 L = 2.0 * dot(V, H) * H - V;
float NdotL = saturate(dot(N, L));
float NdotH = saturate(dot(N, H));
float LdotH = saturate(dot(L, H));
float pdf = DTrowbridgeReitzGGX(roughness, NdotH) * 0.25;
float sampleSolidAngle = 1.0 / (float(samplesCount0) * pdf);
float texelSolidAngle = (4.0 * PI) / (6.0 * currentResolution * currentResolution);
float environmentMapMipLevel = clamp(0.5 * log2(sampleSolidAngle / texelSolidAngle), 0.0, activeMipsCountZeroBased);

float3 Li = environmentMap.SampleLevel(trilinearSampler, L, environmentMapMipLevel).rgb;

Integral += Li * NdotL;
Weight += NdotL;
}

return Integral / Weight;
}


cbuffer ConvolutionData : register(b0)
{
...
}

SamplerState LinearClampSampler : register(s0);

TextureCube Input : register(t0);

RWTexture2DArray<float4> CurrentMipSlice : register(u0);

void cs_main(
uint3 groupID : SV_GroupID,
uint groupIndex : SV_GroupIndex)
{
const float2 uv = ((float2(dispatchThreadID.xy) + 0.5) * InvCubemapResolution) * 2.0 - 1.0;

const float3 V0 = normalize(float3(1.0, -uv.y, -uv.x));
const float3 V1 = normalize(float3(-1.0, -uv.y, uv.x));
const float3 V2 = normalize(float3(uv.x, 1.0, uv.y));
const float3 V3 = normalize(float3(uv.x, -1.0, -uv.y));
const float3 V4 = normalize(float3(uv.x, -uv.y, 1.0));
const float3 V5 = normalize(float3(-uv.x, -uv.y, -1.0));

CurrentMipSlice[uint3(dispatchThreadID.xy, 0)] = float4(ConvolveEnvironmentMap(V0, ...), 1.0);
CurrentMipSlice[uint3(dispatchThreadID.xy, 1)] = float4(ConvolveEnvironmentMap(V1, ...), 1.0);
CurrentMipSlice[uint3(dispatchThreadID.xy, 2)] = float4(ConvolveEnvironmentMap(V2, ...), 1.0);
CurrentMipSlice[uint3(dispatchThreadID.xy, 3)] = float4(ConvolveEnvironmentMap(V3, ...), 1.0);
CurrentMipSlice[uint3(dispatchThreadID.xy, 4)] = float4(ConvolveEnvironmentMap(V4, ...), 1.0);
CurrentMipSlice[uint3(dispatchThreadID.xy, 5)] = float4(ConvolveEnvironmentMap(V5, ...), 1.0);
}


Sample count is pretty low (32), but changing it to 64 doesn't really help. Also because I'm using FIS approach, it should look better than that, I think.

Appreciate any help, thanks!

Edited by xxxkxxx, 21 April 2017 - 01:19 PM.

### #2Tom Sloper  Moderators

Posted 21 April 2017 - 02:24 PM

locking thread at request of OP (solved)
-- Tom Sloper
Sloperama Productions
Making games fun and getting them done.
www.sloperama.com

Please do not PM me. My email address is easy to find, but note that I do not give private advice.

### #3frob  Moderators

Posted 21 April 2017 - 02:28 PM

(Unlocking so he can post his solution to the problem)

Check out my book, Game Development with Unity, aimed at beginners who want to build fun games fast.

Also check out my personal website at bryanwagstaff.com, where I occasionally write about assorted stuff.

### #4xxxkxxx  Members

Posted 21 April 2017 - 11:18 PM

Problem was solved by changing

float texelSolidAngle = (4.0 * PI) / (6.0 * currentResolution * currentResolution);
float environmentMapMipLevel = clamp(0.5 * log2(sampleSolidAngle / texelSolidAngle), 0.0, activeMipsCountZeroBased);


to

float texelSolidAngle = (4.0 * PI) / (6.0 * Mip0Resolution * Mip0Resolution);
float environmentMapMipLevel = max(0.5 * log2(sampleSolidAngle / texelSolidAngle) + 1.0, 0.0);