Nanite landscape switches to non-nanite

The “misalignment” is actually expected : landscape components and sub-sections have duplicate vertices and edit layers merge operates on un-duplicated data but the render targets are still allocated at full resolution (i.e. multiples of power-of-2) because of the final vertex “re-duplication” step. This is why you see these lines in the bottom right at this step of the merge, they contain garbage and are simply not used until the very end of the merge

Salut Jonathan!

Thanks for the fix! I applied it but it’s not fixing everything.

Today our level artists entered a lot of bugs with the edges of some landscape proxies that have huge gaps or spikes. There are no splines on those edges. Again depending on what proxies I load, the result is different. I tried using landscape.EditLayersLocalMerge.Enable=0 and it removed the gap (probably because it cause a rebuild), but if I reload the proxies the gap can return (or not, it’s random). The bugs are also present in packaged builds. Here’s some screenshots. The first screenshot is the bug. You can see the setup on the left. The second screenshot is if I hide the Detail layer. It looks like the Detail layer is not applied all the way to the edge. (this is with 43814550, 43856326 and the new spline fix)

[Image Removed] [Image Removed]

Does that mean the garbage is copied back to CPU and used in the hash computation? ULandscapeTextureHash::CalculateTextureHash64 and ULandscapeTextureHash::DoesTextureDataChangeExceedThreshold seem to use the whole size to compute the hash. That could explain the hash being always different?

No, because the data is expanded back to the multiple of power-of-2 size before the readback, i.e. at the end of the merge, for each batch, we re-duplicate subsection and component borders and then copy to individual readback textures and then read this back on the CPU. The hash is therefore computed on the final (per-component) heightmap which is a power-of-2 texture.

What could explain this is if there is a discrepancy in the source data (i.e. in one or several of the edit layers) of 2 neighboring proxies. If the user only loads a single proxy, we disable the sculpt brush when reaching the border in order to prevent the situation (it becomes red) but a user could have worked with multiple proxies loaded, modified the border (in that case, the brush would not be restricted from sculpting, since it’s now not modifying the border of what’s loaded), and then only saved or submitted one of the 2 proxies.

In such a situation, when we run the landscape edit layers merge, we only pick the border from one of the 2 proxies in order to “solve the ambiguity” and we now do this in a consistent way with those 2 changes :

CL 43814550

CL 43856326

, while before, it would randomly pick the pixel values either from one or the other. So for a given set of proxies being loaded, we at least always get the same result.

However, if there is a discrepancy in the source data, the end result remains dependent on which proxy is loaded. For example, say you have a component A with a value on the right border of 42 for a given pixel and next to that component lies another component B (i.e. one that is supposed to share its values from its left border with the right border of component A), that has, for the corresponding pixel on its left border, a value of 43, then if both A and B are loaded, the merge algorithm, when it runs, will favor the component on the left, hence it will work with a source value of 42 and will use this to product its final height value. However, if only B is loaded, then the value that will be used for the pixel will be B’s, i.e. 43, and the final height value will end up being different…

In such a situation, the fix is to find which edit layer(s) contain(s) the discrepancy and fix the border values by using the smooth or the flatten tool, for example. This will ensure that the source data once again respects the contract of equality on the borders of proxies and will ensure consistency regardless of which proxy is actually loaded.

We have seen such situations in the past, which is why I’m leaning towards this. We have a feature coming up to detect such discrepancy, warn the user and present an “auto-fix” button so that these problems (which are unfortunately impossible to completely prevent because of the user can choose to save, or even because of a file operation, like an un-submitted file, a reverted file, etc.) can be diagnosed and repaired much more efficiently than right now, where it’s just guest work. But we haven’t gotten to work on this just yet so it won’t be available until at least UE5.8.

Please check for the border values on the source data (i.e. the edit layers) of the problematic proxies and see if this is what’s happening. One way to do this is to have the 2 proxies loaded, then RenderDoc-capture (you can use landscape.RenderCaptureLayersNextHeightmapDraws 1 and then landscape.EditLayersLocalMerge.Enable 2 to force a merge to run, or landscape.ForceLayersFullUpdate, if you run with UE5.7, since the former has been removed) and look for the CopyEditLayer -> ScratchRT… step. This is where we “assemble” the heightmaps from the various components/proxies into one “atlas texture”, while also removing the duplicate pixels from one of the 2 neighboring components. So the input PS resources to that stage are actually the individual heightmaps. You can therefore find the 2 heightmaps from the neighboring heightmaps and check that their “common” border has the same value on each pixel.

Let me know how it goes

Cheers,

Jo

So I did the capture and I found that the base layer was different on that edge. That explains why our artist couldn’t fix it by only smoothing the details layer. If I smooth the base layer (even with a tool strength of 0) I can see that the edge is now the same values. Then I can smooth the details layer normally to remove the gap. Is there a way to just save the data to the original heightmap, without having to modify it? Smoothing with a tool strength of 0 seems to work, but I feel that I could just force a resave of all the pixels. That might be what the auto-fix will do? For now this should at least help us fix the specific bugs.

But I still have trouble with Nanite turning itself off and a warning notification saying I need to rebuild the landscape (the initial problem). Let’s say I have 9 landscapes proxies loaded (3x3). I do the smoothing of the edges of the middle proxy to fix it, then I save everything. If I unload the surrounding proxies, Nanite turns off and I have to rebuild the proxy. Then if I rebuild it, save, unload, reload, Nanite turns off again and I get the warning. No matter what, I can’t get Nanite to stick. Here’s some screenshot: 3x3 landscapes with the middle proxy edges fixed, unload all except middle (Nanite turns off, warn about rebuild), middle is rebuilt and saved, load one neighbor (Nanite turn off on both). I’m lost. :expressionless:

[Image Removed]

Hi again,

Sorry to see that this didn’t fully fix the issue. Unfortunately, there’s no way currently to detect or fix the problem apart from touching up the edges. The most straightforward (and transparent) way to do this will be to implement that auto-fix feature I mentioned earlier. Smoothing with a strength of 0 seems like a good workaround, though, just make sure that you do this for every edit layer, since each one of them has to be consistent with its neighbors.

If Nanite gets invalidated when changing the loaded region, I suppose there’s still something that is non-deterministic in your landscape setup. Would you be able to send us a minimal project with this repro (if it’s too confidential, you might be able to just export the heightmaps of every edit layer in order to re-create in a minimal project with just the landscape)?

If the invalidation occurs after reading back the results from the edit layers merge on the GPU, then RenderDoc remains the best way to investigate. Just use landscape.RenderCaptureLayersNextHeightmapDraws 1 before loading/unloading in order to capture what’s going on. Inspecting 2 different captures should point to where the differences are introduced. Another tool that is particularly useful when investigating such situations is the CVars : landscape.DumpHeightmapDiff 1 and landscape.DumpDiffDetails 1. The former will dump png files with the modified heightmaps (before, after) any time a merge runs (in the project’s Saved/LandscapeLayers folder) while the latter will also dump a txt file with precise details (which pixel has been modified, how many pixels are different, etc.) This can help spot exactly the pixel(s) where the problem occurs, and then this makes analyzing the RenderDoc captures much easier.

Also, for completeness, it doesn’t seem to be the case on this screenshot, but it’s possible that the discrepancy exists at the weightmap level in one of the edit layers. These also share pixels on the borders so any discrepancy will lead to the same situation. You can diagnose this using landscape.DumpWeightmapDiff 1. It shouldn’t affect Nanite, though (unless the discrepancy occurs on the Visibility layer in the sculpt panel, which is handled as a weightmap internally and does affect the Nanite mesh). It’s still worth reproing with both landscape.DumpHeightmapDiff and landscape.DumpWeightmapDiff on (as well as landscape.DumpDiffDetails) in order to find out where this comes from.

One last (unlikely) possibility is that Nanite gets invalidated for another reason altogether than the merged heightmaps or weightmaps changing. You can investigate this by debugging ALandscapeProxy::InvalidateNaniteRepresentation, which relies on a hash of the data participating to the Nanite mesh (see ALandscapeProxy::GetNaniteContentId) and see, for a given proxy, where the difference comes from.

Let us know how it goes,

Cheers

Jonathan

I did some test with 2 landscape proxies with those cvars enabled. Here’s the diff when I have 1 proxy loaded and then load the other. The first row is different and if I understand correctly that’s expected. But some pixels on the second row are also different. The blue channel doesn’t have the right value. Looking at renderdoc I think this comes from the GenerateNormals pass. Is it possible that the generation of normals is fetching too far?

[Image Removed]

Hi David,

The heightmaps store the 16bits height on the RG channels and the (packed) normal on the BA channels. The fact that the BA channels differ is fine as the diff detection is only based on height data (see ULandscapeTextureHash::CalculateTextureHash64). It is this way because the normals computation depends on the pixel’s own height value and of 6 of its neighbors (see GenerateNormalsPS in LandscapeEditLayersHeightmaps.usf). Since, on the edges, these neighbors can be present or absent depending on whether the neighboring proxy is loaded, we simply don’t take into account the normals at all in the texture’s hash computation, because they will always differ depending on which proxy is loaded. In order to compensate for this, we perform a runtime patching of the edge normals upong streaming the mips in the cooked game (see ULandscapeHeightmapTextureEdgeFixup).

The fact that the R or G channel (i.e. the height) differs, even on the edge, is likely the source of the problem, though. It indicates that when it comes to pure heights, the result of the merge operation differs, depending on which proxy is loaded, and that probably comes from the fact that the source height data of one of the edit layers on both sides of this edge still differs.

Again, if we had a detection mechanism for this, it would likely be trivial to fix, data-wise, but for now, you need to find the discrepancy in one of the edit layers on both proxies (there’s likely at least one at X = 145, from what I see on this screenshot) and fix it.

Please let me know if you need more information. Also, happy to debug if you have a repro in a test project.

Cheers,

Jonathan

I think I get it now. :stuck_out_tongue: I loaded 3x3 proxies, smoothed the edges of the middle one, rebuild, resave, and then unload everything except the middle. I still had the warning but there was no more diff textures generated. So I turned on the weightmap diff and I saw there was some problems there. I redid the same thing (smooth all the paint layers for the middle landscape) and then it worked! No more warning if I load/unload the middle proxy. If I load the other one I still do, but the diff is not on the edge with middle, only with others, which is expected.

I then tried the same thing with another proxy. This time there was a small part that was always different, no matter how many times I tried to smooth it. After some time it worked, but I was thinking that maybe a spline was doing that? Do the splines affect the weights?

The diff output is really helpful. I might try to hack a tool to fix all our landscapes, but for now I think I know how to fix them manually.

Thanks!

The splines can affect the weights, but only if a layer name is specified in the spline points or segments.

I’m glad you found out the problem. We will try to get this “detect discrepancies on borders + auto-fix” tool done in the next months, because I can see how annoying that can be to debug such issues, which are unfortunately likely to be introduced, no matter the amount of tooling we do, since that comes down to file management and what the user decides to/can check out and/or submit.

Cheers,

Jonathan