1

I am preparing some didactic materials about optical illusions. For this I decide to play around three.js. I’ve done illustrations for structure-from motion effect. The idea of this effect is that our visual system is always working in situation of insufficient information and should add lacking details. Thus, when we have light, shadows, perspective and so on we clearly perceive unambiguous shape (demo in snippet below). On this example we may clearly see that sphere rotates to right.

/* global THREE, getDefaultParams */

'use strict';

let camera;
let renderer;
const currentParameters = getDefaultParams();
// once everything is loaded, we run our Three.js stuff.
function init() {
  // create a scene, that will hold all our elements such as objects, cameras and lights.
  const scene = new THREE.Scene();

  // create a camera, which defines where we're looking at.
  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

  // create a render and set the size
  renderer = new THREE.WebGLRenderer();
  renderer.setClearColor(new THREE.Color(currentParameters.renderer.color));
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = currentParameters.renderer.shadows;

  // create the landscape
  const landscapeGeometry = new THREE.PlaneGeometry(
    currentParameters.landscape.width,
    currentParameters.landscape.height,
  );
  const landscapeMaterial = new THREE.MeshLambertMaterial({ color: currentParameters.landscape.color });
  const landscape = new THREE.Mesh(landscapeGeometry, landscapeMaterial);

  landscape.rotation.x = currentParameters.landscape.rotation.x;
  landscape.rotation.y = currentParameters.landscape.rotation.y;
  landscape.rotation.z = currentParameters.landscape.rotation.z;

  landscape.position.x = currentParameters.landscape.position.x;
  landscape.position.y = currentParameters.landscape.position.y;
  landscape.position.z = currentParameters.landscape.position.z;

  landscape.receiveShadow = currentParameters.landscape.shadows;
  scene.add(landscape);

  // create a SFM shapes
  let parentGeometry = new THREE.SphereGeometry(
    currentParameters.parentShape.size,
    currentParameters.parentShape.numberOfVertices,
    currentParameters.parentShape.numberOfVertices,
  );
  let parentMaterial = new THREE.MeshLambertMaterial({
    color: 0x7777ff,
    opacity: 0.3,
    transparent: true,
  });


  let parentShape = new THREE.Mesh(parentGeometry, parentMaterial); // new THREE.Mesh();

  const childShapeGeometry = new THREE.SphereGeometry(
    currentParameters.childShape.size,
    currentParameters.childShape.numberOfVertices,
    currentParameters.childShape.numberOfVertices,
  );
  const childShapeMaterial = new THREE.MeshLambertMaterial({
    color: currentParameters.childShape.color,
  });
  const childShapeTemplate = new THREE.Mesh(childShapeGeometry, childShapeMaterial);
  childShapeTemplate.castShadow = currentParameters.childShape.shadows;
  for (let i = 0, l = currentParameters.parentShape.numberOfChilds; i < l; i++) {
    const childShape = childShapeTemplate.clone();
    // add random dot to parent shape
    [childShape.position.x, childShape.position.y, childShape.position.z] =
    generateRandomDotOnSphere(0, 0, 0, currentParameters.parentShape.size);
    parentShape.add(childShape);
  }
  scene.add(parentShape);
  // add fixation point
  let fixationGeometry = new THREE.TorusKnotGeometry(3, 1);
  let fixationMaterial = new THREE.MeshLambertMaterial({ color: 0xffff00 });
  let fixationShape = new THREE.Mesh(fixationGeometry, fixationMaterial);
  fixationShape.castShadow = true;
  fixationShape.receiveShadow = true;
  scene.add(fixationShape);
  // position and point the camera to the center of the scene
  camera.position.copy({ x: 0, y: 0, z: 100 });
  camera.lookAt(parentShape.position);
  // add spotlight for the shadows
  const spotLight = new THREE.SpotLight(0xffffff);
  spotLight.position.set(-100, 100, 50);
  spotLight.castShadow = true;
  scene.add(spotLight);
  // Output
  document.getElementById('WebGL-output').appendChild(renderer.domElement);
  renderScene();

  function renderScene() {
    parentShape.rotation.y += 0.02;
    requestAnimationFrame(renderScene);
    renderer.render(scene, camera);
  }
}
function onResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
}


window.onload = init;
// listen to the resize events
window.addEventListener('resize', onResize, false);

// *********************************************
// *********HELPERS*****************************
function generateRandomDotOnSphere(x0, y0, z0, radius) {
/*
source: https://stackoverflow.com/questions/5531827/random-point-on-a-given-sphere
Returns a random point of a sphere, evenly distributed over the sphere.
The sphere is centered at (x0,y0,z0) with the passed in radius.
The returned point is returned as a three element array [x,y,z].
*/
  let u = Math.random();
  let v = Math.random();
  let theta = 2 * Math.PI * u;
  let phi = Math.acos(2 * v - 1);
  let x = x0 + (radius * Math.sin(phi) * Math.cos(theta));
  let y = y0 + (radius * Math.sin(phi) * Math.sin(theta));
  let z = z0 + (radius * Math.cos(phi));
  return [x, y, z];
}

function getDefaultParams() {
  // Define default parameters
  const defaultParameters = { // define all backgroun
    renderer: {
      color: 0xFFFFFF,
      shadows: true,
    }, // renderer

    landscape: {
      color: 0xFFFFFF,
      shadows: true,
      width: 80,
      height: 80,

      rotation: {
        x: -0.5 * Math.PI,
        y: 0,
        z: 0,
      }, // rotation

      position: {
        x: 10,
        y: -30,
        z: -20,
      }, // position
    }, // landscape
    parentShape: {
      numberOfVertices: 10,
      size: 20,
      numberOfChilds: 50,
    }, // parent shape
    childShape: {
      numberOfVertices: 5,
      size: 1,
      color: 0x7777ff,
      shadows: true,
    }, // child shape
  };
  return defaultParameters;
}
<!DOCTYPE html>

<html>

<head>
    <title>Structure_from_motion</title>
    <script src="https://threejs.org/build/three.min.js"></script>
</head>
<body>
<div id="description">
</div>
<!-- Div which will hold the Output -->
<div id="WebGL-output",style="heigh:100px;"></div>
<!-- Javascript code that runs our Three.js examples -->
<script src="demoSFM_simple.js"></script>
</body>
</html>

If I will remove shadows, perspective (OrthographicCamera), make ambient light sphere will change the direction of it’s rotation from time to time.

/*global THREE, getDefaultParams */

'use strict';

let camera;
let renderer;
const currentParameters = getDefaultParams();
// once everything is loaded, we run our Three.js stuff.
function init() {
  // create a scene, that will hold all our elements such as objects, cameras and lights.
  const scene = new THREE.Scene();

  // create a camera, which defines where we're looking at.
  camera = new THREE.OrthographicCamera( window.innerWidth / - 7, window.innerWidth / 7, window.innerHeight / 7, window.innerHeight / - 7, 1, 1000 );

  // create a render and set the size
  renderer = new THREE.WebGLRenderer();
  renderer.setClearColor(new THREE.Color(currentParameters.renderer.color));
  renderer.setSize(window.innerWidth, window.innerHeight);


  // create a SFM shapes
  let parentShape = new THREE.Mesh();

  const childShapeGeometry = new THREE.SphereGeometry(
    currentParameters.childShape.size,
    currentParameters.childShape.numberOfVertices,
    currentParameters.childShape.numberOfVertices,
  );
  const childShapeMaterial = new THREE.MeshBasicMaterial({
    color: currentParameters.childShape.color,
  });
  const childShapeTemplate = new THREE.Mesh(childShapeGeometry, childShapeMaterial);
  for (let i = 0, l = currentParameters.parentShape.numberOfChilds; i < l; i++) {
    const childShape = childShapeTemplate.clone();
    // add random dot to parent shape
    [childShape.position.x, childShape.position.y, childShape.position.z] =
    generateRandomDotOnSphere(0, 0, 0, currentParameters.parentShape.size);
    parentShape.add(childShape);
  }
  scene.add(parentShape);
  // add fixation point
  let fixationGeometry = new THREE.CircleGeometry(1, 10);
  let fixationMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
  let fixationShape = new THREE.Mesh(fixationGeometry, fixationMaterial);
  fixationShape.position.z = -100;

  scene.add(fixationShape);
  // position and point the camera to the center of the scene
  camera.position.copy({ x: 0, y: 0, z: 100 });
  camera.lookAt(parentShape.position);
  // add ambientLight for the shadows
  const ambientLight = new THREE.AmbientLight(0xffffff);
  ambientLight.position.set(-100, 100, 50);
  scene.add(ambientLight);
  // Output
  document.getElementById('WebGL-output').appendChild(renderer.domElement);
  renderScene();

  function renderScene() {
    parentShape.rotation.y += 0.02;
    requestAnimationFrame(renderScene);
    renderer.render(scene, camera);
  }
}

window.onload = init;


// *********************************************
// *********HELPERS*****************************
function generateRandomDotOnSphere(x0, y0, z0, radius) {
/*
source: https://stackoverflow.com/questions/5531827/random-point-on-a-given-sphere
Returns a random point of a sphere, evenly distributed over the sphere.
The sphere is centered at (x0,y0,z0) with the passed in radius.
The returned point is returned as a three element array [x,y,z].
*/
  let u = Math.random();
  let v = Math.random();
  let theta = 2 * Math.PI * u;
  let phi = Math.acos(2 * v - 1);
  let x = x0 + (radius * Math.sin(phi) * Math.cos(theta));
  let y = y0 + (radius * Math.sin(phi) * Math.sin(theta));
  let z = z0 + (radius * Math.cos(phi));
  return [x, y, z];
}
function getDefaultParams() {
  // Define default parameters
  const defaultParameters = { // define all backgroun
    renderer: {
      color: 0xFFFFFF,
    }, // renderer
    parentShape: {
      numberOfVertices: 10,
      size: 20,
      numberOfChilds: 50,
    }, // parent shape
    childShape: {
      numberOfVertices: 5,
      size: 1,
      color: 0x7777ff,
    }, // child shape
  };
  return defaultParameters;
}
<!DOCTYPE html>

<html>

<head>
    <title>Structure_from_motion</title>
    <script src="https://threejs.org/build/three.min.js"></script>
</head>
<body>
<!-- Div which will hold the Output -->
<div id="WebGL-output",style="heigh:100px;"></div>
<!-- Javascript code that runs our Three.js examples -->

<script src="ambiguous.js"></script>
</body>
</html>

GitHub repo.

General Question/Problem:

I am satisfied by demos above, but only as concepts. First demo looks ugly. I am not an artist at all and I ask you to help to make this (first) demo looks pretty and as much natural as possible. Later I plan to add GUI controls to switch off each of disambiguating factors to obtain second demo.

Detailed questions:

Besides main problem to make unambiguous demo more natural and interesting I have several concrete questions:

  • animation looks not smooth and sometimes slow. Is it a limitation of three.js or it is possible to speedup and smooth animation?

  • y coordinate of camera is zero same as centre of transparent sphere (parent shape), but y coordinates of small spheres centres are change during rotation (i put ruler to my monitor). But It should be on the same level if I understand correctly.

  • How do I smooth the shadows? (thanks TheJim01).

  • What is the Physical Based Rendering and will it helps me? (thanks TheJim01)

  • I need to switch from THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000) to THREE.OrthographicCamera(). How I should adjust parameters of OrthographicCamera to keep the size of the parent shape?

TO BE CONTINUED...

question will have bounty tomorrow

Community
  • 1
  • 1
zlon
  • 812
  • 8
  • 24
  • 1
    You can make your question fit SO rules better by asking a specific question. "Help me make this pretty," is not a question. "How do I smooth the shadows? Here is what I tried..." and "How can I use Physical Based Rendering to increase object realism? Here is what I tried..." are answerable questions. Post code in your question as appropriate to the questions asked. You may also link to your github should someone want to run the whole application. – TheJim01 Jan 03 '18 at 15:19
  • Thanks, I've edit question. – zlon Jan 04 '18 at 07:43
  • 1
    **1)** This is actually several questions. SO prefers posts to be single answerable questions (1 question, 1 answer) because it makes searching easier. Imagine trying to find an answer to a material question, but you see "Improve 3D animation for didactic usage" in the results. Confusing, right? **2)** It's important to share what you have tried. If we guess at your progress, we may make suggestions you've already eliminated, wasting everyone's time. – TheJim01 Jan 04 '18 at 13:58
  • In snippets I share what I have. The problem why I open this question I don't understand what is important. Concerning Your first point will it be better if i will open 5 question for the same code, but with a bit different text? – zlon Jan 05 '18 at 07:45
  • In short, yes. You can't accept multiple answers, so try to keep it 1 Question -> 1 Answer. **1.** How can I smooth the animations? **2.** Why do my moving points change vertical position? **3.** How can I smooth these shadows? **4.** How can I make my materials look more natural? **5.** How can I make `OrthographicCamera` retain the scale of `PerspectiveCamera`? ***But with that said***, please do some research before creating new questions. Most of these questions have already been asked/answered. – TheJim01 Jan 05 '18 at 15:03

0 Answers0