Ershad Qaderi
Ershad Qaderi

Reputation: 471

THREE.js - How to achieve this effect?

I have this Codepen demo: The Codepen demo link and I want to transform it into the effect than is on the background of the hero section of this website: The website link

Here are the images that may make this more clear:

This: The first state

To be changed into this: The requested state

I really don't know what to do at all! If someone could have helped me or even created a new Pen it would be awesome!

Thank you so much.

This is the JavaScript codes of the Codepen demo:

/**/ /* ---- CORE ---- */
/**/ const mainColor = '#070707',
/**/       secondaryColor = '#FF7F16',
/**/       bgColor = '#ffae1e';
/**/ let windowWidth = window.innerWidth,
/**/     windowHeight = window.innerHeight;
/**/ class Webgl {
/**/   constructor(w, h) {
/**/     this.meshCount = 0;
/**/     this.meshListeners = [];
/**/     this.renderer = new THREE.WebGLRenderer({ antialias: true });
/**/     this.renderer.setPixelRatio(window.devicePixelRatio);
/**/     this.renderer.setClearColor(new THREE.Color(bgColor));
/**/     this.scene = new THREE.Scene();
          // http://stackoverflow.com/questions/23450588/isometric-camera-with-three-js
          this.aspect = w / h;
          this.distance = 5;
          this.camera = new THREE.OrthographicCamera( - this.distance * this.aspect, this.distance * this.aspect,             this.distance, - this.distance, 1, 1000 );
          this.camera.position.set(20, 10, 20);
          this.camera.rotation.order = 'YXZ';
          this.camera.rotation.y = - Math.PI / 4;
          this.camera.rotation.x = Math.atan( - 1 / Math.sqrt( 2 ) );
/**/     this.dom = this.renderer.domElement;
          this.controls = new THREE.OrbitControls( this.camera, this.dom );
/**/     this.update = this.update.bind(this);
/**/     this.resize = this.resize.bind(this);
/**/     this.resize(w, h); // set render size
/**/   }
/**/   add(mesh) {
/**/     this.scene.add(mesh);
/**/     if (!mesh.update) return;
/**/     this.meshListeners.push(mesh.update);
/**/     this.meshCount++;
/**/   }
/**/   update() {
/**/     let i = this.meshCount;
/**/     while (--i >= 0) {
/**/       this.meshListeners[i].apply(this, null);
/**/     }
/**/     this.renderer.render(this.scene, this.camera);
/**/   }
/**/   resize(w, h) {
/**/    this.renderer.setSize(w, h);
        this.camera.left = -w / 100;
        this.camera.right = w / 100;
        this.camera.top = h / 100;
        this.camera.bottom = -h / 100;
        this.camera.updateProjectionMatrix();
/**/   }
/**/ }
/**/ const webgl = new Webgl(windowWidth, windowHeight);
/**/ document.body.appendChild(webgl.dom);
/**/
/**/
/* ---- CREATING ZONE ---- */

// PROPS / GUI 
const PRECISITION = 40;
const props = {
  displacement: 0.3, // 0 - 1
  displacementX: 0.8, // 0 - 1
  displacementY: 1, // 0 - 1
  speed: 1, // -20 - 20
  speedX: 1, // -20 - 20
  speedY: 1, // -20 - 20
  amplitude: 1, // 0 - 2
  height: 2, // -2 - 2
};

const gui = new dat.GUI();
gui.close();
gui.add(props, 'displacement', 0, 1);
gui.add(props, 'displacementX', 0, 1);
gui.add(props, 'displacementY', 0, 1);
gui.add(props, 'speed', -20, 20);
gui.add(props, 'speedX', 0, 20);
gui.add(props, 'speedY', 0, 20);
gui.add(props, 'amplitude', 0, 2);
gui.add(props, 'height', -1, 4);


// OBJECTS
class Plateau extends THREE.Object3D {
  constructor(size = 5, segment = 10, depth = 1) {
    super();
    this.size = size;
    this.segment = segment;
    this.depth = depth;
    this.part = this.size / this.segment;
    this.updateVertice = v => v;

    this.material = new THREE.MeshLambertMaterial({
      color: new THREE.Color(secondaryColor),
      // shading: THREE.FlatShading,
      side: THREE.DoubleSide,
     //  wireframe: true,
    });
    
    this.geometry = new THREE.PlaneGeometry(this.size, this.size, this.segment, this.segment);
    let i;
    for (i = 1; i < this.segment; i++) {
      this.geometry.vertices[i].add(new THREE.Vector3(0, -this.part, this.depth));
      this.geometry.vertices[this.geometry.vertices.length - i - 1].add(new THREE.Vector3(0, this.part, this.depth));
      const line = (i - 1) * (this.segment + 1);
      this.geometry.vertices[line + (this.segment + 1)].add(new THREE.Vector3(this.part, 0, this.depth));
      this.geometry.vertices[line + (this.segment * 2) + 1].add(new THREE.Vector3(-this.part, 0, this.depth));
    }
    this.geometry.vertices[0].add(new THREE.Vector3(this.part, -this.part, this.depth));
    this.geometry.vertices[this.segment].add(new THREE.Vector3(-this.part, -this.part, this.depth));
    this.geometry.vertices[this.geometry.vertices.length - this.segment - 1].add(new THREE.Vector3(this.part, this.part, this.depth));
    this.geometry.vertices[this.geometry.vertices.length - 1].add(new THREE.Vector3(-this.part, this.part, this.depth));

    this.mesh = new THREE.Mesh(this.geometry, this.material);
    
    this.add(this.mesh);
    this.rotation.set(Math.PI / 2, 0, 0);

    this.update = this.update.bind(this);
  }
  update() {
    this.geometry.verticesNeedUpdate = true;
    let j, k;
    for (j = 2; j <= this.segment; j++) {
      for (k = 0; k < (this.segment - 1); k++) {
        this.updateVertice(this.geometry.vertices[(j + this.segment) + ((this.segment + 1) * k)]);
      }
    }
    this.rotation.z += 0.002;
  }
  updatePlateau(callback) {
    this.updateVertice = callback;
  }
}

// START
const plateau = new Plateau(8, PRECISITION, 0);
plateau.position.y = -2;

const simplex = new SimplexNoise();
let t = 0;
plateau.updatePlateau((vertice) => {
  t += 0.00001 * props.speed;
  vertice.setZ((simplex.noise2D((vertice.x * props.displacementX * props.displacement) + (t * props.speedX), (vertice.y * props.displacementY * props.displacement) + (t *props.speedY)) * props.amplitude) - props.height);1});

const ambientLight = new THREE.AmbientLight(0xaaaaaa);
const pointLight = new THREE.PointLight(0xffffff);
pointLight.position.y = props.height;

// ADDS
webgl.add(plateau);
webgl.add(ambientLight);
webgl.add(pointLight);

/* ---- CREATING ZONE END ---- */
/**/
/**/
/**/ /* ---- ON RESIZE ---- */
/**/ function onResize() {
      console.log('resize')
/**/   windowWidth = window.innerWidth;
/**/   windowHeight = window.innerHeight;
/**/   webgl.resize(windowWidth, windowHeight);
/**/ }
/**/ window.addEventListener('resize', onResize);
/**/ window.addEventListener('orientationchange', onResize);
/**/ /* ---- LOOP ---- */
/**/ function loop(){
/**/ 	webgl.update();
/**/ 	requestAnimationFrame(loop);
/**/ }
/**/ loop();

Upvotes: 1

Views: 1016

Answers (1)

Martin Schuhfu&#223;
Martin Schuhfu&#223;

Reputation: 6986

So I did have a look at the site mentioned and did some reverse-engineering, documented here: https://codepen.io/usefulthink/pen/eGpgjL

As you can see in the pen, the original does all of the effect based on an unmodified PlaneBufferGeometry with a custom shader.

Most notably: it doesn't even do any lighting-calculations, but instead just darken the troughs in the geometry:

// from the fragment-shader
void main() {
  float val = min(quarticIn(1.0 - (vPositionY ) * (1.0 - vLL)), 1.06);
  gl_FragColor = vec4( val * mix(shadowColor, blendColor, val), 1.0);
  gl_FragColor = vec4(vPositionY * 10.0, 0.0, 0.0, 1.0);
}

The varying vPositionY is the displacement along the y-axis and vLL is basically a radial gradient from 0 in the center to 1 towards the outside.

You can see these two values if you replace the last line of the vertex-shader with these:

gl_FragColor = vec4(vPositionY * 10.0, 0.0, 0.0, 1.0);

or

gl_FragColor = vec4(vLL, 0.0, 0.0, 1.0);

That last value vLL is used to blend the deformed surface with the background, which is basically the only thing missing from your re-implementation. To see this in action, remove the vLL-component from the fragment-shader:

float val = min(quarticIn(1.0 - vPositionY), 1.06);
gl_FragColor = vec4( val * mix(shadowColor, blendColor, val), 1.0);

Now, to your main question – what you can do to get there with your own implementation: I can think of a few ways – the first one is just a bit of css to achieve the smooth fading into the background: https://codepen.io/usefulthink/pen/LzprZj

Another option is probably to move to using a ShaderMaterial like the one used in the demo. You could replicate the shading-behaviour and skip the displacement in the vertex-shader, as you are already doing this with javascript (it's arguably less performant, but also easier to reason about and manipulate).

Upvotes: 5

Related Questions