5
votes

I have tried to give 3D feeling to SVG paths (lineto and moveto) by extruding shapes using Three.js, but the process causes some artifacts that I cannot remove.

What could cause odd artifacts in my rendered 3D shape? Is there a way to remove them?

The artifacts are marked with arrows in the sample image below.

enter image description here

The live example is here: http://jsfiddle.net/pHn2B/24/

And the code is here:

// Picking with Callback

// three.js r.52
var container,
    info,
    camera,
    scene,
    light,
    geometry,
    mesh,
    projector,
    renderer,
    controls;
    objects = [];

// dom
container = document.createElement( 'div' );
document.body.appendChild( container );

// info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '10px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.innerHTML = "drag to rotate camera; click to select";
container.appendChild( info );

// renderer
renderer = new THREE.CanvasRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );

// scene
scene = new THREE.Scene();

// camera
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
camera.position.set( 0, 300, 500 );
scene.add( camera );

// controls
controls = new THREE.OrbitControls( camera );

// light
scene.add( new THREE.AmbientLight( 0x222222 ) );

// light
light = new THREE.PointLight( 0xaaaaaa );
light.position = camera.position;
scene.add( light );

// geometry
geometry = new THREE.CubeGeometry( 100, 100, 500 );

// material
material = new THREE.MeshLambertMaterial( { color: 0xff0000, ambient: 0xff0000, overdraw: true } );

// mesh
mesh = new THREE.Mesh( geometry, material );
mesh.position.set( -100, -100, 200 );
mesh.name = "Red Object";
mesh.callback = function() { info.innerHTML = this.name; }
scene.add( mesh );

objects.push( mesh );

// geometry
////////////
    // CUSTOM //
    ////////////

var starPoints2 = new THREE.Shape();

starPoints2.moveTo(307.94,275.49);
starPoints2.lineTo(296.26,275.23);
starPoints2.lineTo(286.64,272.99);
starPoints2.lineTo(279.78,269.31);
starPoints2.lineTo(274.14,263.55);
starPoints2.lineTo(271.65,260.21);
starPoints2.lineTo(269.2,261.06);
starPoints2.lineTo(254.83,268.51);
starPoints2.lineTo(242.11,272.97);
starPoints2.lineTo(227.59,275.23);
starPoints2.lineTo(209.91,275.48);
starPoints2.lineTo(197.47,273.63);
starPoints2.lineTo(187.91,270.13);
starPoints2.lineTo(180.48,265.09);
starPoints2.lineTo(175.32,258.88);
starPoints2.lineTo(172.2,251.44);
starPoints2.lineTo(171.1,242.23);
starPoints2.lineTo(172.24,233.63);
starPoints2.lineTo(175.49,226.24);
starPoints2.lineTo(181,219.54);
starPoints2.lineTo(189.42,213.3);
starPoints2.lineTo(201.36,207.73);
starPoints2.lineTo(217.23,203.25);
starPoints2.lineTo(238.28,200.1);
starPoints2.lineTo(269.37,198.47);
starPoints2.lineTo(269.98,182.93);
starPoints2.lineTo(268.74,171.32);
starPoints2.lineTo(266.05,163.7);
starPoints2.lineTo(261.58,157.72);
starPoints2.lineTo(255.24,153.24);
starPoints2.lineTo(247.06,150.32);
starPoints2.lineTo(235.44,149.13);
starPoints2.lineTo(224.71,150.05);
starPoints2.lineTo(215.91,153);
starPoints2.lineTo(210.23,156.86);
starPoints2.lineTo(207.64,160.85);
starPoints2.lineTo(207.19,165.28);
starPoints2.lineTo(209.34,169.86);
starPoints2.lineTo(212.01,174.15);
starPoints2.lineTo(212.14,177.99);
starPoints2.lineTo(209.8,181.78);
starPoints2.lineTo(204.22,185.79);
starPoints2.lineTo(197.62,187.68);
starPoints2.lineTo(188.65,187.43);
starPoints2.lineTo(182.41,185.39);
starPoints2.lineTo(178.45,181.77);
starPoints2.lineTo(176.2,176.9);
starPoints2.lineTo(176.03,170.64);
starPoints2.lineTo(178.2,164.13);
starPoints2.lineTo(183.09,157.69);
starPoints2.lineTo(191.04,151.36);
starPoints2.lineTo(202.01,145.82);
starPoints2.lineTo(216.09,141.57);
starPoints2.lineTo(232.08,139.24);
starPoints2.lineTo(250.07,139.18);
starPoints2.lineTo(266.13,141.23);
starPoints2.lineTo(279.05,145.06);
starPoints2.lineTo(289.15,150.3);
starPoints2.lineTo(295.91,156.19);
starPoints2.lineTo(300.73,163.41);
starPoints2.lineTo(303.85,172.47);
starPoints2.lineTo(305.07,183.78);
starPoints2.lineTo(305.07,241.97);
starPoints2.lineTo(306,251.51);
starPoints2.lineTo(308.18,256.39);
starPoints2.lineTo(311.72,259.09);
starPoints2.lineTo(317.31,260.01);
starPoints2.lineTo(324.71,259.01);
starPoints2.lineTo(332.45,255.86);
starPoints2.lineTo(335.57,257.53);
starPoints2.lineTo(337.6,260.44);
starPoints2.lineTo(336.94,262.33);
starPoints2.lineTo(328.27,268.74);
starPoints2.lineTo(317.89,273.41);
starPoints2.lineTo(307.94,275.49);
/*
starPoints2.moveTo(245.79,125.33);
starPoints2.lineTo(232.93,124.53);
starPoints2.lineTo(222.21,121.74);
starPoints2.lineTo(213.14,117.11);
starPoints2.lineTo(207.36,111.92);
starPoints2.lineTo(203.7,105.75);
starPoints2.lineTo(201.94,98.18);
starPoints2.lineTo(202.34,90.12);
starPoints2.lineTo(204.86,83.4);
starPoints2.lineTo(210.01,76.81);
starPoints2.lineTo(217.49,71.33);
starPoints2.lineTo(227.17,67.31);
starPoints2.lineTo(238.35,65.2);
starPoints2.lineTo(243.99,64.95);
starPoints2.lineTo(255.92,66.06);
starPoints2.lineTo(266.21,69.28);
starPoints2.lineTo(274.98,74.44);
starPoints2.lineTo(280.64,80.19);
starPoints2.lineTo(284.02,86.85);
starPoints2.lineTo(285.26,94.52);
starPoints2.lineTo(284.27,102.84);
starPoints2.lineTo(281.24,109.66);
starPoints2.lineTo(276.03,115.43);
starPoints2.lineTo(267.89,120.46);
starPoints2.lineTo(257.68,123.93);
starPoints2.lineTo(245.79,125.33);
*/
var smileyEye1Path = new THREE.Path();

smileyEye1Path.moveTo(221.69,258.13);
smileyEye1Path.lineTo(215.2,255.08);
smileyEye1Path.lineTo(210.86,250.57);
smileyEye1Path.lineTo(208.4,244.49);
smileyEye1Path.lineTo(207.92,237.03);
smileyEye1Path.lineTo(209.69,230.71);
smileyEye1Path.lineTo(213.82,224.85);
smileyEye1Path.lineTo(220.9,219.34);
smileyEye1Path.lineTo(230.95,214.67);
smileyEye1Path.lineTo(245.76,210.86);
smileyEye1Path.lineTo(266.59,208.36);
smileyEye1Path.lineTo(269.48,208.76);
smileyEye1Path.lineTo(269.99,212.88);
smileyEye1Path.lineTo(269.99,244.81);
smileyEye1Path.lineTo(269.34,247.02);
smileyEye1Path.lineTo(266.07,250.04);
smileyEye1Path.lineTo(255.27,255.23);
smileyEye1Path.lineTo(242.52,258.58);
smileyEye1Path.lineTo(230.57,259.43);
smileyEye1Path.lineTo(221.69,258.13);

/*
smileyEye1Path.moveTo(238.44,116.65);
smileyEye1Path.lineTo(231.99,114.29);
smileyEye1Path.lineTo(227.23,110.22);
smileyEye1Path.lineTo(223.94,104.53);
smileyEye1Path.lineTo(222.41,96.92);
smileyEye1Path.lineTo(223.05,88.57);
smileyEye1Path.lineTo(225.65,82.21);
smileyEye1Path.lineTo(230.07,77.36);
smileyEye1Path.lineTo(235.93,74.4);
smileyEye1Path.lineTo(243.68,73.34);
smileyEye1Path.lineTo(246.08,73.43);
smileyEye1Path.lineTo(253.37,75.08);
smileyEye1Path.lineTo(258.65,78.43);
smileyEye1Path.lineTo(262.47,83.41);
smileyEye1Path.lineTo(264.59,90.25);
smileyEye1Path.lineTo(264.64,98.93);
smileyEye1Path.lineTo(262.63,106.12);
smileyEye1Path.lineTo(258.87,111.5);
smileyEye1Path.lineTo(253.73,115.1);
smileyEye1Path.lineTo(246.81,116.94);
smileyEye1Path.lineTo(238.44,116.65);
*/
var starShape = starPoints2;

starShape.holes.push( smileyEye1Path );

    var extrusionSettings = {
        //size: 1, height: 1, curveSegments: 100, step = 10,
        // font, weight, style,
        amount:20,
        bevelEnabled: true, 
        bevelThickness: 0.5, 
        bevelSize: 0.5,
        bevelSegments: 8,
        // extrudePath:
        // bendPath:
        material: 0,
        extrudeMaterial: 1
        //,
        //uvGenerator: BoundingUVGenerator
       //uvGenerator: THREE.ExtrudeGeometry.WorldUVGenerator
    };

    var starGeometry = new THREE.ExtrudeGeometry( starShape, extrusionSettings );

    var materialFront = new THREE.MeshLambertMaterial( { color: 0xffff00, ambient: 0xffff00, overdraw: false, transparent:false, opacity: 1.0, side: THREE.DoubleSide } );
    var materialSide = new THREE.MeshLambertMaterial( { color: 0xff8800, ambient: 0xff8800, overdraw: false, transparent:false, opacity: 1.0, side: THREE.DoubleSide } );

//var crateTexture = new THREE.ImageUtils.loadTexture( 'http://www.kahkonen.com/asiakkaat/crate2.gif');
//var crateTexture = new THREE.ImageUtils.generateDataTexture(10,10,{r:255,g:0,b:0});

//var materialFront = new THREE.MeshBasicMaterial( { map: crateTexture } );

var materialArray = [ materialFront, materialSide ];
var materialArray = [ materialFront, materialSide ];

var starMaterial = new THREE.MeshFaceMaterial(materialArray);

    var star = new THREE.Mesh( starGeometry, starMaterial );

    star.position.set(-150,-150,0);
    scene.add(star);
/*  
    // add a wireframe to model
    var wireframeTexture = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true, transparent: false } ); 
    var star = new THREE.Mesh( starGeometry, wireframeTexture );
    star.position.set(50,10,0);
    scene.add(star);
*/
objects.push( star );

// projector
projector = new THREE.Projector();

// listeners
document.addEventListener( 'mousedown', onDocumentMouseDown, false)

// keyboard handler
function onDocumentMouseDown( event ) {

    event.preventDefault();

    var vector = new THREE.Vector3( 
        ( event.clientX / window.innerWidth ) * 2 - 1, 
        - ( event.clientY / window.innerHeight ) * 2 + 1, 
        0.5 );

    projector.unprojectVector( vector, camera );

    var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );

    var intersects = ray.intersectObjects( objects );    

    if ( intersects.length > 0 ) {

        intersects[0].object.callback();

    }

}

// render
function render() {

    controls.update()

    renderer.render( scene, camera );
}

// animate            
(function animate() {

    requestAnimationFrame( animate );
    render();
}());

I have tried to remove artifacts by appending texture, but texture is not shown at all:

enter image description here

Other problem (that may or may not be related to artifact-problem) is that background can be seen through edges.

Texture is not a must, but removing artifacts is.

The code that I have used to add texture is below and full code in the fiddle (link is after the code):

var crateTexture = new THREE.ImageUtils.loadTexture( 'http://www.kahkonen.com/asiakkaat/crate.gif'); var crateTexture = new THREE.ImageUtils.generateDataTexture(10,10,{r:255,g:0,b:0}); var materialFront = new THREE.MeshBasicMaterial( { map: crateTexture } );

// http://jsfiddle.net/pHn2B/27/

Sample images are from Chrome. The same behavior occurs in Firefox too.

3
It's not an artifact in extrusion, it is a known bug in CanvasRenderer related to z sorting. If you switch to WebGLRenderer you will see that it stops. stackoverflow.com/questions/11199049/…John McKnight
Thanks! It really solves the artifact issue, but not the texture issue: jsfiddle.net/pHn2B/28. There should be texture on the glyph (front side), but nothing there.Timo Kähkönen
THREE.ImageUtils.loadTexture is a helper function so it doesn't need a new. You can also create your mesh a little easier by using var mesh = THREE.SceneUtils.createMultiMaterialObject( starGeometry, [ materialFront, materialSide ] );John McKnight
I got the texture on each side of the cube (by putting all stuff on my own local server which made securityError to disappear), but the materialFront on front of the glyph is plain brown, no texture. It was black before. I really have no idea why the texture is not there.Timo Kähkönen
So I assume that three.js supports textures only on predefined shapes and not with extruded custom shapes. It would very nice if also custom shapes could have textures. Maybe some other library than three.js have support for extruding path and appending texture on it?Timo Kähkönen

3 Answers

3
votes

This is a know limitation of CanvasRenderer caused by z-buffering problems. It is made worse by your geometry which has many elongated faces. The model renders correctly with WebGLRenderer.

ExtrudeGeometry was originally written for text, and if you look at the UVs it generates, it just uses the x- and y-components of the vertex positions for UVs, which in your case, produces values that are outside the range [ 0, 1 ]. You have the option of providing your own UV-generator in a callback function.

Make sure you can successfully add a texture to a cube first.

three.js r.58

2
votes

Just for further reference. The answer of @Westlangley lead me to the right direction to get texture on the shape. The key was scaling values from one range to another range, from world coordinates to shapes inner UV range, which must be 0-1.

So I copied THREE.ExtrudeGeometry.WorldUVGenerator and renamed it to THREE.ExtrudeGeometry.BoundingBoxUVGenerator and replaced in member function generateTopUV() these lines:

        return [
            new THREE.Vector2( ax, ay ),
            new THREE.Vector2( bx, by ),
            new THREE.Vector2( cx, cy )
        ];

with these:

    var bb = extrudedShape.getBoundingBox();
    var bb_minX = bb.minX;
    var bb_minY = bb.minY;
    var bb_width = bb.maxX - bb_minX;
    var bb_height = bb.maxY - bb_minY;
        if (bb_width == 0) bb_width = 1;
        if (bb_height == 0) bb_height = 1;
        return [new THREE.Vector2( (ax - bb_minX) / bb_width,  (ay - bb_minY) / bb_height  ),
            new THREE.Vector2( (bx - bb_minX) / bb_width,  (by - bb_minY) / bb_height  ),
            new THREE.Vector2( (cx - bb_minX) / bb_width,  (cy - bb_minY) / bb_height  )
        ];

and the texture "magically" appeared on the right place!

( For someone who have tried similarly named function from https://github.com/mrdoob/three.js/issues/1811 . It does not work, because range conversion is made some odd way. )

This works also in cases where the vertice coordinates are really in range 0-1, and in cases where the coordinates are negative. And if for some reason width and height are 0, returns 0.

For the most of the possible purposes this is far more useful than the default THREE.ExtrudeGeometry.WorldUVGenerator. And I cannot even imagine any purposes for the default generator, because it works only for vertices that have coordinates in the range 0-1. But I'm new to three.js, and there can be reasons that I cannot imagine yet.

1
votes

I have the same issue before.
I also replaced member function generateSideWallUV()
these lines:

if ( Math.abs( ay - by ) < 0.01 ) {
            return [
                new THREE.Vector2( ax, 1 - az ),
                new THREE.Vector2( bx, 1 - bz ),
                new THREE.Vector2( cx, 1 - cz ),
                new THREE.Vector2( dx, 1 - dz )
            ];
} else {
            return [
                new THREE.Vector2( ay, 1 - az ),
                new THREE.Vector2( by, 1 - bz ),
                new THREE.Vector2( cy, 1 - cz ),
                new THREE.Vector2( dy, 1 - dz )
            ];
        }

with these lines:

var amount = extrudeOptions.amount;
if ( Math.abs( ay - by ) < 0.01 ) {
            return [
                new THREE.Vector2( (ax - bb_minX) / bb_width, 1-(az / amount) ),
                new THREE.Vector2( (bx - bb_minX) / bb_width, 1-(bz  / amount) ),
                new THREE.Vector2( (cx - bb_minX) / bb_width, 1-(cz  / amount) ),
                new THREE.Vector2( (dx - bb_minX) / bb_width, 1- (dz  / amount) )
            ];
        } else {
            return [
                new THREE.Vector2( (ay - bb_minY) / bb_height, 1-(az / amount )),
                new THREE.Vector2( (by - bb_minY) / bb_height, 1-(bz / amount )),
                new THREE.Vector2( (cy - bb_minY) / bb_height, 1-(cz / amount )),
                new THREE.Vector2( (dy - bb_minY) / bb_height, 1-(dz / amount ))
            ];
        }`