トップへ(mam-mam.net/)

[Three.js] Expressing Metaballs with MarchingCubes.js | Generate Smooth 3D Objects with Polygons

Japanese

Expressing Metaballs with Three.js and MarchingCubes.js | Generate Smooth 3D Objects with Polygons

Use Three.js together with MarchingCubes.js to smoothly represent metaballs with polygons!
This article explains how to generate realistic 3D objects with interactive viewpoint rotation.
It introduces techniques ideal for creating 3D content with WebGL, explained in detail with source code and examples.

From what I tested, it seems only spheres can be handled.
<script src="./three_r145/MarchingCubes.js">
— include this to make MarchingCubes available.

Rotate the viewpoint with left mouse drag or panning.

<canvas id="can" style="width:1000px; height:600px; max-width:calc(100vw - 36px);max-height:calc((100vw - 36px) * 60 / 100);margin:0;padding:0;cursor:grab;" width="1000" height="600"></canvas>

<script src="./three_r145/three.min.js"></script>
<script src="./three_r145/OrbitControls.js"></script>
<script src="./three_r145/MarchingCubes.js"></script>

<script>
  var balls=[];
  var scene,camera;
  var SphereMesh;
  var main = function () {
    // ■ Create scene
    scene = new THREE.Scene();

    // Get target canvas
    let can=document.getElementById('can');
    // Get canvas width and height
    let w = parseInt(can.style.width);
    let h = parseInt(can.style.height);

    // ■ Create and set camera
    // THREE.PerspectiveCamera( field of view (deg), aspect ratio, near clipping plane, far clipping plane );
    camera = new THREE.PerspectiveCamera( 50, w/h, 1, 10000 );
    camera.position.set( 0, 0, 2000 );// set camera position
    camera.rotation.set(0,0,0);     // set camera rotation
    //camera.lookAt(new THREE.Vector3(0, 0, 0));
    scene.add( camera );

    // ■ Place renderer on DOM(canvas)
    renderer = new THREE.WebGLRenderer({canvas:can , antialias: true});// enable antialiasing
    renderer.setSize( w, h );

    // ■ Lights ------------------------------------------------
    // Ambient light (applies light from all directions, prevents completely black shadows)
    var AmbientLight=new THREE.AmbientLight(0xffffff,0.4);
    scene.add( AmbientLight );
    // SpotLight            color, intensity, distance, angle, penumbra[0-1], decay[1,1]
    const spotLight=new THREE.SpotLight(0xffffff, 2,3000,Math.PI/4, 0.8, 1);
    //(x→, y↑, z forward)
    spotLight.position.set(100,1000,1500);
    spotLight.target.position.set(0,0,0);
    scene.add(spotLight);
    scene.add(spotLight.target);

    // ■ Load and set texture mapping image
    // Load texture image to apply to sphere
    let Texture360=new THREE.TextureLoader().load('./three_r145/img/360.jpg');
    Texture360.wrapS=THREE.RepeatWrapping;
    Texture360.wrapT=THREE.RepeatWrapping;
    // Set texture repeat count
    Texture360.repeat.set(1,1);
    // Create basic material for sphere
    let SphereMaterial=new THREE.MeshBasicMaterial({
      color:0xFFFFFF,
      side:THREE.BackSide,// show both sides
      map: Texture360
    });
    // Create sphere geometry (radius, width segments, height segments)
    let SphereGeometry=new THREE.SphereGeometry(1,32,24);
    // Create sphere mesh
    SphereMesh=new THREE.Mesh(SphereGeometry,SphereMaterial);
    SphereMesh.scale.set(5000,5000,5000);
    scene.add(SphereMesh);

    // Cube map
    // ■ Create target for reflection mapping
    cubeRenderTargetReflection = new THREE.WebGLCubeRenderTarget(512,{
        generateMipmaps: true,
        minFilter: THREE.LinearMipmapLinearFilter    });
    cubeRenderTargetReflection.texture.mapping = THREE.CubeReflectionMapping;// set to reflection mapping
    // ■ Create cube camera for reflection mapping
    //(near clipping plane, far clipping plane, target)
    cubeCameraReflection = new THREE.CubeCamera( 0.1, 100000, cubeRenderTargetReflection);
    // ■ Create reflection material
    let materialReflection=new THREE.MeshLambertMaterial({
      color:0xffffff,
      envMap:cubeCameraReflection.renderTarget.texture,
      reflectivity:0.8,    // reflectivity
    });

    // ■ Create MarchingCubes instance
    //(resolution: higher value = smoother rendering 20–100, material, enableUvs, enableColors, maxPolyCount)
    effect = new THREE.MarchingCubes( 64, materialReflection, true, true ,100000);
    effect.position.set( 0, 0, 0 );
    effect.scale.set( 1000, 1000, 1000 );
    //effect.isolation=80; // isolation???
    scene.add(effect);

    // Prepare metaballs
    for(let i=0;i<20;i++){
      balls[i]=[];
      balls[i].x=Math.random()*0.8;
      balls[i].y=Math.random()*0.8;
      balls[i].z=Math.random()*0.1+0.5;
      balls[i].vx=Math.random()*0.01-0.005;
      balls[i].vy=Math.random()*0.01-0.005;
    }

    // Move with left/right mouse drag + middle button wheel
    const controls = new THREE.OrbitControls(camera, renderer.domElement);  
    controls.target.set(0,0,0);
    controls.enablePan=false; // disable panning
    controls.enableZoom=false;// disable zoom
    controls.update();
    renderLoop();
  };

  function renderLoop () {
    setTimeout(renderLoop,33);
    effect.reset();// reset
    for(let i=0;i<balls.length;i++){
      if((balls[i].x+balls[i].vx)>0.8){balls[i].vx=-balls[i].vx;}
      if((balls[i].x+balls[i].vx)<0.0){balls[i].vx=-balls[i].vx;}
      if((balls[i].y+balls[i].vy)>0.8){balls[i].vy=-balls[i].vy;}
      if((balls[i].y+balls[i].vy)<0.0){balls[i].vy=-balls[i].vy;}
      balls[i].x+=balls[i].vx;
      balls[i].y+=balls[i].vy;
      //        x,y,z(0–1), strength, subtract, color
      // addBall(x,y,z,strength,subtract,colors)
      // radius = sqrt(strength ÷ subtract)
      effect.addBall(balls[i].x+0.1, balls[i].y+0.1, balls[i].z, 1.6, 100);
    }
    effect.update();
    SphereMesh.visible=true;
    effect.visible=false;
    cubeCameraReflection.update(renderer,scene);
    effect.visible=true;
    SphereMesh.visible=false;

    renderer.render( scene, camera );
  }

  window.addEventListener( 'DOMContentLoaded', main, false );
</script>

Back to the list of 3D content with JavaScript