It took me a while to diagnose what was going on here but I think I’ve figured out what the problem is, I’m just not sure how to go about fixing it. I’m working on a streaming clipmap terrain where a large heightmap is split into a grid of smaller heightmaps, then at runtime I load in a (at the moment 11×11, but it’s configurable) grid of heightmap chunks into a 2D texture array, along with a small layer index map that I can sample at chunk coordinates (x, y) to determine which array layer that chunk’s heightmap is stored. Layers become recyclable when the camera moves out of range, and new chunks get loaded in.

So far the heightmapping works perfectly, there are no gaps in the terrain. However now I have added normal mapping in using the same method (another identically sized 2D texture array using the same layer indexes as the heightmap array for simplicity), I’ve come across an issue that happens with polygons on the outer edges of each chunk.

In the screenshots above the axis helper is placed exactly on `(x, z) = (512.0, 512.0)`

(Y is "up"). Each square of terrain vertices at the lowest clipmap resolution is always equal to 1 pixel of heightmap and normal map. This makes determining vertex height a simple matter of using `texelFetch`

. I’ve added a variable called `IVS`

(intra-vertex spacing) to control how many world units each pixel represents. In all my screenshots I’m using a 32×32 grid of 1024×1024 heightmaps with an IVS of 0.5, so each chunk is 512u and 1024 pixels.

Unfortunately if I colour the terrain based on normal map coordinates I can see the problem (I am pretty sure) is on the edge squares. For the squares on the chunk edges the face of the poly is shaded from tex coordinate `1023/1024`

to `0.0`

, rather than from `1023/1024`

to `1024/1024`

/`1.0`

, or that’s at least what it looks like from this:

**NOTE: in this screenshot, 511 should be 511.5. Apologies.**

Essentially because the texture coordinates for a vertex at the "start" (top left) of a chunk are always `0,0`

, at no point do any vertices get assigned `1,1`

. Is there a way I can tell OpenGL that I want world coordinates `512,512`

to be texture coordinates `1,1`

when shading the triangles between it and `511.5,511.5`

, and `0,0`

for the ones on the other side?

In case my analysis was wrong or my explanation wasn’t great here’s my shader code. Maybe there is something easy I have missed in here, but from what I can tell the numbers work:

`//Vertex shader for terrain. #include <attributes> uniform mat4 ModelViewMatrix; uniform mat3 NormalMatrix; uniform mat4 MVP; uniform float Scale; uniform vec2 Offset; uniform mat4 RotMatrix; uniform float IVS; uniform float MinHeight; uniform float MaxHeight; uniform int ChunkSize; uniform int MapWidth; uniform int MapHeight; uniform sampler2DArray Heightfield; uniform sampler2D LayerIndexMap; out float Height; out vec3 NormalMapCoords; void main() { // Get the XZ world coordinates of the vertex. vec2 vertXZ = Offset + (RotMatrix * vec4(VertexPosition, 1.0)).xz * Scale; float chunkWorldSize = ChunkSize * IVS; // Get the chunk xy and the layer index to sample from. int chunkX = int(floor(vertXZ.x / chunkWorldSize)); int chunkY = int(floor(vertXZ.y / chunkWorldSize)); float layerIdx = texelFetch(LayerIndexMap, ivec2(chunkX, chunkY), 0).r * 256.0; // Scale the vertex coords to the chunk image size. vec2 pixelXY = vec2( mod(vertXZ.x, chunkWorldSize) / IVS, mod(vertXZ.y, chunkWorldSize) / IVS ); // if IVS = 0.5 and vertXZ = 511.5, then pixelXY = 1023 float sampledHeight = texelFetch(Heightfield, ivec3(pixelXY.x, pixelXY.y, layerIdx), 0).r; NormalMapCoords = vec3( pixelXY.x / float(ChunkSize), pixelXY.y / float(ChunkSize), layerIdx ); float heightMod = int(chunkX >= 0 && chunkX < MapWidth && chunkY >= 0 && chunkY < MapHeight); sampledHeight = sampledHeight * heightMod; Height = ((MaxHeight - MinHeight) * sampledHeight) + MinHeight; vec3 finalPos = vec3(VertexPosition.x, Height, VertexPosition.z); // Set the vertex position. gl_Position = MVP * mat4(1.0) * vec4(finalPos, 1.0); } `

`//Fragment shader for terrain. uniform vec3 SunVec; uniform sampler2DArray NormalMap; uniform int MeshType; //TODO debug uniform int RotIndex; //TODO debug in float Height; in vec3 NormalMapCoords; out vec4 fragColor; // All components are in the range [0…1], including hue. vec3 hsv2rgb(vec3 c) { vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } void main() { //vec3 baseCol = vec3(hsv2rgb(vec3(106.0 / 255.0, 1, 1))); // Flat colour. //vec3 baseCol = vec3(hsv2rgb(vec3(Height, 1, 1))); // Colour by height. vec3 baseCol = vec3(hsv2rgb(vec3((NormalMapCoords.x + NormalMapCoords.y) / 2.0, 1, 1))); // Colour by normal map coords. vec3 ambient = vec3(0.1, 0.1, 0.1); //TODO use a combination of colour control maps and daylight in-shadow light intensity to calc this (col * intensity) vec2 normalXZ = texture(NormalMap, NormalMapCoords, 0).rg; float normalY = 1.0 - (normalXZ.x + normalXZ.y); float sunlight = max(0.0, dot(normalize(vec3(normalXZ.x, normalY, normalXZ.y)), normalize(SunVec))); vec3 finalCol = (ambient + sunlight) * baseCol; fragColor = vec4(finalCol, 1.0); } `

The normal map is using `LINEAR`

for both filters and `CLAMP_TO_EDGE`

for S and T wrapping, and I should also point out a chunk is not a single mesh. The clipmap follows the camera around and covers multiple chunks.