如何在ShaderMaterial上获得正确的阴影

时间:2019-07-18 11:27:37

标签: javascript three.js shader

我用InstancedBufferGeometry创建了很多盒子,然后用Perlin噪点更新位置。但是网格已经投射,并收到错误的阴影。如何计算右阴影?

vertexShader

attribute vec3 offset;
attribute vec4 orientation;
attribute vec3 color;

varying vec3 pos;
varying vec3 vNormal;
varying vec3 vWorldPosition;
varying vec3 vColor;

vec3 applyQuaternionToVector( vec4 q, vec3 v ){
    return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );
}

THREE.ShaderChunk["common"]
THREE.ShaderChunk["shadowmap_pars_vertex"]

void main() {
    vColor = color;

    vec3 vPosition = applyQuaternionToVector( orientation, position );
    pos = vPosition + offset;

    vNormal = normalMatrix * vec3(normal + normalize(offset) * 0.3);

    vec4 worldPosition = modelMatrix * vec4(pos, 1.0);
    vWorldPosition = worldPosition.xyz;

    gl_Position = projectionMatrix * modelViewMatrix * worldPosition;
    THREE.ShaderChunk["shadowmap_vertex"]
}

fragmentShader

THREE.ShaderChunk['common']
THREE.ShaderChunk['packing']
varying vec3 pos;
varying vec3 vNormal;
varying vec3 vWorldPosition;
varying vec3 vColor;

uniform vec3 lightPosition;

THREE.ShaderChunk['shadowmap_pars_fragment']
void main() {
    vec3 lightDirection = normalize(lightPosition + pos);

    float c = max(0.0, dot(vNormal, lightDirection)) * 2.;
    gl_FragColor = vec4(.3+c , .3+c , .3+c , 1.);
    THREE.ShaderChunk['shadowmap_fragment']
}

demo link is here

three.js r.106
谢谢

var scene, camera, renderer;
var plane, temp, vnh, point;
var radius = 10;
var stats = new Stats();
var start = Date.now();
var options = {
  scale: 200,
  density: 2.5
}
var currentQ = new THREE.Quaternion();
var initedBoxes = false;

var init = function() {
  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
  camera.position.z = 25;
  var controls = new THREE.OrbitControls(camera);

  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  document.body.appendChild(renderer.domElement);

  var gui = new dat.GUI({
    width: 300
  });
  gui.add(options, 'scale').min(0).max(200).name('Scale');
  gui.add(options, 'density').min(0).max(10).name('Density');

  // stats
  stats.showPanel(0);
  stats.domElement.style.position = 'fixed';
  stats.domElement.style.top = 0;
  document.body.appendChild(stats.domElement);

  initLights();
  initSphere();
  initBoxes();

  renderer.setAnimationLoop(function() {
    update();
    render();
  });
}

var initLights = function() {
  var ambientLight = new THREE.AmbientLight(0x555555);
  scene.add(ambientLight);
  light1 = new THREE.SpotLight(0xffffff, 2, 200, 10);
  light1.position.set(-30, 30, 40);
  light1.castShadow = true;
  scene.add(light1);

  light1Helper = new THREE.SpotLightHelper(light1, 0xffffff);
  scene.add(light1Helper);
}

var initSphere = function() {
  var geometry = new THREE.IcosahedronGeometry(radius, 3);
  var material = new THREE.MeshPhongMaterial({
    color: 0x999999,
    wireframe: false
  });
  material.shininess = 0;
  sphere = new THREE.Mesh(geometry, material);
  // sphere.castShadow = true;
  // sphere.receiveShadow = true;
  // scene.add(sphere);

  tempGeo = new THREE.Geometry();
  tempGeo.copy(geometry);
  temp = new THREE.Mesh(tempGeo, material);

  var pGeo = new THREE.PlaneGeometry(100, 100, 1, 1);
  plane = new THREE.Mesh(pGeo, material);
  plane.receiveShadow = true;
  plane.position.y = -radius - 3;
  plane.rotation.x = -90 * Math.PI / 180;
  scene.add(plane);
}

var initBoxes = function() {
  initedBoxes = true;
  var bufferGeometry = new THREE.BoxBufferGeometry(1, 1, 1);
  var geometry = new THREE.InstancedBufferGeometry();
  geometry.index = bufferGeometry.index;
  geometry.attributes.position = bufferGeometry.attributes.position;
  // geometry.attributes.normal = bufferGeometry.attributes.normal;

  // per instance data
  var offsets = [];
  var orientations = [];
  var colors = [];
  var vector = new THREE.Vector4();
  var x, y, z, w;
  var cscale = chroma.scale(['#ff0000', '#00ff00', '#0000ff']).classes(10);

  for (var i = 0; i < sphere.geometry.faces.length; i++) {
    center = getCenter(sphere.geometry.faces[i]);
    x = center.x;
    y = center.y;
    z = center.z;
    vector.set(x, y, z, 0).normalize();

    offsets.push(x + vector.x, y + vector.y, z + vector.z);

    // rotate

    rotation = getRotation(sphere.geometry.faces[i].normal);
    vector.copy(rotation).normalize();

    orientations.push(vector.x, vector.y, vector.z, vector.w);

    var color = chroma(cscale(THREE.Math.randFloat(0, 1))).brighten(1).hex();
    color = new THREE.Color(color);
    colors.push(color.r, color.g, color.b);
  }
  offsetAttribute = new THREE.InstancedBufferAttribute(new Float32Array(offsets), 3);
  orientationAttribute = new THREE.InstancedBufferAttribute(new Float32Array(orientations), 4);
  colorAttribute = new THREE.InstancedBufferAttribute(new Float32Array(colors), 3);

  geometry.addAttribute('offset', offsetAttribute);
  geometry.addAttribute('orientation', orientationAttribute);
  geometry.addAttribute('color', colorAttribute);

  var material = new THREE.ShaderMaterial({
    lights: true,
    uniforms: THREE.UniformsUtils.merge([
      THREE.UniformsLib["shadowmap"],
      THREE.UniformsLib["lights"],
      {
        lightPosition: {
          type: 'v3',
          value: light1.position
        },
        time: {
          type: 'f',
          value: 0
        }
      }
    ]),
    vertexShader: [
      'attribute vec3 offset;',
      'attribute vec4 orientation;',
      'attribute vec3 color;',

      'varying vec3 pos;',
      'varying vec3 vNormal;',
      'varying vec3 vWorldPosition;',
      'varying vec3 vColor;',

      'vec3 applyQuaternionToVector( vec4 q, vec3 v ){',
      'return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );',
      '}',

      THREE.ShaderChunk["common"],
      THREE.ShaderChunk["shadowmap_pars_vertex"],

      'void main() {',
      'vColor = color;',

      'vec3 vPosition = applyQuaternionToVector( orientation, position );',
      'pos = vPosition + offset;',

      'vNormal = normalMatrix * vec3(normal + normalize(offset) * 0.3);',

      'vec4 worldPosition = modelMatrix * vec4(pos, 1.0);',
      'vWorldPosition = worldPosition.xyz;',

      'gl_Position = projectionMatrix * modelViewMatrix * worldPosition;',
      THREE.ShaderChunk["shadowmap_vertex"],
      '}'
    ].join('\n'),
    fragmentShader: [
      THREE.ShaderChunk['common'],
      THREE.ShaderChunk['packing'],
      'varying vec3 pos;',
      'varying vec3 vNormal;',
      'varying vec3 vWorldPosition;',
      'varying vec3 vColor;',

      'uniform vec3 lightPosition;',

      THREE.ShaderChunk['shadowmap_pars_fragment'],
      'void main() {',
      'vec3 lightDirection = normalize(lightPosition + pos);',

      'float c = max(0.0, dot(vNormal, lightDirection)) * 2.;',
      // 'gl_FragColor = vec4(vColor.r + c , vColor.g + c , vColor.b + c , 1.);',
      'gl_FragColor = vec4(.3+c , .3+c , .3+c , 1.);',
      THREE.ShaderChunk['shadowmap_fragment'],
      '}'
    ].join('\n')
  });

  boxes = new THREE.Mesh(geometry, material);
  boxes.castShadow = true;
  boxes.receiveShadow = true;
  scene.add(boxes);
}

// find center position from 3 vertices
function getCenter(face) {
  var centroid = new THREE.Vector3(0, 0, 0);
  centroid.add(sphere.geometry.vertices[face.a]);
  centroid.add(sphere.geometry.vertices[face.b]);
  centroid.add(sphere.geometry.vertices[face.c]);
  centroid.divideScalar(3);
  return centroid;
}

function getRotation(normal) {
  var planeVector1 = new THREE.Vector3(0, 1, 0);
  var matrix1 = new THREE.Matrix4();
  var quaternion = new THREE.Quaternion().setFromUnitVectors(planeVector1, normal);
  var matrix = new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
  var a = new THREE.Euler();
  a.setFromRotationMatrix(matrix, 'XYZ')
  // return a.toVector3();
  return quaternion;
}

// noise.seed(Math.random());
var update = function() {
  stats.update();
  var timer = (Date.now() - start) * .0002;

  // animate vertices of sphere with noise
  for (var i = 0; i < sphere.geometry.vertices.length; i++) {
    var p = sphere.geometry.vertices[i];
    var tp = temp.geometry.vertices[i];
    var n = noise.perlin3(tp.x / (10 - options.density) + timer, tp.y / (10 - options.density) + timer, tp.z / (10 - options.density) + timer) * options.scale;

    // move vertices with noise
    p.normalize().multiplyScalar(radius + n / 100);
  }

  sphere.geometry.verticesNeedUpdate = true;
  sphere.geometry.normalsNeedUpdate = true;
  sphere.geometry.computeVertexNormals();
  sphere.geometry.computeFaceNormals();


  // animate boxes
  if (initedBoxes) {
    for (var i = 0; i < sphere.geometry.faces.length; i++) {
      center = getCenter(sphere.geometry.faces[i]);
      x = center.x;
      y = center.y;
      z = center.z;
      offsetAttribute.setXYZ(i, center.x, center.y, center.z);

      rotation = getRotation(sphere.geometry.faces[i].normal);
      currentQ.copy(rotation).normalize();
      orientationAttribute.setXYZW(i, currentQ.x, currentQ.y, currentQ.z, currentQ.w);
    }
    offsetAttribute.needsUpdate = true;
    orientationAttribute.needsUpdate = true;
  }
}


var animate = function() {
  draw();
}

var render = function() {
  renderer.render(scene, camera);
}

init();
<html>

<head>
  <title>Instanced buffer test</title>
  <style>
    * {
      padding: 0px;
      margin: 0px;
    }
    
    html,
    body {
      overflow: hidden;
    }
  </style>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script>
  <script src="https://josephg.github.io/noisejs/perlin.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.3/chroma.min.js"></script>
  <script src="https://unpkg.com/three@0.85.0/examples/js/controls/OrbitControls.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>
</head>

<body>
</body>

</html>

1 个答案:

答案 0 :(得分:1)

如果希望网格物体投射适当的阴影,对于网格物体的ShaderMaterial属性使用.material是不够的。
点光源的阴影取决于.customDepthMaterial属性。这意味着您必须编写一个着色器(ShaderMaterial),以通过模型转换将对象渲染到阴影贴图。


网格的阴影被阴影摄像机的近平面部分裁剪。
通过设置透视阴影相机(light1.shadow.camera)的.near属性,使用更近的近平面(例如0.1而不是0.5)来解决此问题:

light1 = new THREE.SpotLight( 0xffffff, 2, 200, 10 );
light1.position.set( -30, 30, 40 );
light1.castShadow = true;
light1.shadow.mapSize.x = 2048;
light1.shadow.mapSize.y = 2048;
light1.shadow.camera.near = 0.1;
scene.add(light1);

此外,着色器中存在一些问题。声明

  
vec3 lightDirection = normalize(lightPosition + pos);

没有任何意义,因为方向是从一个点到另一个点的向量,它是由(-)运算符(例如lightPosition - pos)计算出来的。
但这不能解决问题,因为lightPosition是世界空间中的一个点,而pos是模型空间中的一个点。

您必须在视图空间中计算向量,因为

  
float c = max(0.0, dot(vNormal, lightDirection)) * 2.;

vNormal是视图空间中的向量。

我建议在顶点着色器(vLightDir)中计算光照方向,并将其传递给片段着色器。
计算世界空间的方向,并通过视图矩阵(mat3(viewMatrix))的高3x3对其进行转换:

vLightDir = mat3(viewMatrix) * (lightPosition - vWorldPosition);

由于worldPosition(顾名思义)是世界空间中的一个位置,因此必须由viewMatrix而不是modelViewMatrix进行转换:

gl_Position = projectionMatrix * viewMatrix * worldPosition;

请注意,modelViewMatrix从对象空间转换为视图空间。 viewMatrix从世界空间转换为视图空间。另请参见WebGLProgram

顶点着色器:

vertexShader:
[
    'attribute vec3 offset;',
    'attribute vec4 orientation;',
    'attribute vec3 color;',

    'varying vec3 pos;',
    'varying vec3 vNormal;',
    'varying vec3 vWorldPosition;',
    'varying vec3 vColor;',
    'varying vec3 vLightDir;',

    'uniform vec3 lightPosition;',

    'vec3 applyQuaternionToVector( vec4 q, vec3 v ){',
        'return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );',
    '}',

    THREE.ShaderChunk["common"],
    THREE.ShaderChunk["shadowmap_pars_vertex"],

    'void main() {',
        'vColor = color;',

        'vec3 vPosition = applyQuaternionToVector( orientation, position );',
        'pos = vPosition + offset;',

        'vNormal = normalMatrix * vec3(normal + normalize(offset) * 0.3);',

        'vec4 worldPosition = modelMatrix * vec4(pos, 1.0);',
        'vWorldPosition = worldPosition.xyz;',

        'vLightDir = mat3(viewMatrix) * (lightPosition - vWorldPosition);',

        'gl_Position = projectionMatrix * viewMatrix * worldPosition;',
        THREE.ShaderChunk["shadowmap_vertex"],
    '}'
].join('\n')

Fragmetn着色器:

fragmentShader:
[
    THREE.ShaderChunk['common'],
    THREE.ShaderChunk['packing'],
    'varying vec3 pos;',
    'varying vec3 vNormal;',
    'varying vec3 vWorldPosition;',
    'varying vec3 vColor;',
    'varying vec3 vLightDir;',

    THREE.ShaderChunk['shadowmap_pars_fragment'],
    'void main() {',
        'vec3 lightDirection = normalize(vLightDir);',

        'float c = max(0.0, dot(vNormal, lightDirection)) * 2.;',
        // 'gl_FragColor = vec4(vColor.r + c , vColor.g + c , vColor.b + c , 1.);',
        'gl_FragColor = vec4(.3+c , .3+c , .3+c , 1.);',
        THREE.ShaderChunk['shadowmap_fragment'],
    '}'
].join('\n')

var scene,camera,renderer;
var plane, temp, vnh, point;
var radius = 10;
var stats = new Stats();
var start = Date.now();
var options = {
    scale:200,
    density:2.5
}
var currentQ = new THREE.Quaternion();
var initedBoxes = false;

var init = function(){
    scene = new THREE.Scene();
    camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
    camera.position.z = 25;
    var controls = new THREE.OrbitControls( camera );

    renderer = new THREE.WebGLRenderer();
    renderer.setPixelRatio( window.devicePixelRatio );
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    document.body.appendChild( renderer.domElement );
    
    window.onresize = function() {
      renderer.setSize(window.innerWidth, window.innerHeight);
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
    }
    
    var gui = new dat.GUI({width: 300});
    gui.add(options,'scale').min(0).max(200).name('Scale');
    gui.add(options,'density').min(0).max(10).name('Density');

    // stats
    stats.showPanel(0);
    stats.domElement.style.position = 'fixed';
    stats.domElement.style.top = 0;
    document.body.appendChild( stats.domElement );

    initLights();
    initSphere();
    initBoxes();

    renderer.setAnimationLoop(function(){
        update();
        render();
    });
}

var initLights = function(){
    var ambientLight = new THREE.AmbientLight( 0x555555 );
    scene.add( ambientLight );
    light1 = new THREE.SpotLight( 0xffffff, 2, 200, 10 );
    light1.position.set( -30, 30, 40 );
    light1.castShadow = true;
    light1.shadow.mapSize.x = 2048;
    light1.shadow.mapSize.y = 2048;
    light1.shadow.camera.near = 0.1;
    scene.add(light1);

    light1Helper = new THREE.SpotLightHelper(light1, 0xffffff);
    scene.add(light1Helper);
}

var initSphere = function(){
    var geometry = new THREE.IcosahedronGeometry( radius, 3 );
    var material = new THREE.MeshPhongMaterial( {color: 0x999999, wireframe:false} );
    // var material = new THREE.ShaderMaterial({
    //     vertexShader: document.getElementById('vertexShader').innerHTML,
    //     fragmentShader: document.getElementById('fragmentShader').innerHTML
    // });
    material.shininess = 0;
    sphere = new THREE.Mesh( geometry, material );
    sphere.castShadow = true;
    sphere.receiveShadow = true;
    // scene.add(sphere);

    tempGeo = new THREE.Geometry();
    tempGeo.copy(geometry);
    temp = new THREE.Mesh( tempGeo, material );

    var pGeo = new THREE.PlaneGeometry(200, 200, 1, 1);
    plane = new THREE.Mesh( pGeo, material );
    plane.receiveShadow = true;
    plane.position.y = -radius - 10;
    plane.rotation.x = -90 * Math.PI/180;
    scene.add(plane);
}

var initBoxes = function(){
    initedBoxes = true;
    var bufferGeometry = new THREE.BoxBufferGeometry(1,1,1);
    var geometry = new THREE.InstancedBufferGeometry();
    geometry.index = bufferGeometry.index;
    geometry.attributes.position = bufferGeometry.attributes.position;
    // geometry.attributes.normal = bufferGeometry.attributes.normal;

    // per instance data
    var offsets = [];
    var orientations = [];
    var colors = [];
    var vector = new THREE.Vector4();
    var x, y, z, w;
    var cscale = chroma.scale(['#ff0000','#00ff00','#0000ff']).classes(10);

    for(var i=0; i<sphere.geometry.faces.length; i++){
        center = getCenter(sphere.geometry.faces[i]);
        x = center.x;
        y = center.y;
        z = center.z;
        vector.set( x, y, z, 0 ).normalize();

        offsets.push( x + vector.x, y + vector.y, z + vector.z );

        // rotate

        rotation = getRotation(sphere.geometry.faces[i].normal);
        vector.copy(rotation).normalize();

        orientations.push( vector.x, vector.y, vector.z, vector.w );

        var color = chroma(cscale(THREE.Math.randFloat(0,1))).brighten(1).hex();
        color = new THREE.Color(color);
        colors.push(color.r, color.g, color.b);
    }
    offsetAttribute = new THREE.InstancedBufferAttribute( new Float32Array( offsets ), 3 );
    orientationAttribute = new THREE.InstancedBufferAttribute( new Float32Array( orientations ), 4 );
    colorAttribute = new THREE.InstancedBufferAttribute( new Float32Array( colors ), 3 );

    geometry.addAttribute( 'offset', offsetAttribute );
    geometry.addAttribute( 'orientation', orientationAttribute );
    geometry.addAttribute( 'color', colorAttribute );

    var material = new THREE.ShaderMaterial( {
        lights: true,
        uniforms: THREE.UniformsUtils.merge([
            // THREE.UniformsLib["shadowmap"],
            THREE.UniformsLib["lights"],
            {
                lightPosition: {type: 'v3', value: light1.position},
                time: {type: 'f', value: 0}
            }
        ]),
        vertexShader:
        [
            'attribute vec3 offset;',
            'attribute vec4 orientation;',
            'attribute vec3 color;',

            'varying vec3 pos;',
            'varying vec3 vNormal;',
            'varying vec3 vWorldPosition;',
            'varying vec3 vColor;',
            'varying vec3 vLightDir;',
                                    
            'uniform vec3 lightPosition;',
    
            'vec3 applyQuaternionToVector( vec4 q, vec3 v ){',
                'return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );',
            '}',

            THREE.ShaderChunk["common"],
            THREE.ShaderChunk["shadowmap_pars_vertex"],

            'void main() {',
                'vColor = color;',

                'vec3 vPosition = applyQuaternionToVector( orientation, position );',
                'pos = vPosition + offset;',
                
                'vNormal = normalMatrix * vec3(normal + normalize(offset) * 0.3);',

                'vec4 worldPosition = modelMatrix * vec4(pos, 1.0);',
                'vWorldPosition = worldPosition.xyz;',

                'vLightDir = mat3(viewMatrix) * (lightPosition - vWorldPosition);',

                'gl_Position = projectionMatrix * viewMatrix * worldPosition;',
                THREE.ShaderChunk["shadowmap_vertex"],
            '}'
        ].join('\n')
        ,
        fragmentShader:
        [
            THREE.ShaderChunk['common'],
            THREE.ShaderChunk['packing'],
            'varying vec3 pos;',
            'varying vec3 vNormal;',
            'varying vec3 vWorldPosition;',
            'varying vec3 vColor;',
            'varying vec3 vLightDir;',

THREE.ShaderChunk['shadowmap_pars_fragment'],
            'void main() {',
                'vec3 lightDirection = normalize(vLightDir);',
                
                'float c = max(0.0, dot(vNormal, lightDirection)) * 2.;',
                // 'gl_FragColor = vec4(vColor.r + c , vColor.g + c , vColor.b + c , 1.);',
                'gl_FragColor = vec4(.3+c , .3+c , .3+c , 1.);',
                THREE.ShaderChunk['shadowmap_fragment'],
            '}'
        ].join('\n')
    });

    boxes = new THREE.Mesh( geometry, material );
    boxes.castShadow = true;
    boxes.receiveShadow = true;

    boxes.customDepthMaterial = new THREE.ShaderMaterial({
        vertexShader:
        [
            'attribute vec3 offset;',
            'attribute vec4 orientation;',

            'varying vec3 vWorldPosition;',

            'vec3 applyQuaternionToVector( vec4 q, vec3 v ){',
                'return v + 2.0 * cross( q.xyz, cross( q.xyz, v ) + q.w * v );',
            '}',

            'void main() {',
                'vec3 vPosition = applyQuaternionToVector( orientation, position );',
                'vec3 pos = vPosition + offset;',

                'vec4 worldPosition = modelMatrix * vec4(pos, 1.0);',
                'vWorldPosition = worldPosition.xyz;',
                
                'gl_Position = projectionMatrix * viewMatrix * worldPosition;',
            '}',
        ].join('\n')
        ,
        fragmentShader: THREE.ShaderLib.distanceRGBA.fragmentShader,
        uniforms: material.uniforms
    });
    scene.add(boxes);
}

// find center position from 3 vertices
function getCenter(face){
    var centroid = new THREE.Vector3(0,0,0);
    centroid.add(sphere.geometry.vertices[face.a]);
    centroid.add(sphere.geometry.vertices[face.b]);
    centroid.add(sphere.geometry.vertices[face.c]);
    centroid.divideScalar(3);
    return centroid;
}

function getRotation(normal){
    var planeVector1 = new THREE.Vector3(0,1,0);
    var matrix1 = new THREE.Matrix4();
    var quaternion = new THREE.Quaternion().setFromUnitVectors(planeVector1, normal);
    var matrix = new THREE.Matrix4().makeRotationFromQuaternion(quaternion);
    var a = new THREE.Euler( );
    a.setFromRotationMatrix ( matrix, 'XYZ' )
    // return a.toVector3();
    return quaternion;
}	

// noise.seed(Math.random());
var update = function(){
    stats.update();
    var timer = (Date.now() - start) * .0002;

    // animate vertices of sphere with noise
    for(var i=0; i<sphere.geometry.vertices.length; i++){
        var p = sphere.geometry.vertices[i];
        var tp = temp.geometry.vertices[i];
        var n = noise.perlin3(tp.x / (10-options.density) + timer, tp.y / (10-options.density)  + timer, tp.z / (10-options.density)  + timer) * options.scale;

        // move vertices with noise
        p.normalize().multiplyScalar(radius + n/100);
    }
    
    sphere.geometry.verticesNeedUpdate = true;
    sphere.geometry.normalsNeedUpdate = true;
    sphere.geometry.computeVertexNormals();
    sphere.geometry.computeFaceNormals();

    
    // animate boxes
    if(initedBoxes){
        for(var i=0; i<sphere.geometry.faces.length; i++){
            center = getCenter(sphere.geometry.faces[i]);
            x = center.x;
            y = center.y;
            z = center.z;
            offsetAttribute.setXYZ(i, center.x, center.y, center.z);

            rotation = getRotation(sphere.geometry.faces[i].normal);
            currentQ.copy(rotation).normalize();
            orientationAttribute.setXYZW( i, currentQ.x, currentQ.y, currentQ.z, currentQ.w );
        }
        offsetAttribute.needsUpdate = true;
        orientationAttribute.needsUpdate = true;
    }
}


var animate = function(){
    draw();
}

var render = function(){
    renderer.render( scene, camera );
}

init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script>
<script src="https://josephg.github.io/noisejs/perlin.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.0.3/chroma.min.js"></script>
<script src="https://unpkg.com/three@0.85.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5/dat.gui.min.js"></script>