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
