I`m trying to implement vertex normal generation from vertex positions using the cross product method (in javaScript, from a gltf positions buffer), but most of resulting normals turn out to be (0, 0, 0). Code:
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]
}
What I expected to get is something similar to this image:
But what I actually get now is this:
I tried changing the winding order, to no effect. Normalizing or not also seems to make no difference (but that is due to normals being normalized in the shader anyway).
From what i have seen on the internet, the math seems to be fine.
I`m also rather confident that all vertices are processed, because tangents i generate in the same manner, turn out just fine. To me that means that normals[indices[i]] += normal[0]
regularly produces 0, but i can not understand why. And what to do about it, if that is the problem. Any suggestions would be much appreciated.
FYI: rendering is done with webGPU, shader code doesn`t differ for either normals. Model is generated with Khronos Blender glTF 2.0 exporter
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:
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.