If the splatmap is identical resolution to the heightmap, then it’s obvious: the coord in splatmap is the same as the coord in heightmap.
But e.g. if your heightmap is 1025 (i.e. coords 0…1024), and your splatmap is 820 (i.e. coords 0…819), then:
For every 5 height map samples, there are 4 splatmap samples. So … which one does Unity use? Does Unity round up? Round down? Round to nearest?
NB: since there is no Terrain.SampleSplatmap( Vector3 ) method, there’s no explicit way to verify this. So far, I’ve been making guesses and trying to describe them by looking at it visually (and is it the same on all platforms? WHO KNOWS!!)
This is with mesh-wireframe view enabled in Editor. What you’re seeing:
On the left:
33x33 Unity heightmap (Unity optimizes this down to 17x17 mesh verts)
32x32 Unity splatmap
Splatmap has a 1-pixel border of red, then a 1-pixel border of green, then blue center
The heightmap is sub-sampling the splatmap: the bottom left square has heightmap coords that are red, blue, red, and red. But instead of lerping red-to-blue, it samples the splatmap across the full quad
On the right:
33x33 Unity heightmap (as above … 17x17)
16x16 Unity splatmap (i.e. half-resolution)
Splatmap has a 1-pixel border of red, then a 1-pixel border of green, then blue center
The heighmap is now sampling 1-for-1 the splatmap coords, so you see what you’d expect, even though the splatmap has been defined as half the resolution
So, takehomes:
The splatmap-coord for a heightmap-coord is:
Find the X splatmap-coord less than heightmap-coord
Find the X splatmap-coord greater than heightmap-coord
Find the Y splatmap-coord less than heightmap-coord
Find the Y splatmap-coord greater than heightmap-coord
Bilinearly interpolate (double-lerp) between the four values you now have; this is your final splatmap-coord
If you want to store splatmap data for each heightmap coord
You must store FOUR splatmap values for every ONE heightmap value (the four above)
To restore the splatmap data for a single heightmap coord, you have to overwrite all FOUR surrounding splat values
Setting splat values for a height value ALWAYS corrupts neighbouring heightmap coords’ textures/splats, unless the splatmap is PRECISELY one less than the resolution of the heightmap (Unity’s default, but not enforced, and has no warnings if you change it)
A height map of 33x33 will produce a full resolution terrain mesh of 33x33 vertices if there’s any height in the height map. Try moving one “vertex” up to a height of 1 and you’ll see the entire terrain go up to the full resolution. If you’re still seeing lower than that then you’re viewing the terrain from far enough away that it’s dropping the terrain LOD. Try setting the terrain’s Pixel Error to 1.
The control map resolution (ie: the splat maps / alpha maps) are generally disconnected from the heightmap and rendered mesh resolution for greater flexibility, also because for world alignment you want the heightmap to be an odd resolution (33x33) so that each vertex of an evenly sized terrain falls nicely on the world grid, but since splat maps are textures they had to be power of two sizes to work on some older platforms (none of which Unity actually supports anymore).
The fallout of this is while the 33x33 heightmap and 16x16 control map resolution appears to have a single texel per vertex around the outside edge, if you try do draw a line down the center you’ll see this isn’t the case as the center vertex line is half way between two control map pixels. You’d need the control map resolution to match the heightmap resolution for this to be true, but due to the legacy nature of the current terrain system this isn’t possible.
To my surprise … there is (Unity2019) no limit on the alphamap resolution other than “must be greater than or equal to 16”.
So (not included above, but I tested further), the following are all valid (and work!):
height: 33x33, alpha: 32x32 (expected)
height: 33x33, alpha: 33x33 (not expected!)
height: 33x33, alpha: 99x99 (not expected)
…but anything you to do to height resolution is forceable converted by Unity to “largest POT+1 that is less than or equal to the number you attempted”
The alphamap appears to simply be interpolated across the region with no concern or worry. I guessed that was because the interpolation is being done 100% GPU side (hence: Unity doesn’t care what the texture size is, nor its relationship to the heightmap) … and that this is also the reason there is no “Terrain.SampleAlphamap()” method to match the Terrain.SampleHeight() method.
I may be misunderstanding your final paragraph, but … “due to the legacy nature of the current terrain system this isn’t possible.” seems to be incorrect today?
(NB: exactly what you write was what I believed to be true, and have habitually used in all projects - but I started with Unity Terrains in … 3.5, I think? … so maybe it’s a constraint that got relaxed recently?)
I checked this with Unity 2019.2.5f1 and the resolution of the control map (which sets the splat map resolution) always goes to the nearest power of 2 resolution, min 16x16, and the heightmap always nearest power of 2 + 1, min 33x33, thus they can never match. If there’s a version of Unity where this is fixed, then great!
And yes, the splat map is just a bilinear filtered texture sampled by the fragment shader on the GPU. The terrain mesh is setup so the UVs for the splatmap go from the center of the pixel in one corner to the center of the pixel in the other corner. There’s probably no way to get an interpolated sample in c# because no one every asked for one. It’d be easy enough to generate one by doing the interpolation yourself. That’s what I do (or rather, I just calculate the int position and use that).
The 16 vs 17 difference isn’t clear, so I did 25 as well to make it more obvious.
I don’t know what’s different in 2019.2.5, but it certainly works (worked?) fine in 2019.2.2
EDIT: to be clear … if you do a similar “set, then Assert” with heightmaps, the value returned by terrainData.heightmapResolution is NOT the same as the value you set - Unity does the conversion at the moment of setting the property; I would expect alphamapResolution to work the same (I thought it used to!)
EDIT2: again, to be clear … this is using same code as earlier post in this thread: Iterate across the alphamap coords, coloring red for 0 <= coord <1, green for 1 <= coord < 3, blue for coord >= 3. So: changing the alphamap resolution makes the blue take up more or less of the center, with the red/green borders shrinking or growing.
I’m setting the values from the inspector, so I would guess the inspector itself is enforcing the power of two sizes for the control maps then. I didn’t try setting them from script.