Burst happy recalculate normals

Mesh.RecalulateNormals() is great but does it on the mainthread.

Does anyone have this code or know how Unity does it? Same with mesh.RecalculateTangenets(). I just wanna feed in mesh info to a burst job and do it in there.

3 Likes

I’m in search of this exact thing too.

Personally if I’m ever generating meshes I can almost always generate the normal myself at the time of manipulation.

However, calculating a surface normals for a mesh is a pretty easy operation

https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal

The only trick is figuring out is if you need to flip it.

1 Like

@tertle I assumed this is what it was, but I am wondering if Unity does anything crazy like looking at all attached triangles and averaging the normal of these?

My solution to averaging normals is, most likely, not very smart, but here is what i can share. I’m new to job system. I can not be sure this code is somehow burst-friendly. It’s not reliable, because it does not calculate distances between all verts.

[BurstCompile]

    //first we sort all vertices by position and sum up their normals
    public struct SortNormalsJob : IJob
    {
        [NativeDisableContainerSafetyRestriction]
        public NativeHashMap<int3, float3> vertNormalSum;
        [NativeDisableContainerSafetyRestriction]
        public NativeHashMap<int, int3> vertIndex;
        [NativeDisableContainerSafetyRestriction]
        public NativeHashMap<int3, int> vertNumberOfVerts;

        [ReadOnly]
        public NativeArray<float3> normals;
        [ReadOnly]
        public NativeArray<float3> vertices;

        public float intScale;

        public void Execute()
        {
            for (int i = 0; i < normals.Length; i++)
            {
                //If the model is smaller then 1, we should scale all vertexes up
                float3 scaledVertex = vertices[i] * intScale;

                int3 vert = new int3(
                    (int)math.round(scaledVertex.x),
                    (int)math.round(scaledVertex.y),
                    (int)math.round(scaledVertex.z));
                          
                if (vertNormalSum.TryGetValue(vert, out float3 item))
                {
                    var currentval = vertNormalSum[vert];
                    vertNormalSum.Remove(vert);
                    vertNormalSum.TryAdd(vert, currentval + normals[i]);

                    var currentNumberOf = vertNumberOfVerts[vert];
                    vertNumberOfVerts.Remove(vert);
                    vertNumberOfVerts.TryAdd(vert, currentNumberOf + 1);

                    vertIndex.TryAdd(i, vert);
                }
                else
                {
                    vertNormalSum.TryAdd(vert, normals[i]);
                    vertNumberOfVerts.TryAdd(vert, 1);
                    vertIndex.TryAdd(i, vert);
              
                }
            }
        }
    }

    //then we average out new normals and apply them
    [BurstCompile]
    public struct ApplyNormalsJob : IJobParallelFor
    {
        [ReadOnly]
        public NativeHashMap<int3, float3> vertNormalSum;
        [ReadOnly]
        public NativeHashMap<int, int3> vertIndex;
        [ReadOnly]
        public NativeHashMap<int3, int> vertNumberOfVerts;

        [WriteOnly]
        [NativeDisableParallelForRestriction]
        public NativeArray<float3> normals;
        [WriteOnly]
        [NativeDisableParallelForRestriction]
        public NativeArray<float3> vertices;

        public void Execute(int i)
        {      
            int3 index = vertIndex[i];
            normals[i] = math.normalizesafe(vertNormalSum[index] / vertNumberOfVerts[index]);
        }
    }

@Lynxed I think you should do that in just one job. It takes quite a bit of time to queue up something on the main thread and if you can just get everything done inside a burst job you won’t have to transfer information between the two jobs.

Other than that it makes sense. You can also create the NativeHashMaps inside the burst job as temp guys instead of creating them on the main thread.

From what I’ve experienced feed everything into a single job and get it done unless you have to come back onto the main thread for something.

1 Like

Look at this now, I’ve moved away from creating allocations inside Jobs. If you have a lot of allocations inside a job, it can really extend the life of a job and throw large allocation creation. So now I just create a working NativeArray and use that every round. Seems to work well.

1 Like