Hi,
While rendering to terrainData.heightmapTexture I discovered that writing 1.0f to pixels doesn’t result in terrain of maximum height (as specified in “Terrain Height” inspector field) but 0.5 does (1.0 is twice that and not available for manual brush edits).
Seems odd/surprising but I expect there is sensible reason behind it. Can sb explain this behavior?
(image shows terrain after rendering a sine wave (0.0-1.0 range) to it. I was using a compute shader > Graphics.CopyTexture > terrainData.DirtyHeightmapRegion path)
My guess so far is that it has something to do with R16_UNORM format, an integer type ranging from 0 to uint16.maxvalue, and the way how it’s being converted to/from float32 on GPU results in this halved range (??)
For CPU this data is formatted as unsigned 16bit integer (range 0 to 65535). But for GPU it’s R16_UNORM where UNORM describes hardware conversion: unsigned & normalized (ie: 0.0-1.0 on read/write as a float).
From what I can deduct - in order for this conversion to go astray like this it must be (wrongly?) cast to signed int16 just before GPU conversion does the rest - treating 32767 as 1.0 and not 0.5 (?). But … that would mean there is bug somewhere in the pipeline and I don’t wan’t to jump to this conclusion too hastily.
I’ve reported this as a bug about a month ago. There is more to this than just a false limit, the heightmap’s position and collider itself starts behaving wrongly if you check out the video.
For now i would suggest to divide the heightmap data by 2 if you are looking to import heightmaps adequately.
I was loosing my mind over this - no useful documentation, not even one code sample anywhere on the internet (I don’t mean brushes but workflow for rendering to heightmap directly outside editor), any proofs this api even works.
Well, at least we know it is a dead end now for now.
This is correct. The heightmap implementation itself is signed but is treated as unsigned when rendering so we only have half the precision available to use for height values. That’s why all of our Terrain painting shaders clamp the returned value between 0f and .5f so that we don’t end up writing signed values into the heightmap. If you were to put in values greater than .5, you’ll see the Terrain surface “wrap” to negative height values. I can’t say why this was done but it probably has stayed this way because it would take a lot of code changes to make either of them signed or unsigned to match.
The values are normalized so that we can get the most precision we can out of the .5f for a given Terrain’s max height. 0 being a world height offset of 0 and .5f being terrain.terrainData.size.y (the max height)
@wyatttt so could you tell me how’d go for more precision if i use GetHeigts(worldpos.z,worldpos.x,terrain width, terrain heights)? because if i use sampleheight, it’s completly a diffrent result…
So, if a user paints a value in instanced mode, it will clamp at 0.5 and render correctly, but when you turn off instancing, the parts of the terrain that are at the ceiling will suddenly wrap to the floor. And only halving half the precision is, well, not great.
Unfortunately the current state of Draw Instancing is not great. I’d love to switch to requiring it, but I doubt I’ll be able to do that any time soon. First, as long as tessellation doesn’t work with Draw Instanced in Surface Shaders, I have to support not having Draw Instanced on as well. Second, when draw instanced is disabled, TerrainData.normalmapTexture will return null. This means that to access the normal map in a shader, I have to generate my own - which is not hard, but it’s extra data per terrain, and I have to have the user manually update it any time the terrain is changed. When some of my users are using 50 terrains, it can add up to a lot of memory quick.
Either Draw Instance should always be able to be used, or I should always be able to get the Height map and normal map textures should I need them (they could be generate on request if the terrain isn’t using them?). Right now, I end up having to duplicate a ton of data when I need height/normal data access via shaders, such as for my terrain blending or dynamic streams features, because I don’t know if that data will be available or not, and the user could toggle something at any time.