three.js maintaining creases when smooth shading custom geometry

1.7k views Asked by At

I created a custom mesh by adding vertices and faces to a new THREE.Geometry(), then running computeFaceNormals() and computeVertexNormals() on it to smooth out the rendering (I'm using the MeshPhongMaterial). Without computevertexnormals, parts of my mesh appear striped. The problem is that the stock computeVertexNormals() included in r69 ignores sharp edges. It's an elegant function that builds each vertex's normal by averaging the surrounding faces. However it averages the normals at edges that I need to remain sharp in appearance. There were some promising comments on another question with the same topic However no code was posted to solve the issue of keeping edges sharp.

I have attempted to modify computeVertexNormals() to add edge detection but with no luck. My attempt is based on detecting the angle between neighboring faces and only adding their normal to the average if it's within a given threshold. Here's my code:

function computeVertexNormals( object, angle_threshold, areaWeighted ) { //will compute normals if faces diverge less than given angle (in degrees)

var v, vl, f, fl, face, vertices;

angle = angle_threshold * 0.0174532925; //degrees to radians

vertices = new Array( object.vertices.length );

for ( v = 0, vl = object.vertices.length; v < vl; v ++ ) {

    vertices[ v ] = new THREE.Vector3();

}

if ( areaWeighted && areaWeighted == true) {

    // vertex normals weighted by triangle areas
    // http://www.iquilezles.org/www/articles/normals/normals.htm

    var vA, vB, vC, vD;
    var cb = new THREE.Vector3(), ab = new THREE.Vector3(),
        db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3();

    for ( f = 0, fl = object.faces.length; f < fl; f ++ ) {

        face = object.faces[ f ];

        vA = object.vertices[ face.a ];
        vB = object.vertices[ face.b ];
        vC = object.vertices[ face.c ];

        cb.subVectors( vC, vB );
        ab.subVectors( vA, vB );
        cb.cross( ab );

        vertices[ face.a ].add( cb );
        vertices[ face.b ].add( cb );
        vertices[ face.c ].add( cb );

    }

} else {

    for ( f = 0, fl = object.faces.length; f < fl; f ++ ) {

        face = object.faces[ f ];

            vertices[ face.a ].add(face.normal);
            vertices[ face.b ].add( face.normal );
            vertices[ face.c ].add( face.normal );
    }

}

for ( v = 0, vl = object.vertices.length; v < vl; v ++ ) {

    vertices[ v ].normalize();

}

for ( f = 0, fl = object.faces.length; f < fl; f ++ ) {

    face = object.faces[ f ];

    //**********my modifications are all in this last section*************

    if(face.normal && face.normal != undefined){

        if(vertices[ face.a ].angleTo(face.normal) < angle_threshold){
            face.vertexNormals[ 0 ] = vertices[ face.a ].clone();
        }else{
            face.vertexNormals[ 0 ] = face.normal.clone();
        }
        if(vertices[ face.b ].angleTo(face.normal) < angle_threshold){
            face.vertexNormals[ 1 ] = vertices[ face.b ].clone();
        }else{
            face.vertexNormals[ 1 ] = face.normal.clone();
        }
        if(vertices[ face.c ].angleTo(face.normal) < angle_threshold){
            face.vertexNormals[ 2 ] = vertices[ face.c ].clone();
        }else{
            face.vertexNormals[ 2 ] = face.normal.clone();
        }

    }

}

}

Can anybody please offer a strategy for crease detection so I can have smooth shapes with some sharp edges? Thanks in advance!

1

There are 1 answers

1
Chris Chalmers On BEST ANSWER

WestLangley is correct in the comment above. To get the sharp edges I wanted, I simply duplicated vertices that were on "creases" while constructing my geometry. Then I used the standard computeVertexNormals() function included in the THREE.Geometry() prototype.

I was constructing my geometry with a home-made 'loft' function: basically iterating through an array of shapes (using i) and creating B-splines between their vertices (using j), then constructing a mesh from the B-Splines. The fix was to test the angle at each vertex of each shape. If its angle was larger than a given threshold (I used 70 degrees), I added the B-Spline a second time, effectively duplicating the vertices. Sorry if the code below is a little cryptic taken out of context.

                    //test if vertex is on a crease
                if (j == 0) {
                    before = arrCurves[i].vertices[j].clone().sub(arrCurves[i].vertices[arrCurves[i].vertices.length-1]);
                }else{
                    before = arrCurves[i].vertices[j].clone().sub(arrCurves[i].vertices[j-1]);
                }

                if (j == arrCurves[i].vertices.length-1) {
                    after = arrCurves[i].vertices[0].clone().sub(arrCurves[i].vertices[j]);
                }else{
                    after = arrCurves[i].vertices[j+1].clone().sub(arrCurves[i].vertices[j]);
                }   

                if( before.angleTo(after) > crease_threshold ){ 
                    //here's where I'm adding the curve for a second time to make the 'crease'
                    arrSplines.push(new THREE.SplineCurve3(nurbsCurve.getPoints(resolution)));
                }

Works like a charm, Thanks WestLangley!