Sign in to follow this  
xxxkxxx

Specular convolution problem.

Recommended Posts

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):

[attachment=35679:bug.png]

In final rendered image looks like that:

[attachment=35680:bug1.PNG]

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;
}

Here's calling compute shader:

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

SamplerState LinearClampSampler : register(s0);

TextureCube Input : register(t0);

RWTexture2DArray<float4> CurrentMipSlice : register(u0);

[numthreads(16, 16, 1)]
void cs_main(
	uint3 groupID : SV_GroupID,
	uint3 dispatchThreadID : SV_DispatchThreadID,
	uint3 groupThreadID : SV_GroupThreadID,
	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.

[attachment=35681:bug2.PNG]

Appreciate any help, thanks!

Edited by xxxkxxx

Share this post


Link to post
Share on other sites

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);

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this