0

I am looking on how to implement something similar than this work: https://vimeo.com/9121195 . But with explicitly attributing colors to each metaball from an array of given colors. As far as I can see, this is done entirely from the shader side in this example but I was wondering if this could not be implemented with Threejs and marchingcubes.

By creating a new THREE.ShaderMaterial I assume I can pass positions of each metaball to the shader and then evaluate the distance of each vertex with the given positions to finally assign a color to each pixel.

Just to simplify the problem I start with two moving metaballs.

from threejs:

function init() {
    //...
var strength = 0.61;
var resolution = 70;
var substract = 25;
var scaleFactor = 1200;
var posData = []; // array storing metaballs positions

var myShaderMaterial = new THREE.ShaderMaterial({ uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
                                                   vertexShader: shader.vertexShader, 
                                                   fragmentShader: shader.fragmentShader,
                                                   name:"myShaderMaterial" }); // shader is described below

effect = new THREE.MarchingCubes( resolution, myShaderMaterial,false,true);

effect.position.set( 0, 0, 0 );
effect.scale.set( scaleFactor, scaleFactor, scaleFactor );
effect.enableUvs = true;
effect.enableColors = true;
effect.hasColors = true;
effect.castShadow = true;
//...

scene.add( effect );
}

function render() {
effect.reset();
effect.init(resolution);
for (var i = 0; i <= posData.length - 1; i++) {
    item = posData[i];
    effect.addBall(item[0], item[1], item[2], strength, substract);
    effect.material.uniforms.allPos.value[i] = new THREE.Vector3(item[0],item[1],item[2]).multiplyScalar(1./scaleFactor);

}
//...

}

here is my shader:

'myShaderMaterial' : {

uniforms: {

    "colChoice":{type: "v3v", value:[new THREE.Color( 0xef5350 ),
                                    new THREE.Color( 0xffffff )]},
    "allPos":{type: "v3v",value:[new THREE.Vector3(0., 0., 0.),
                                 new THREE.Vector3(0., 0., 0.)]},
},

vertexShader: [

    "uniform vec3 colChoice[2];",
    "varying vec3 vNormal;",
    "varying vec3 vRefract;",
    "varying vec3 blobColor;",
    "varying vec3 otherColor;",
    "uniform vec3 allPos[2];",
    "varying float mixScale;",

    "void main() {",

        "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
        "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
        "vec3 worldNormal = normalize ( mat3( modelMatrix[0].xyz, modelMatrix[1].xyz, modelMatrix[2].xyz ) * normal );",
        "vNormal = normalize( normalMatrix * normal );",

        "gl_Position = projectionMatrix * mvPosition;",

        "float distMin = 100000000000000000000000000000000000000.;",
        "float distMax = 0.;",      
        "for (int i=0;i<2;i++){",               
                "float distV = distance(allPos[i], position );",
                "if (distV<distMin){",
                    "distMin = distV;",
                "}",
                "if (distV>distMax){",
                    "distMax = distV;",
                "}",

                "mixScale = smoothstep(distMin,distMax,distV);",
                "if (mod(float(i),2.0)==0.){",
                    "blobColor = colChoice[0];",
                    "otherColor = colChoice[1];",
                "}",
                "else{",
                    "blobColor = colChoice[1];",
                    "otherColor = colChoice[0];",
                "}",
        "}",
    "}",
].join( "\n" ),

fragmentShader: [

    "varying vec3 blobColor;",
    "varying vec3 otherColor;",
    "varying vec3 vNormal;",
    "varying float mixScale;",


    "void main() {",

        "vec3 finalColor = (0.3*vNormal) +  mix(otherColor,blobColor,mixScale);",
        "gl_FragColor = vec4(finalColor,1.0);",

    "}",

].join( "\n" )

here is a sample result:

The issue here is that obviously the distance is not calculated properly and the transition between two colors that I tried to do with mixScale also does not work. Any idea?

compmonks
  • 647
  • 10
  • 24

1 Answers1

0

Yeah it's possible but I don't think it does it out of the box. I peeked at the marchingcubes code you linked, and it has an "enableColor" flag but color seems to be filled with the vertex coordinate instead of interpolating a separate color value from the density field.

I've had to do something very similar in the past to do multi material terrain generation from noise fields. I think I ended up extending the marching cubes to interpolate colors, then assigned each material to a color channel, r, g, b.

manthrax
  • 4,918
  • 1
  • 17
  • 16
  • any existing sample somewhere to understand how to proceed? I have found similar work done by mnmxmx on 2d : https://codepen.io/mnmxmx/pen/VjjvEq – compmonks Apr 13 '18 at 11:28
  • Wow cool demo! But that isn't marching cubes.. it's a gaussian blur/blob on canvas i think. No I don't have links to code.. I did it in Unity. looking at that video again, it kinda looks like just the surface normal. to find the surface normal, you can evaluate the density field twice in marching cubes.. the second time with a slight increase bias of the density.. then take the difference between those 2 points, normalize it.. and assign it to the color channels rgb=(xyz*0.5)+1 – manthrax Apr 14 '18 at 10:27
  • I edited the question towards a more simple solution: the color would be computed in the materialshader passed to threeJS, and relative to fields position. Does not work well yet though. – compmonks May 28 '18 at 11:20