SmoothNormals

Static utility class that computes averaged (smooth) normals for mesh vertices and stores them in the UV3 channel. Used by the Outline system to eliminate hard-edge artifacts at mesh seams where vertices share positions but have different normals. Results are cached per Mesh instance to avoid redundant computation.

Definition

Namespace: Paragon.Townskeep.OutlineSystem Assembly: Townskeep.dll

public static class SmoothNormals

Remarks

Why Smooth Normals?

When a mesh has split normals at edges (hard edges), the outline shader would render visible seams because adjacent vertices have different normal directions. By averaging normals across all vertices that share the same position and storing the result in UV3, the outline shader can read smooth normals for a continuous outline silhouette.

Baking Pipeline

Bake() processes two types of renderers:

  1. MeshFilter — Computes averaged normals by grouping vertices by position, averaging their normals, and storing the result as Vector3[] in UV3 via mesh.SetUVs(3, smoothNormals).

  2. SkinnedMeshRenderer — Stores empty Vector2[] in UV3 (smooth normals are not computed for skinned meshes, but UV3 must be populated for shader compatibility).

After writing UV3, if the mesh has multiple submeshes, an extra submesh is appended containing all triangles. This combined submesh is used by the outline materials to render the outline across the entire mesh as one draw call.

Caching

A static Dictionary<Mesh, Vector3[]> caches computed normals per mesh instance. IsBaked() checks the cache before processing, so shared meshes (e.g., mesh instancing) are only computed once.

Quick Lookup

Goal
How

Bake all meshes on a GameObject

SmoothNormals.Bake(gameObject)

Check if a mesh is already baked

SmoothNormals.IsBaked(mesh)

Methods

IsBaked

Checks whether smooth normals have already been computed and cached for this mesh.

Parameter
Type
Description

mesh

Mesh

The mesh to check

Returns: true if the mesh has been baked.

Bake

Discovers all MeshFilter and SkinnedMeshRenderer components in the hierarchy (including inactive children) and bakes smooth normals into UV3 for each unbaked mesh.

Parameter
Type
Description

gameObject

GameObject

The root GameObject to process

Pipeline for each MeshFilter:

Pipeline for each SkinnedMeshRenderer:

Internal Mechanism

GenerateSmoothNormals (private)

Groups all mesh vertices by position using LINQ GroupBy. For each group with more than one vertex, the normals are summed and normalized, then assigned back to every vertex in the group. Single-vertex groups retain their original normal.

CombineSubMeshes (private)

If a mesh has multiple submeshes (and fewer submeshes than materials), appends an additional submesh containing all mesh triangles. This allows the outline material to render across all submeshes in a single pass. Skipped for single-submesh meshes.

Cache

Initialized in the static constructor. Persists for the application lifetime. Each mesh's computed smooth normals are stored once and reused across all GameObjects sharing that mesh.

Common Pitfalls

circle-exclamation
circle-exclamation
circle-exclamation
circle-exclamation

See Also

  • Outline — the rendering component that calls Bake()

Last updated