Trouble getting normals into THREE.BufferGeometry()

I cannot seem to get my normal data into the buffergeometry.
This is my current code.

let Id = 0;
let geometry = new THREE.BufferGeometry();      
let vertices = [...];
let indices = [...];  
let normals = [0.002, 0.1, 3.0, ...];
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
geometry.setAttribute( 'position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.computeFaceNormals();
geometry.computeVertexNormals();
const texture = new THREE.TextureLoader().load('Textures/DefaultTexture.jpg');
const material = new THREE.MeshPhongMaterial({map: texture});
MeshArray[Id] = new THREE.Mesh(geometry, material);
Scene.add(MeshArray[Id]);
Id++;

if i call thisā€¦ instead of computeFaceNormals()ā€¦
geometry.setAttrubite( ā€˜normalā€™ , new THREE.Float32BufferAttribute(normals, 3));

then i get this in the consoleā€¦
geometry.setAttrubite is not a function at [line];

well it is what it says, there is no function called setAttrubite

3 Likes

well it works for this lineā€¦

geometry.setAttribute( ā€˜positionā€™, new THREE.Float32BufferAttribute(vertices, 3));

not really, you are calling setAttribute there

1 Like

oooh okay, thanks! how do i set vertex normals, instead of face normals?
how do i tell which im setting. do i need to provide normal indices? if so, how?

By default normals are treated as vertex normals, not face normals.

Your problem is lack of attention, you are trying to use a method that does not exist. The is a method called setAttribute. There is no method called setAttrubite.

Is that German as in ā€˜bitteā€™ or Russian as in ā€œAtrubite pozhalustaā€?

My recommendation is - get yourself a development setup that will check your API method spelling. Otherwise, get used to errors like these.

I take it youā€™re new to programming, please consider that spelling in software is way more important than spelling in natural language writing.

3 Likes

mmm kinda. Been doing it for about 1.5 years, and im new to javascript. i started javascript less than a week ago. My first language is c++, then glsl, c, java, html, sql, etc. Iā€™m experienced in openGL with c++, but im having troubles with vsCode, javascript, and this api. Should probs get this setup in vs2019, idkā€¦

My vertex normals arent quite coming out rightā€¦ so ill check over the .OBJ specs a little closer, and check if im doing it right.

&no not german lol.

2 Likes

How do i set normal indices??? obj has seperate indices for vertex normals, or atleast thats what im reading.

It bites :smiley:

If you have experience with opengl and GLSL this should be familiar to you. WebGL is OpenGL ES 2, the API itā€™self should be the same, itā€™s the only thing in the stack that doesnā€™t actually feel like javascript. Everything else, i agree, can be daunting.

You should look into tooling, for example, eslint works well with vscode. I think that out of the box, (not sure how, automagically) it could catch an error like this, that the method doesnā€™t exist, even if you are not using typescript.

Also, please format the code :slight_smile:

Consult the OBJLoader and what it does. No matter what, you have to have the same amount of indecis for the vertices and normals. Usually i think what happens is that you have less vertices then normals. A cube is a good example, where you can describe it with 8 vertices, but 24 normals. Both would have 36 indecis. In this case we have to expand the 8 vertices, to align with the 24 normals.

const verts = [...] // x 8
const norms = [...] // x 24
const vertexIndecis = [...] x 36
const normalIndecis = [...] x 36
const map = {} //to avoid collisions

Now you start walking your indecis, and say

const rVertices= []
for (let i = 0 ; i < indexCount ; i ++ ) {
   const vertexIndex = vertexIndecis[i] 
   const normalIndex = normalIndecis[i]  
   if(!map[normalIndex]) {
     for (let j = 0 ; j < 3 ; j ++ ) 
       rVertices[normalIndex + j] = vertices[vertexIndex + j]
     map[normalIndex] = true
   }
}

If normals 4,5,6 all share vertex 2, you need to repeat vertex 2ā€™s data three times, to align with these three unique normals.

1 Like

:rofl: I almost spilt my morning coffee, burst with laughter!

THREE.BoxBufferGeometry() has 24 vertices (4 vertices per side), 24 normals and 36 indices for a 1x1x1 box. So amount of normals has to be equal to amount of vertices, not to amount of indices.
When we call .computeVertexNormals(), for indexed elements it simply re-writes the values for a vertex normal with values of the normal for the last computed face that the vertex belongs to: three.js/src/core/BufferGeometry.js at master Ā· mrdoob/three.js Ā· GitHub
And I think thatā€™s why THREE.OBJLoader() returns a mesh with a non-indexed buffer geometry (ā€œtriangle soupā€), otherwise, with an indexed buffer geometry, shading will look weird or incorrect.
But maybe Iā€™m wrong with my suggestions :thinking:

1 Like

Maybe this will help you out.

https://hofk.de/main/threejs/BufferGeometry/buffer_withNormals.html

( see in discourse.threejs Problems with creating custom polyhedron geometry )

NOTE! .addAttribute is now .setAttribute

This is after the expansion. There is no reason to repeat the 8 normals 3 times for each corner when stored in the obj.

Uvs are a better example, you can have UV0 be each triangle mapped separately, and UV1 completely continous mesh, at this point, no amount of indexing will help :slight_smile: Even if the mesh is actually completely smooth, you have to break apart both the vertices and the normals.

Perfectly agreed about storing data in obj.

But what about an indexed buffer geometry in Three.js (as far as I can get the author wants this type of geometry from obj)?
Having this box: https://people.sc.fsu.edu/~jburkardt/data/obj/cube.obj, how to set/assign (donā€™t know which word is appropriate in this case :slight_smile: ) three different normals to one vertex? Computing average normal of faces, a vertex belongs to, is also an option, but a vertex of a corner can belong to more than 3 faces.

Well to assign one is easy, whch one is up to you :slight_smile: it doesnā€™t work like that, you need a unique vertex wherever there is something colliding, be it index, uv, normal, etc. Lets put it like this, best case scenario, the attribute with the most elements will win, thats how many vertices you will have. Worst case scenario youll have as many vertices as you have triangles aka a triangle soup.

Simple example, take a plane, `new PlaneG(1,1,100,100). Now make two mappings, one where half the plane is connected and welded (shared vertices) and other where its broken in a triangle soup. Make another chanel where its the opposite. Now you need a triangle soup, even though all your attributes are smaller than it.

Seems weā€™re talking about the same thing, but with different words :slight_smile:

I mean that to have correct shading (that relies on correct normals), you need a triangle soup, where all three vertices of a face have the same values for normals. As there is no such thing for a buffer geometry like ā€œnormal indexā€.
At least I got it like that, that the author wants to re-use everything as much as possible, including re-using of normals by their indices from an OBJ-file somehow.

if you use non-indexed BufferGeometry, just write 3 same normals per triangle - then you get flat faces. you cant really have both face and vertex normals in BufferGeometry, unless you create a non-standadrd attribute for one of them, and then the shader to use it.

2 Likes

Would be interesting to see this :slight_smile:

@prisoner849, basically

geometry.setAttribute( 'myDopeNormals', new THREE.Float32BufferAttribute(faceNormals, 3));

and then

attribute vec3 myDopeNormals;

in vertex shader. There is an example:
https://threejs.org/examples/?q=attr#webgl_custom_attributes

However, this applies if you want to use both. If you only need one at a time, you could extend BuferGeometry to keep an extra attribute and add a method to set it into normals slot for 3js standard materials to use

@makc3d just a reminder: weā€™re talking about an indexed buffer geometry (that has to be built from an obj file).
Attributes work per vertex.
For example, a geometry of 4 points.

[0]---[1]
 |   / |
 |  /  |
 | /   |
[2]---[3]
var pts = [
  new THREE.Vector3(-1, 1, 0),
  new THREE.Vector3(1, 1, 1),
  new THREE.Vector3(-1, -1, 1),
  new THREE.Vector3(1, -1, 0)
]

Itā€™s index is [0, 2, 1, 2, 3, 1].
Also weā€™ve got a list of normals for faces in the obj file:

vn -1.0 1.0 1.0
vn 1.0 -1.0 1.0

so, weā€™ll have an array

var normals = [
  new THREE.Vector3(-1, 1, 1).normalize(),
  new THREE.Vector3(1, -1, 1).normalize()
];

and letā€™s convert them into a typed array

var ns = [];
normals.forEach(n => {ns.push(n.x, n.y, n.z)});

And then create a buffer attribute

geom.setAttribute("dopeNormals", new THREE.Float32BufferAttribute(ns, 3));

If you try to run this, youā€™ll get an error that there is a difference between amount of items of vertices and amount of items of dopeNormals (4 vs 2).

Even somehow we can pass the data about normals as a uniform (an array of vec3 or encode it in a THREE.DataTexture()), how an attribute with indices of normals has to look like (considering that amount of items in it must be 4, the same as the amount of items of vertices)?

1 Like