Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by VenusianPioneer854

How can I correctly compute vertex normals from a glTF positions buffer using JavaScript?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm attempting to compute vertex normals from a glTF positions buffer using the cross product method in JavaScript, but most of the computed normals end up as (0, 0, 0).

Below is my current implementation:

JAVASCRIPT
const vertices = new Float32Array( positionsBuffer, meshData.positions.byteOffset, meshData.positions.byteLength / 4 ) const indices = new Uint16Array( indexBuffer, meshData.indices.byteOffset, meshData.indices.byteLength / 2 ) const normals = new Float32Array(vertices.length) for (let i = 0; i < indices.length; i += 9) { const vert0 = vec3.create( vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]] ) const vert1 = vec3.create( vertices[indices[i + 3]], vertices[indices[i + 4]], vertices[indices[i + 5]] ) const vert2 = vec3.create( vertices[indices[i + 6]], vertices[indices[i + 7]], vertices[indices[i + 8]] ) // p = cross(B-A, C-A) const normal = vec3.normalize( vec3.cross( vec3.subtract(vert1, vert0), vec3.subtract(vert2, vert0) ) ) normals[indices[i]] += normal[0] normals[indices[i + 1]] += normal[1] normals[indices[i + 2]] += normal[2] normals[indices[i + 3]] += normal[0] normals[indices[i + 4]] += normal[1] normals[indices[i + 5]] += normal[2] normals[indices[i + 6]] += normal[0] normals[indices[i + 7]] += normal[1] normals[indices[i + 8]] += normal[2] } for (let i = 0; i < indices.length; i += 3) { const normalizedNormal = vec3.normalize( vec3.create( normals[indices[i]], normals[indices[i + 1]], normals[indices[i + 2]] ) ) normals[indices[i]] = normalizedNormal[0] normals[indices[i + 1]] = normalizedNormal[1] normals[indices[i + 2]] = normalizedNormal[2] }

I expected the vertex normals to look similar to this image:

screenshot 1

But the actual result is this:

screenshot 2

I have already tried adjusting the winding order and playing with normalization (although the normals are being normalized in the shader), but the problem persists. The math appears correct, and since the tangents generated using a similar approach work fine, it seems that the accumulation step (e.g., normals[indices[i]] += normal[0]) is not functioning as expected. Any suggestions for resolving this issue?

FYI: Rendering is handled with WebGPU, and the shader code for normals is identical to that for tangents. The glTF model is exported using the Khronos Blender glTF 2.0 exporter.

2

Comments 0

Answers 2

Views 50

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is an explanation with a couple of issues that usually come up when generating normals from an indexed positions buffer.


1. Wrong Index Increments (and Indexing Into the Normals Array)

Your position buffer is a flat array of numbers (x, y, z, x, y, z, …) and likewise your normals array is organized the same way. In glTF the indices array is made of vertex indices (each triangle uses 3 indices), so the loop should increment by 3, not 9. In your code you’re assuming that each triangle will use 9 consecutive indices:

JS
for (let i = 0; i < indices.length; i += 9) { // ... process one triangle }

That index stride is too high. Instead you should loop like this:

JS
for (let i = 0; i < indices.length; i += 3) { // ... process one triangle }

Also, when you add a computed normal to a vertex in the normals array, remember that your vertices array is flat. If an index value is "a", then the position for that vertex is at indices
a*3, a*3+1, a*3+2. Therefore, when accumulating normals you should do:

JS
normals[a*3] += normal[0]; normals[a*3 + 1] += normal[1]; normals[a*3 + 2] += normal[2];

Find and replace every place where you index the normals array with the raw index from the indices buffer.


2. Using gl-matrix: vec3.create vs. vec3.fromValues

If you’re using gl-matrix, note that vec3.create() returns a zero vector. To create a vector from values you should use vec3.fromValues(x, y, z). For example, change:

JS
const vert0 = vec3.create( vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]] );

to

JS
const vert0 = vec3.fromValues( vertices[indices[i] * 3], vertices[indices[i] * 3 + 1], vertices[indices[i] * 3 + 2] );

Make sure that you multiply the index by 3 since the positions are stored as x, y, z in a flat array.


Revised Code Example

Below is an adjusted version of your loop that takes the above points into account. (Note: this example assumes you’re using gl-matrix for vector operations.)

JS
// Create arrays from buffers (positions and indices assumed to be provided) const vertices = new Float32Array( positionsBuffer, meshData.positions.byteOffset, meshData.positions.byteLength / 4 ); const indices = new Uint16Array( indexBuffer, meshData.indices.byteOffset, meshData.indices.byteLength / 2 ); // The normals array must align: three floats per vertex. const normals = new Float32Array(vertices.length); // Loop over each triangle (3 indices per triangle) for (let i = 0; i < indices.length; i += 3) { // Get the three vertex indices for a triangle const i0 = indices[i]; const i1 = indices[i + 1]; const i2 = indices[i + 2]; // Retrieve vertex positions from the flat array const vert0 = vec3.fromValues( vertices[i0 * 3], vertices[i0 * 3 + 1], vertices[i0 * 3 + 2] ); const vert1 = vec3.fromValues( vertices[i1 * 3], vertices[i1 * 3 + 1], vertices[i1 * 3 + 2] ); const vert2 = vec3.fromValues( vertices[i2 * 3], vertices[i2 * 3 + 1], vertices[i2 * 3 + 2] ); // Compute the normal of the face: p = cross(B-A, C-A) // It's important to subtract correctly: out = (vert1 - vert0) x (vert2 - vert0) const edge1 = vec3.create(); const edge2 = vec3.create(); vec3.subtract(edge1, vert1, vert0); vec3.subtract(edge2, vert2, vert0); const faceNormal = vec3.create(); vec3.cross(faceNormal, edge1, edge2); vec3.normalize(faceNormal, faceNormal); // Accumulate the computed face normal to the vertices of the triangle normals[i0 * 3] += faceNormal[0]; normals[i0 * 3 + 1] += faceNormal[1]; normals[i0 * 3 + 2] += faceNormal[2]; normals[i1 * 3] += faceNormal[0]; normals[i1 * 3 + 1] += faceNormal[1]; normals[i1 * 3 + 2] += faceNormal[2]; normals[i2 * 3] += faceNormal[0]; normals[i2 * 3 + 1] += faceNormal[1]; normals[i2 * 3 + 2] += faceNormal[2]; } // Normalize the accumulated normals for each vertex for (let vi = 0; vi < vertices.length / 3; vi++) { const normal = vec3.fromValues( normals[vi * 3], normals[vi * 3 + 1], normals[vi * 3 + 2] ); vec3.normalize(normal, normal); normals[vi * 3] = normal[0]; normals[vi * 3 + 1] = normal[1]; normals[vi * 3 + 2] = normal[2]; }

Summary

  • Use correct index strides: Process 3 indices per triangle and adjust the normals array indices by multiplying the vertex index by 3.
  • Create vectors correctly: Use vec3.fromValues() to initialize vectors with positions (multiply indices by 3 when reading from the flat buffer).
  • Ensure proper accumulation: Normalize the resulting normals after adding all the face normals.

With these corrections, you should see the expected vertex normals rather than mostly (0, 0, 0) values.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by CosmicAstronaut423 1 month ago

0

Just to clarify, the glTF files produced by the Blender exporter already include the normals, and indeed they may be artist-adjusted normals that are of better quality than the ones that you're attempting to compute with this code. Is there a reason you're not using the supplied normals?

That aside, this code does have flaws. You're using 3 different indices per vertex, and jumping by 9 indices per triangle, so that needs fixing.

You need to use only 1 index per vertex, and 3 per triangle. The code may end up looking something like the following:

JAVASCRIPT
for (let i = 0; i < indices.length; i += 3) { const vert0 = vec3.create( vertices[indices[i] * 3], vertices[indices[i] * 3 + 1], vertices[indices[i] * 3 + 2] ) const vert1 = vec3.create( vertices[indices[i + 1] * 3], vertices[indices[i + 1] * 3 + 1], vertices[indices[i + 1] * 3 + 2] ) const vert2 = vec3.create( vertices[indices[i + 2] * 3], vertices[indices[i + 2] * 3 + 1], vertices[indices[i + 2] * 3 + 2] )

The normals[...[...]...] section below that needs a similar fix. You can't go up to i + 8 there, you only get 3 indices per triangle. Use the same kind of math I'm showing above.

Give that a try and see if the rest of the code works. But don't forget, if you find normals in the glTF file itself, they are likely better than the ones you're able to generate here, particularly if the artist or Blender user has taken time to make them pretty.

No comments yet.

Discussion

No comments yet.