Triangle-wise shading from individual vertice/mesh index inputs

Hi, I am making a background for my website, and I would like it to be similar to:
image

With a wireframe load, I am getting:
image

Without wireframe, I am getting:
image

This is achieved using a simple smoothstep-shader where I take the y-coordinate of the vertex into account when creating the color:

<script type="x-shader/x-fragment" id="fragmentShader">
    uniform vec3 color1;
    uniform vec3 color2;
    varying float y; //passed from vertex shader position.y
    void main() {
      float t = smoothstep(0.0, 1.0, (y + 10.0) / 20.0);
      gl_FragColor = vec4(mix(color1, color2, t), 1.0);
    }
</script>

So, the tricky part here is that I want the triangles to be triangulated on every frame depending on the position of the vertices/white points, this means that I am working with a set of points or a set of meshIndices:

var indexDelaunay = Delaunator.from(
  pts.map(v => {
    return [v.x, v.y];
  })
);

var meshIndex = []; // delaunay index => three.js index
for (let i = 0; i < indexDelaunay.triangles.length; i++){
  meshIndex.push(indexDelaunay.triangles[i]);
}

g.setIndex(meshIndex); // add three.js index to the existing geometry
g.computeVertexNormals();

const myShaderMaterial = new THREE.ShaderMaterial({
  uniforms: {
    time: gu.time,
    color1: { value: new THREE.Color(0xec5800) },
    color2: { value: new THREE.Color(0xffbf00) },
  },

  vertexShader: document.getElementById("vertexShader").textContent,
  fragmentShader: document.getElementById("fragmentShader").textContent,
  wireframe: false,
  vertexColors: true
});
console.log(myShaderMaterial.time)

var mesh = new THREE.Mesh(
  g,  
  myShaderMaterial
);
mesh.material.side = THREE.DoubleSide;
scene.add(mesh);

I have tried creating a new color-buffer to store the colors for the specific triangles by averaging together three following meshindices and setting all 3 :

var colors = [];
for (let i = 0; i < meshIndex.length; i+=3) {
  let avgy = (meshIndex[i].y + meshIndex[i+1].y + meshIndex[i+2].y)/3;
  let color = new THREE.Color(avgy);
  colors[meshIndex[i]] = color;
  colors[meshIndex[i+1]] = color;
  colors[meshIndex[i+2]] = color;
}

g.setAttribute('color', new THREE.Float32BufferAttribute(colors, 1)); // set vertex color

But here I get a bit confused, as the triangulator can use the same vertex for many triangles, while I will always give them the last color seen to triangulate from. This means that we will end up with the same issue if a vertice has more than 1 connecting triangle.

I have also tried to simply set a uniform color and use a modified pointlight + simulated depth to color the triangles, but it does not create the effect that I want.

So I was then wondering if anyone here has experience with this. Or knows if this is possible to do with my constraints?

Looks like it comes from this topic: Three.js + Delaunator
The approach there uses an indexed buffer geometry, which is not suitable for the thing you want to achieve.
I would cast the result geometry to non-indexed. And it needs to be done on every render.

There are also options with fragment shaders: Browse (1) - Shadertoy BETA

You are correct the initial Delunator comes from your post there. I see.

If I understand you correct, you would simply take the meshIndices and pass them directly to a Shader instead of adding them to the existing point geometry using:

g.setIndex(meshIndex);

Would you still be able to use a THREE.Mesh to render the non-indexed geometry?

If you want just background, why not simply make it with canvas’ 2d-context.

Stuff used: Delaunaror, THREE.Vector2, THREE.Color, THREE.MathUtils, canvas.getContext(“2d”).
Here, three.js used as a lib with helper classes/methods.

1 Like

I have the feeling that a more faceted (flat shaded) appearance is needed? Something like this:

Crystal planet

image

1 Like

Not that off-topic, as I used parts of three.js :sweat_smile:

2 Likes

You guys :sweat_smile: Good job both! I have other 3D objects in the scene, so maybe a combination of Pavel’s and Paul’s ideas will be the final result to avoid the 2D context but still get the desired effect. The idea is that this will be a backdrop running behind the other geometry. I am going to play with the SDF/Fragment shader/whatever you can call that codepen tomorrow after work.