0

I am attempting to repurpose the heightmap shader example found here into one that will work with 32-bits of precision instead of 8. The work-in-progress code is on github: https://github.com/bgourlie/three_heightmap

The height map is being generated in .NET. The heights are within 0f...200f and converted into a 32-bit color value (Unity's Color struct) using the following method:

private static Color DepthToColor(float height)
{
    var depthBytes = BitConverter.GetBytes(height);
    int enc = BitConverter.ToInt32(depthBytes, 0);
    return new Color((enc >> 24 & 255)/255f, (enc >> 16 & 255)/255f, (enc >> 8 & 255)/255f,
            (enc & 255)/255f);
}

The color data is encoded as a png, with the result looking like this:

enter image description here

The vertex shader is taking this image data and coverting the RBGA values back to the original height value (using the technique answered in my question here):

uniform sampler2D bumpTexture; // defined in heightmap.js
varying float vAmount;
varying vec2 vUV;

void main()
{
  vUV = uv;
  vec4 bumpData = texture2D( bumpTexture, uv );
  vAmount = dot(bumpData, vec4(1.0, 255.0, 65025.0, 16581375.0));
  // Uncomment to see a "flatter" version
  //vAmount = dot(bumpData, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/160581375.0));

  // move the position along the normal
  vec3 newPosition = position + normal * vAmount;
  gl_Position = projectionMatrix * modelViewMatrix * vec4( newPosition, 1.0 );
}

The result is definitely messed up:

enter image description here

I can make it flatter by changing this line:

vAmount = dot(bumpData, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/16581375.0));

This will give me a much flatter image, which at least shows a nice outline of the generated terrain, but with an almost entirely flat plane (there is slight, albeit unnoticeable variation):

enter image description here

I assume I'm doing a few things wrong, I just don't know what. I'm not sure if I'm encoding the original float correctly. I'm not sure if I'm decoding it correctly in the vertex shader (the value I'm getting is certainly outside the range of 0...200). I'm also not very experienced in 3d graphics in general. So any pointers as to what I'm doing wrong, or generally how to achieve this would be greatly appreciated.

Again, the self contained work-in-progress code can be found here: https://github.com/bgourlie/three_heightmap

Community
  • 1
  • 1
w.brian
  • 16,296
  • 14
  • 69
  • 118
  • The noise in the `png` of the height map does definitely look wrong. Even when using all channels for the depth you should at least have a smooth gradient within one color and if you have noise it should not be that extrem. As of that I would suggest to narrow down the search to the part where you create the height map. I would guess you somewhere access uninitialized memory. – t.niese Feb 22 '15 at 20:52
  • I assumed the noise was the result of using the alpha channel (in doing so expecting some noise). Here's the resulting heightmap without using the alpha channe;: http://i.imgur.com/l3zcpcG.jpg. Admittedly, the gradients still don't look right considering the heights they're supposed to represent. – w.brian Feb 22 '15 at 21:00
  • Sorry I think I had a wrong assumption: The white dots are actually transparent pixels? Did the png originally had smooth transparency? Did you save it as a png-8 to show it here and the alpha channel was lost as of that? That would explain the white dots. If you used that exported image for the shader then you would need to check the PNG creation, if you can choose between 8 and 24. (Using a png with alpha channel might be a problem anyway as the compression could make problems at the areas where the image if fully transparent) . – t.niese Feb 22 '15 at 21:31
  • It is in fact a 32-bit PNG. Here's a picture of the PNG on top of a red background illustrating the alpha channel: http://i.imgur.com/iJS3Dqo.png. AFAIK, PNG uses lossless compression so the vertex shader should be operating on lossless, decompressed data. – w.brian Feb 23 '15 at 00:45
  • `255^3` is `16581375.0` and not 16 **0** 581375.0 – t.niese Feb 23 '15 at 01:49
  • Good catch, and fixed, but I'm pretty certain you're still right in that I'm encoding the image incorrectly. I got some ideas. Really appreciate your help. – w.brian Feb 23 '15 at 03:01

2 Answers2

1

If you encode a wide integer into the components of a RGBA vector it's essential that you turn off filtering so that no interpolation happens between the values. Also OpenGL may internally convert to a different format, but that should only reduce your sample depth.

Tommy
  • 99,986
  • 12
  • 185
  • 204
datenwolf
  • 159,371
  • 13
  • 185
  • 298
1

Your colour:

return new Color((enc >> 24 & 255)/255f, (enc >> 16 & 255)/255f, (enc >> 8 & 255)/255f,
        (enc & 255)/255f);

... contains the most significant byte of enc in r, the second most significant in g, etc.

This:

vAmount = dot(bumpData, vec4(1.0, 255.0, 65025.0, 160581375.0));

builds vAmount with r in the least significant byte, g in the next-least significant, etc (though the multiplicands should be 256, 65536, etc*). So the bytes are in the incorrect order. The flatter version:

vAmount = dot(bumpData, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/160581375.0));

gets the bytes in the correct order but scales the output values into the range [0.0, 1.0], which is probably why it looks essentially flat.

So switch the order of encoding or of decoding the bytes and pick an appropriate scale.

(*) think about it this way: the smallest number that can go on any channel is 1.0 / 255.0. The least significant channel will be in the range [0, 1.0] — from 0 / 255.0 to 255.0 / 255.0. You want to scale the next channel so that its smallest value is the next thing on that scale. So its smallest value should be 256 / 255.0. So you need to turn 1.0 / 255.0 into 256.0 / 255.0. You achieve that by multiplying by 256, not by 255.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Really appreciate the explanation. It's made me realize how and why I am encoding the height map in a horribly naive, wrong way. At least now once I get that right, I'll know how to get the ARGB values out correctly in the shader. Thanks again! – w.brian Feb 23 '15 at 03:57