Home Technical Talk

Mini Tutorial: Baking seamless normal maps for glTF

I made this for a client recently, and it's generic enough that I can share here. So, why not?

Here are the source meshes, shaded and in wireframe:


When I open hexcube-lowpoly.glb in Babylon.js Sandbox (https://sandbox.babylonjs.com/), the normal map does not represent the beveled high-poly shape from hexcube-highpoly.glb, it has a lot of errors and seams. 


(I've attached both models, in glTF format, see the zip below)

This rendering result is because of a few issues:

1. The vertices need to be welded, or shared between all the triangles. When I import this GLB into 3ds Max (I'm not a Blender expert, so I use 3ds Max instead) each quad is a separate primitive, and all the vertices are unwelded. This is bad for normal mapping, because the rounded bevels won't shade correctly in the normal map. The model imports with 120 vertices, but it should only have 32 vertices.

2. When baking a normal map, if the model has hard edges (split vertex normals), then you must split the UVs for each hard edge. In this specific model, all the edges are hard, so that means each quad needs to be its own UV "shell" with spacing between them all. This will allows the baking tool to add padding between the shells, which then allows proper texture filtering for the drastically different colors on each side of each hard edge. Here is the fixed UV layout, next to the original UVs:


3. When you bake the normal map, you need to use averaged-ray projection. I don't know Blender all that well, so I don't know which settings to use there. Here's a visual example from 3ds Max of the projection type to use:


Here's more information about this issue (plus a lot more useful info!): https://polycount.com/discussion/107196/making-sense-of-hard-edges-uvs-normal-maps-and-vertex-counts/p1

Here are some different bakes, and the results in Max's viewport vs. in Babylon.js Sandbox. I used Mikkt space for baking, this is what glTF expects. 

Also, I'm using the Babylon.js "studio" HDR for my previews in 3ds Max, so it will more closely match the lighting in the Babylon.js Sandbox on the web. You can get that HDR here:

First, your original model. Lots of seams, this is from all three issues I mentioned above.


Next, I welded the vertices and re-baked. This is better, but there are still thin seams (red arrows), because the UV has no padding between hard edges. 


Then I split all the UVs so each quad is a separate UV shell. This is the best result, it has no seams.


Here is an additional test. I got rid of all hard edges in the low-poly, so all edges are smoothed (averaged vertex normals, instead of split vertex normals). 
This also has no seams when shaded, but it is not ideal because this causes a lot of gradients in the normal map, which makes it difficult for texture compression. This kind of map also tends to show more shading problems in other renderers which might not be as good a match to Mikkt space.


I hope this helps you! Let me know if you have any questions.

Replies

Sign In or Register to comment.