javascriptthree.jsorbitcontrols

How do I keep the color blending the same when orbiting a threejs object?


Currently when I rotate around the rectangle that I have drawn, the colors change because they're based on the normals, how can I keep the colors the same? I'm using the orbitcontrols library addon.

Attached below is the jsfiddle and the code:

/* import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/build/three.module.js'; */
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r110/examples/jsm/controls/OrbitControls.js';

let camera, scene, renderer, controls;

init();
render();

function init() {
    let width = $('#container').width();
    let height = $('#container').height();
  renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width,height);
  $("#container").html(renderer.domElement);
  
  camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 10);
  camera.position.z = 4;

  scene = new THREE.Scene();
  
  const geometry = new THREE.BufferGeometry();
  const vertices = [
    -2, -1, 0,
    2, -1, 0,
    2, 1, 0,
    -2, 1, 0
  ];

  const indices = [
    0, 1, 2, // first triangle
    2, 3, 0 // second triangle
  ];
  
  const normals = [
  1,0,0, // bottom left
  0,0,1, // bottom right
  0,0,1, // top right
  1,0,0  // top left
  ];
  
  geometry.setIndex(indices);
  geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
  geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));

  const material = new THREE.MeshNormalMaterial({vertexColors:true,flatShading:false});

  const mesh = new THREE.Mesh(geometry, material);
  scene.add(mesh);

  controls = new OrbitControls( camera, renderer.domElement );
  controls.target.set(0, 0, 0);
  controls.update();
  
  animate();

}

function animate() {
    requestAnimationFrame( animate );
    controls.update(); // only required if controls.enableDamping = true, or if controls.autoRotate = true
    render();
}

function render() {

  renderer.render(scene, camera);

}

https://jsfiddle.net/ZQVerse/2an9edhL/

When I set the normals attribute to try to get more than one color showing on the faces of the rectangle, they change based on how the camera is orbited around the rectangle. I was expecting the colors to be the same, regardless of rotation.

I looked online but I couldn't find any good examples of what would be causing this issue and how to fix it.

I suspect the normals are getting manipulated when the camera moves, so I probably have to update the normals somehow to keep the colors correct based on the rotation, but I don't know how to accomplish this.


Solution

  • The normal material always changes depending of the angle between the normal and the camera. Which is the default behavior because normals are mostly used for lightning. And you want to see if the surface is reflecting the light to the camara, so it's important to know if the normal of the surface is directly facing to the camera (very simple and may not be completely true)

    here is a repo with a new normal material which uses the world space and not the view space (Normal Position and Camera Position combined): https://github.com/maximeq/three-js-mesh-world-normal-material

    and the updated jsfiddle with a custom shader, which doesn't apply the view to the normal. https://jsfiddle.net/szamkh5w/1/

    const material = new THREE.ShaderMaterial( { vertexShader: [

    "varying vec3 vPositionW;",
    "varying vec3 vNormalW;",
    
    "void main() {",
    "   vNormalW = normalize( vec3( vec4( normal, 0.0 ) * modelMatrix ) );",
    "   gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
    "}" ].join( "\n" ),
    fragmentShader: [
    "varying vec3 vNormalW;",
    
    "void main() {",
    "   gl_FragColor = vec4( vNormalW, 1.);",
    
    "}"
    ].join( "\n" )
    } );