Progressively reveal circle in loop

Viewed 74

I currently have this: Codepen (embedded snippet below).

I would like to be able to change the color progressively after the scanline, something like this: scanline render

After searching online and trying to find something in three.js / WebGL I failed to procure what I wanted, probably because I don't quite know what I should search for.

Could you help me with a solution or pointing me in the right direction? I have considered the following possibilities:

  • Having a second green circle and a dynamic mask that reveals it after the scanline.
    • How to create a mask in three.js that can show a slice of an increasing angle θ?
  • CircleGeometry has parameters to create a slice with angle θ. But constantly changing the geometry of my mesh doesn't sound very smart.
  • Adding tiny circle slices after the scanline passes so it creates the impression of revealing a circle but it's actually just adding tiny slices.

P.S. - I am using three.js because later there will be 3d elements to this project.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();

renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const innerRadius = 1;
const outerRadius = innerRadius*2;
const barLenght = innerRadius;

// create scanline
const outerMaterial = new THREE.MeshBasicMaterial({color: 0x34ebd2});
const outerCircle = new THREE.Mesh(new THREE.CircleGeometry(outerRadius, 60), outerMaterial);
scene.add(outerCircle);

// Create innerCircle 
const innerMaterial = new THREE.MeshBasicMaterial({color: 0x0000ff});
const innerCircle = new THREE.Mesh(new THREE.CircleGeometry(innerRadius, 60), innerMaterial);
scene.add(innerCircle);


// create static line
const staticLine = new THREE.Mesh(new THREE.PlaneGeometry(0.05, barLenght), new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide}));
scene.add(staticLine);

// create scan line
const scanLine = new THREE.Mesh(new THREE.PlaneGeometry(0.05, barLenght), new THREE.MeshBasicMaterial({color: 0xff0000, side: THREE.DoubleSide}));
scene.add(scanLine);

// position static line
staticLine.position.y = innerRadius + barLenght/2;

// position scan line
scanLine.position.y = innerRadius + barLenght/2;

// create pivot to rotate dateline
const pivot = new THREE.Group();
pivot.position.set( 0.0, 0.0, 0 );
pivot.add(scanLine);
scene.add(pivot);

camera.position.z = 5;

function animate() {
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
  
  pivot.rotation.z -= 0.005;
}
animate();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>

1 Answers

This is a 2-in-1 example (fragment and vertex shader implementations) of progressive arcs, just from the scratch. Use it as a starting point.

body{
  overflow: hidden;
  margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three@0.132.2";
import {OrbitControls} from "https://cdn.skypack.dev/three@0.132.2/examples/jsm/controls/OrbitControls.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(-5, 3, 8);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);

// fragment shader option
let g = new THREE.CircleGeometry(5, 64);
let m = new THREE.MeshBasicMaterial({
  color: 0x7f7f7f,
  side: THREE.DoubleSide,
  onBeforeCompile: shader => {
    shader.uniforms.time = m.userData.uniforms.time;
    shader.uniforms.currColor = m.userData.uniforms.currColor;
    shader.uniforms.prevColor = m.userData.uniforms.prevColor;
    shader.fragmentShader = `
        uniform float time;
      uniform vec3 currColor;
      uniform vec3 prevColor;
      ${shader.fragmentShader}
    `.replace(
        `#include <color_fragment>`,
      `#include <color_fragment>
      
      vec2 cUv = vUv - 0.5;
      float dist = length(cUv);
      vec3 col = prevColor;
      
      float ang = mod(atan(cUv.y, cUv.x) + PI * 3.5, PI2);
      float aRatio = 1. - ang / PI2;
      float slice = 1. - step(time, aRatio);
      col = mix(prevColor, currColor, slice);
      
      float innerCirc = 1. - step(0.25, dist);
      col = mix(col, diffuseColor.rgb, innerCirc);
      
      diffuseColor.rgb = col;
      
      `
    );
    
    console.log(shader.fragmentShader);
    
  }
})
m.defines = {
  "USE_UV": " "
};
m.userData = {
  uniforms: {
    time: {
      value: 0.5
    },
    currColor: {
        value: new THREE.Color(0xff00ff)
    },
    prevColor: {
        value: new THREE.Color(0x00ffff)
    }
  }
}

let o = new THREE.Mesh(g, m);
scene.add(o);

// vertex shader option
let g2 = new THREE.PlaneGeometry(1, 1, 180, 1);
let m2 = new THREE.MeshBasicMaterial({
    color: 0xffff00,
  wireframe: true,
  onBeforeCompile: shader => {
    shader.uniforms.rMin = m2.userData.uniforms.rMin;
    shader.uniforms.rMax = m2.userData.uniforms.rMax;
    shader.uniforms.arcRatio = m2.userData.uniforms.arcRatio;
    shader.vertexShader = `
        uniform float rMin;
      uniform float rMax;
      uniform float arcRatio;
      
      mat2 rot(float a){return mat2(cos(a), -sin(a), sin(a), cos(a));}
      
      ${shader.vertexShader}
    `.replace(
        `#include <begin_vertex>`,
      `#include <begin_vertex>
        
        float rDiff = rMax - rMin;
        float r = rMin + (rDiff * uv.y);
        
        float ang = PI2 * uv.x * arcRatio;
        
        transformed.xy = rot(ang) * vec2(0., r);
        
      `
    );
    console.log(shader.vertexShader);
  }
});
m2.userData = {
    uniforms: {
    rMin: {value: 2.5},
    rMax: {value: 5},
    arcRatio: {value: 0.25} // 0..1
  }
}
let o2 = new THREE.Mesh(g2, m2);
o2.position.z = 2;
scene.add(o2);

let clock = new THREE.Clock();

window.addEventListener("resize", onResize);

renderer.setAnimationLoop(_ => {
    let t = (clock.getElapsedTime() * 0.1) % 1;
    m.userData.uniforms.time.value = t;
  m2.userData.uniforms.arcRatio.value = t;
  renderer.render(scene, camera);
})

function onResize(){
    camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
}
</script>

Related