Is computeVertexNormals working correctly?

I have a cylinder setup, it starts with smooth transition between vertical faces:

calcVert_01

after computeVertexNormals() is applied, the smooth transition remains, except the edge between the first and the last face, where there are 3 different normals instead of 2 (or 3 matching in to 2 before):

calcVert_02

this creates a jump between normals on more complex geometries, where there shouldn’t be any:

It is actually not intended to recompute normals of geometry generators since they are often computed analytically to get correct result. Using computeVertexNormals() will undo this effort and produce unexpected results.

In this particular case, the seam occurs since the vertices are duplicated at the edge because different texture coordinates are required. So because the body has no continuous geometry, computeVertexNormals() can’t be used.

2 Likes

For special geometries, you should always calculate the normals yourself.
However, I sometimes use .computeVertexNormals( ) when only a few changes are needed afterwards.

From the Collection of examples from discourse.threejs.org : MultiFormGeometry
see
view-source:https://hofk.de/main/discourse.threejs/2022/MultiFormGeometry/multiFormGeometryStatic.js

line 473

       g.computeVertexNormals( );
        
        // calculate new average normals at seam ( smooth shading )
        
        for ( let i = 0; i < hss; i ++ ) { // height
            
            smoothEdge( rss * i, rss * i + rs );
            
        }
        
        if( wb ) {  // calculate new average normals at bottom  ( smooth shading )
        
            for( let j = 0; j < rss ; j ++ ) { // bottom
                
                smoothEdge( j, rss * hss + 1 + j );
                
            }
            
        }
        
        if( wt ) {  // calculate new average normals at top ( smooth shading )
        
            for( let j = 0; j < rss ; j ++ ) { // top
                
                smoothEdge( rss * hs + j, rss * hss + rss + 2 + j );
                
            }
        }

       function smoothEdge( idxa, idxb ) {
            
            const v3a = new THREE.Vector3( );
            const v3b = new THREE.Vector3( );
            const v3  = new THREE.Vector3( );
            
            v3a.set( g.attributes.normal.getX( idxa ), g.attributes.normal.getY( idxa ), g.attributes.normal.getZ( idxa ) );
            v3b.set( g.attributes.normal.getX( idxb ), g.attributes.normal.getY( idxb ), g.attributes.normal.getZ( idxb ) );
            
            v3.addVectors( v3a, v3b ).normalize( );
            
            g.attributes.normal.setXYZ( idxa, v3.x, v3.y, v3.z );
            g.attributes.normal.setXYZ( idxb, v3.x, v3.y, v3.z );
            
        }

see also Curved2Geometry

Yeah, I realized that shape agnostic algorithms will not work, didn’t think about it much before. I wrote some code for updating normals for certain shapes after twisting and profiling them along main axes, that’s what I needed. Instead of running computeVertexNormals and then fixing seams, I took a different approach, where I’m reusing original normals, this way I’m less bound to how the geometry was generated and where the seam are.

Here is an example for CylinderGeometry:

calcVert_04

Short answer yes.
The logic used here has been used years before 3JS existed.

Usually a solution of removing uv and norm attribute and running mergevertices is given.
I never consider that a solution, but surprising many did at FIRST.

If you are into creating geometry from scratch, then you are familiar with term Seam. It happens at the border line of two adjacent faces having different vertex normals. We use the same technique to form hard edges that are independent of material in use.

Weeding out these vertices that have same xyz value but different norms is easy. Neutralizing the seams even easer. The problem lies in producing a logic detecting if these vertices are part of hard edge or not.

This is where User input is required.