# Normal Map & AO Baker (high → low poly) > Bake tangent-space normal and ambient-occlusion maps from a detailed mesh onto a low-poly OBJ. Upload a detailed high-poly mesh and a low-poly mesh with UVs (or let the tool unwrap it). Every texel casts a ray from a cage around the low-poly surface to the high-poly mesh: the hit normal becomes a tangent-space normal map with MikkTSpace-style tangents (OpenGL or DirectX green), and seeded hemisphere rays measure ambient occlusion. Download both maps as PNG together with a report of coverage, misses and mean normal. Runs on the same engine as Renderloom Sculpt Lab. Part of usefulweb.site (https://usefulweb.site/). Part of [Renderloom](https://renderloom.usefulweb.site/) (ownerApp: renderloom). Results are deterministic calculations from the inputs and the listed assumptions; the API returns exactly what the web page shows. ## Use it - Human interface: https://renderloomnormalmapbaker.usefulweb.site/ (interactive tool at https://renderloomnormalmapbaker.usefulweb.site/tool) - JSON API: POST https://renderloomnormalmapbaker.usefulweb.site/api/v1/calculate with a JSON object of inputs (schema: https://renderloomnormalmapbaker.usefulweb.site/api/v1/tool) - OpenAPI: https://renderloomnormalmapbaker.usefulweb.site/openapi.json - MCP (Streamable HTTP): https://renderloomnormalmapbaker.usefulweb.site/mcp - tool `renderloom_normal_map_baker` ## Inputs - high (file): High-poly mesh (STL, OBJ or PLY) - The detailed mesh, for example a sculpt exported from Renderloom Sculpt Lab. - low (file): Low-poly mesh (OBJ with UVs, or STL/PLY to unwrap) - An OBJ with vt coordinates. Without UVs the mesh is unwrapped automatically if allowed below. - autoUnwrap (boolean): Unwrap the low-poly mesh when it has no UVs - size (select): Map size (px) - maps (select): Maps - space (select): Normal space - convention (select): Green channel - cage (number): Cage distance (% of the model size) - How far above and below the low-poly surface rays search for the high-poly mesh. - aoSamples (integer): Occlusion rays per texel - aoDistance (number): Occlusion distance (% of the model size) - seed (integer): Random seed - padding (integer): Edge padding (px) ## Method - High-poly mesh: welded, oriented, angle-weighted vertex normals, binned-SAH BVH. Low-poly mesh: OBJ texture coordinates per corner; meshes without UVs are unwrapped with the renderloom-uv-unwrap engine when allowed. Low-poly normals are angle weighted over vertices that share a position, so UV seams keep one normal. - Tangents: per-face tangent and bitangent from the UV derivatives, projected into the plane of the vertex normal, angle weighted and averaged over corners with the same vertex, UV and handedness; the bitangent sign is kept per corner. Decoding (as in Blender, glTF viewers, Unity and Unreal): normalise interpolated normal and tangent per pixel, bitangent = sign × cross(normal, tangent). - Each texel centre inside a UV triangle casts one ray from the cage (surface point + cage distance × normal) along −normal over twice the cage distance; the first high-poly hit gives the interpolated high-poly normal, expressed in the texel’s tangent frame and encoded as RGB = 0.5 + 0.5 × (x, y, z). DirectX maps store −y. - Ambient occlusion: cosine-weighted hemisphere rays around the high-poly normal from the hit point, seeded per texel (same seed, same map); a ray that hits anything within the AO distance counts as blocked; AO = open fraction. - Texels outside the layout are filled by padding rounds of neighbour averaging, then with a flat normal and white AO. Maps are 8-bit RGBA PNG. Budget: map size² × (1 + AO rays) ≤ 16.7 million rays, at most 300000 high-poly and 60000 low-poly triangles. ## Limits - Tangents follow the published MikkTSpace rules but are an independent implementation; check the maps in your target engine. Both meshes must share the same position and scale. Rays that miss the high-poly mesh keep the low-poly normal and full AO — the report counts them.