High quality Normals from terrain heightmap

Started by
2 comments, last by KaiserJohan 4 years, 5 months ago

I have a terrain based on CDLOD paper. It uses a single static mesh that is scaled & dynamically morphed to progress between LOD levels (marked by different colors)

I compute the world position of each vertex to sample the heightmap.

float2 GetTextureCoordinates( float3 worldPos )
{
	float2 texcoord = ( worldPos.xz - gWorldMin ) / ( gWorldMax - gWorldMin );
	texcoord = clamp( texcoord, 0.0f, 1.0f );

	return texcoord;
}

Currently I use a compute shader to generate a normal map like this

#ifndef TERRAIN_COMPUTE_NORMAL_HLSL
#define TERRAIN_COMPUTE_NORMAL_HLSL

#include "Constants.hlsl"
#include "Common.hlsl"

Texture2D gHeightmap : register( TEXTURE_REGISTER_EXTRA );
RWTexture2D<float3> gNormalTexture : register( UAV_REGISTER );

[numthreads(TERRAIN_NORMAL_THREADS_AXIS, TERRAIN_NORMAL_THREADS_AXIS, 1)]
void cs_main(uint3 groupID : SV_GroupID, uint3 dispatchTID : SV_DispatchThreadID, uint3 groupTID : SV_GroupThreadID, uint groupIndex : SV_GroupIndex)
{
	float3 normal = SobelFilter( gHeightmap, int3( dispatchTID.xy, 0) );
	normal += 1.0f;
	normal *= 0.5f;

	gNormalTexture[ dispatchTID.xy ] = normal;
}

#endif

The result looks promising (a snapshot of it):

I must be doing something wrong though because the results looks blocky and shit when sampled:

const int2 offset = 0;
const int mipmap = 0;
float3 normal = gNormalMap.SampleLevel( gLinearSampler, postMorphTexcoord, mipmap, offset ).rgb;
normal *= 2.0;
normal -= 1.0;

VertexOut ret;
ret.mNormal = mul( ( float3x3 )gFrameView, normal );
ret.mNormal = normalize( ret.mNormal );

postMorphTexcoord variable is the same I sample the heightmap with.

1. I am thinking I either need to add some “weight”/interpolation before sampling or try and average neighouring pixels (again, even though I did it when computing the normal map?)

2. I am primarily interested in quality of speed; is there anything else I could do in this regard?

Any input is welcome!

Advertisement

What does SobelFilter()? Is the normal in world space and did you normalize() it before storing and after sampling it?

Also I would recommend to use the mad HLSL intrinsic to scale and offset the normal:

cs_normal = mad(normal, 0.5f, -0.5f);
normal = mad(sampled_normal, 2.0f, -1.0f);

Next, I would use another packaging method for normals, i.e. a sphere map with a rg8ui buffer.

float3 SobelFilter( in Texture2D normalTexture, int3 texCoord )
{
	float h00 = normalTexture.Load( texCoord, int2( -1, -1 ) ).r;
	float h10 = normalTexture.Load( texCoord, int2( 0, -1 ) ).r;
	float h20 = normalTexture.Load( texCoord, int2( 1, -1 ) ).r;

	float h01 = normalTexture.Load( texCoord, int2( -1, 0 ) ).r;
	float h21 = normalTexture.Load( texCoord, int2( 1, 0 ) ).r;

	float h02 = normalTexture.Load( texCoord, int2( -1, 1 ) ).r;
	float h12 = normalTexture.Load( texCoord, int2( 0, 1 ) ).r;
	float h22 = normalTexture.Load( texCoord, int2( 1, 1 ) ).r;

	float Gx = h00 - h20 + 2.0f * h01 - 2.0f * h21 + h02 - h22;
	float Gy = h00 + 2.0f * h10 + h20 - h02 - 2.0f * h12 - h22;
	float Gz = 0.01f * sqrt( max( 0.0f, 1.0f - Gx * Gx - Gy * Gy ) );

	return normalize( float3( 2.0f * Gx, Gz, 2.0f * Gy ) );
}

It is indeed in worldspace and then transformed into view space when stored in the normal buffer. It looks correct in the texture aswell.

Surely the problem must be the way I am sampling or applying it though? I would've expected it to look more “smooth”

This topic is closed to new replies.

Advertisement