我用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']
}
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>
答案 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>