Synching two meshes, one with ShaderMaterial - three.js

I've got two meshes created from the same geometry and running the same animation. If I do absolutely nothing to the meshes they stay in perfect lockstep, which is what I want. But if I change their position or rotation they go out of sync.
Here's a jsfiddle of an example. There's a blob of minified js at the top which contains the contents of EffectComposer.js, ShaderPass.js, RenderPass.js, MaskPass.js, and CopyShader.js from the r77 source---the three.js CDN doesn't contain them and jsfiddle won't work with linking to them from three.js github repo. The start of the example problem is with the definition of THREE.OutlineShader:
THREE.OutlineShader = {
uniforms: {
"offset": {
type: "f",
value: 2.0
},
"boneTexture": {
type: "t",
value: null
},
"boneTextureWidth": {
type: "i",
value: null
},
"boneTextureHeight": {
type: "i",
value: null
},
},
vertexShader: [
"uniform sampler2D boneTexture;",
"uniform int boneTextureWidth;",
"uniform int boneTextureHeight;",
"uniform float offset;",
"mat4 getBoneMatrix(const in float i) {",
"float j = i * 4.0;",
"float x = mod(j, float(boneTextureWidth));",
"float y = floor(j / float(boneTextureWidth));",
"float dx = 1.0 / float(boneTextureWidth);",
"float dy = 1.0 / float(boneTextureHeight);",
"y = dy * (y + 0.5);",
"vec4 v1 = texture2D(boneTexture, vec2(dx * (x + 0.5), y));",
"vec4 v2 = texture2D(boneTexture, vec2(dx * (x + 1.5), y));",
"vec4 v3 = texture2D(boneTexture, vec2(dx * (x + 2.5), y));",
"vec4 v4 = texture2D(boneTexture, vec2(dx * (x + 3.5), y));",
"mat4 bone = mat4(v1, v2, v3, v4);",
"return bone;",
"}",
"void main() {",
"mat4 boneMatX = getBoneMatrix(skinIndex.x);",
"mat4 boneMatY = getBoneMatrix(skinIndex.y);",
"mat4 boneMatZ = getBoneMatrix(skinIndex.z);",
"mat4 boneMatW = getBoneMatrix(skinIndex.w);",
"vec4 skinVertex = vec4(position + normal * offset, 1.0);",
"vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
"skinned += boneMatY * skinVertex * skinWeight.y;",
"skinned += boneMatZ * skinVertex * skinWeight.z;",
"skinned += boneMatW * skinVertex * skinWeight.w;",
"vec4 mvPosition = modelViewMatrix * skinned;",
"gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n"),
fragmentShader: [
"uniform int boneTextureWidth;",
"void main() {",
"gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);",
"}"
].join("\n")
};
var camera, light, renderer, composer, clock;
var sceneMain, sceneOutline;
var meshMain = null,
meshOutline = null;
var mixerMain, mixerOutline;
var animMain, animOutline;
var height = 500,
width = 500;
var objData = '{"metadata":{"formatVersion":3.1,"generatedBy":"Blender 2.7 Exporter","vertices":24,"faces":22,"normals":18,"colors":0,"uvs":[],"materials":1,"morphTargets":0,"bones":2},"scale":1.000000,"materials":[{"DbgColor":15658734,"DbgIndex":0,"DbgName":"Material","blending":"NormalBlending","colorDiffuse":[0.1569801711586143,0.17312412519937936,0.6400000190734865],"colorEmissive":[0.0,0.0,0.0],"colorSpecular":[0.2535329759120941,0.0,0.007157782092690468],"depthTest":true,"depthWrite":true,"shading":"Lambert","specularCoef":50,"opacity":1.0,"transparent":false,"vertexColors":false}],"vertices":[1.51034,-1,-1,1.51034,-1,1,-0.489661,-1,1,-0.489661,-1,-1,1.51034,1,-1,1.51034,1,1,-0.489662,1,1,-0.489661,1,-1,3.23233,-1,-0.999999,3.23233,-1,1,3.23233,1,-0.999999,3.23233,1,1,-1.98848,-1,1,-1.98848,-1,-1,-1.98848,1,0.999999,-1.98848,1,-1,1.51034,-5.70811,-1,1.51034,-5.70811,1,3.23233,-5.70811,-0.999999,3.23233,-5.70811,1,-0.489661,-5.62708,1,-0.48966,-5.62708,-1,-1.98848,-5.62708,1,-1.98848,-5.62708,-1],"morphTargets":[],"normals":[-0.301492,-0.301492,-0.904508,-0.301492,-0.301492,0.904508,0.301492,-0.301492,0.904508,0.301492,-0.301492,-0.904508,0,0.707083,-0.707083,0,0.707083,0.707083,0.577349,0.577349,0.577349,0.577349,0.577349,-0.577349,-0.577349,0.577349,-0.577349,-0.577349,0.577349,0.577349,0.707083,0,-0.707083,0.707083,0,0.707083,0.577349,-0.577349,-0.577349,-0.577349,-0.577349,-0.577349,-0.707083,0,0.707083,-0.707083,0,-0.707083,-0.577349,-0.577349,0.577349,0.577349,-0.577349,0.577349],"colors":[],"uvs":[],"faces":[35,0,1,2,3,0,0,1,2,3,35,4,5,11,10,0,4,5,6,7,35,1,5,6,2,0,1,5,5,2,35,6,7,15,14,0,5,4,8,9,35,4,0,3,7,0,4,0,3,4,35,8,10,11,9,0,10,7,6,11,35,5,1,9,11,0,5,1,11,6,35,0,4,10,8,0,0,4,7,10,35,0,8,18,16,0,0,10,12,13,35,12,14,15,13,0,14,9,8,15,35,7,3,13,15,0,4,3,15,8,35,2,6,14,12,0,2,5,9,14,35,2,12,22,20,0,2,14,16,17,35,17,16,18,19,0,16,13,12,17,35,9,1,17,19,0,11,1,16,17,35,8,9,19,18,0,10,11,17,12,35,1,0,16,17,0,1,0,13,16,35,21,20,22,23,0,12,17,16,13,35,13,3,21,23,0,15,3,12,13,35,12,13,23,22,0,14,15,13,16,35,3,2,20,21,0,3,2,17,12,35,4,7,6,5,0,4,4,5,5],"bones":[{"parent":-1,"name":"leg.R","pos":[-1.24994,0.43791,0.191651],"rotq":[-0.00523508,-0.706875,-0.707296,-0.00579363],"scl":[1,1,1]},{"parent":-1,"name":"leg.L","pos":[2.49995,0.280193,0.066556],"rotq":[-0.00523507,-0.706875,-0.707296,-0.00579363],"scl":[1,1,1]}],"skinIndices":[1,0,1,0,0,1,0,1,1,0,1,0,0,1,0,1,1,-1,1,-1,1,-1,1,-1,0,-1,0,-1,0,-1,0,1,1,-1,1,-1,1,-1,1,-1,0,-1,0,-1,0,-1,0,-1],"skinWeights":[0.928373,0.0680346,0.937978,0.0587701,0.949888,0.0463839,0.937937,0.0591265,0.821856,0.122838,0.79233,0.145709,0.876929,0.0825711,0.830405,0.115734,0.989868,0,0.992278,0,0.968805,0,0.966368,0,0.993762,0,0.989439,0,0.978637,0,0.962526,0.00173758,0.997334,0,0.997776,0,0.999229,0,0.999402,0,0.998345,0,0.997508,0,0.99955,0,0.999106,0],"animations":[{"name":"ArmatureAction","fps":24,"length":0.416667,"hierarchy":[{"parent":-1,"keys":[{"time":0,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00643926,-0.522937,-0.852335,-0.0044168],"scl":[1,1,1]},{"time":0.0416667,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00683821,-0.561328,-0.827555,-0.00415746],"scl":[1,1,1]},{"time":0.0833333,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00791262,-0.665775,-0.746103,-0.00335735],"scl":[1,1,1]},{"time":0.125,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00910612,0.78443,0.620147,0.00222404],"scl":[1,1,1]},{"time":0.166667,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00983298,0.859093,0.511724,0.00131562],"scl":[1,1,1]},{"time":0.208333,"pos":[-1.24994,0.43791,0.191651],"rot":[0.0100438,0.881367,0.472325,0.000997505],"scl":[1,1,1]},{"time":0.25,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00983298,0.859093,0.511724,0.00131562],"scl":[1,1,1]},{"time":0.291667,"pos":[-1.24994,0.43791,0.191651],"rot":[0.00910612,0.78443,0.620147,0.00222404],"scl":[1,1,1]},{"time":0.333333,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00791262,-0.665775,-0.746103,-0.00335735],"scl":[1,1,1]},{"time":0.375,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00683821,-0.561328,-0.827555,-0.00415746],"scl":[1,1,1]},{"time":0.416667,"pos":[-1.24994,0.43791,0.191651],"rot":[-0.00643926,-0.522937,-0.852335,-0.0044168],"scl":[1,1,1]}]},{"parent":0,"keys":[{"time":0,"pos":[2.49995,0.280193,0.066556],"rot":[0.0033329,0.881416,0.472275,0.00706144],"scl":[1,1,1]},{"time":0.0416667,"pos":[2.49995,0.280193,0.066556],"rot":[0.00316317,0.858922,0.512045,0.00734349],"scl":[1,1,1]},{"time":0.0833333,"pos":[2.49995,0.280193,0.066556],"rot":[0.00263566,0.783706,0.621074,0.00807219],"scl":[1,1,1]},{"time":0.125,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00187897,-0.664887,-0.74689,-0.00880854],"scl":[1,1,1]},{"time":0.166667,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00126496,-0.561008,-0.827759,-0.00919252],"scl":[1,1,1]},{"time":0.208333,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00104832,-0.522977,-0.852295,-0.009288],"scl":[1,1,1]},{"time":0.25,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00126496,-0.561007,-0.827759,-0.00919252],"scl":[1,1,1]},{"time":0.291667,"pos":[2.49995,0.280193,0.066556],"rot":[-0.00187897,-0.664887,-0.74689,-0.00880854],"scl":[1,1,1]},{"time":0.333333,"pos":[2.49995,0.280193,0.066556],"rot":[0.00263566,0.783706,0.621074,0.00807219],"scl":[1,1,1]},{"time":0.375,"pos":[2.49995,0.280193,0.066556],"rot":[0.00316317,0.858921,0.512045,0.0073435],"scl":[1,1,1]},{"time":0.416667,"pos":[2.49995,0.280193,0.066556],"rot":[0.0033329,0.881416,0.472275,0.00706144],"scl":[1,1,1]}]}]}]}';
load();
function load() {
var loader = new THREE.JSONLoader();
clock = new THREE.Clock();
sceneMain = new THREE.Scene();
sceneOutline = new THREE.Scene();
var obj = loader.parse(JSON.parse(objData));
for (var k in obj.materials) {
obj.materials[k].skinning = true;
}
setModel(obj.geometry, obj.materials);
init();
animate();
}
function init() {
camera = new THREE.PerspectiveCamera(40, height / width, 1, 10000);
camera.position.set(0, 0, 25);
light = new THREE.DirectionalLight(0xffffff)
light.position.set(1, 1, 1);
sceneMain.add(light);
renderer = new THREE.WebGLRenderer({
width: width,
height: height,
antialias: true,
});
renderer.setSize(width, height);
renderer.setClearColor(0x666666);
renderer.autoClear = false;
renderer.gammaInput = true;
renderer.gammaOutput = true;
document.body.appendChild(renderer.domElement);
var renderTarget = new THREE.WebGLRenderTarget(width, height, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
stencilBuffer: true,
});
composer = new THREE.EffectComposer(renderer, renderTarget);
composer.renderTarget1.stencilBuffer = true;
composer.renderTarget2.stencilBuffer = true;
var pMain = new THREE.RenderPass(sceneMain, camera);
var pOut = new THREE.RenderPass(sceneOutline, camera);
pOut.clear = false;
var pCopy = new THREE.ShaderPass(THREE.CopyShader);
pCopy.renderToScreen = true;
composer.addPass(pMain);
composer.addPass(pOut);
composer.addPass(pCopy);
animMain.play();
animOutline.play();
}
function setModel(geometry, materials) {
meshMain = new THREE.SkinnedMesh(geometry,
new THREE.MeshFaceMaterial(materials));
sceneMain.add(meshMain);
mixerMain = new THREE.AnimationMixer(meshMain);
animMain = mixerMain.clipAction(geometry.animations[0]);
var shader = THREE.OutlineShader;
var shaderMaterial = new THREE.ShaderMaterial({
uniforms: THREE.UniformsUtils.clone(shader.uniforms),
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
skinning: true,
side: THREE.BackSide,
});
meshOutline = new THREE.SkinnedMesh(geometry, shaderMaterial);
shaderMaterial.uniforms['boneTextureWidth'].value = meshOutline.skeleton.boneTextureWidth;
shaderMaterial.uniforms['boneTextureHeight'].value = meshOutline.skeleton.boneTextureHeight;
shaderMaterial.uniforms['boneTexture'].value = meshOutline.skeleton.boneTexture;
shaderMaterial.uniforms['offset'].value = 0.5;
shaderMaterial.uniforms['boneTextureWidth'].value.needsUpdate = true;
shaderMaterial.uniforms['boneTextureHeight'].value.needsUpdate = true;
shaderMaterial.uniforms['boneTexture'].value.needsUpdate = true;
shaderMaterial.uniforms['offset'].value.needsUpdate = true;
sceneOutline.add(meshOutline);
mixerOutline = new THREE.AnimationMixer(meshOutline);
animOutline = mixerOutline.clipAction(geometry.animations[0]);
}
function animate() {
var delta = clock.getDelta();
requestAnimationFrame(animate);
update(delta);
render(delta);
}
function update(delta) {
if (meshMain && meshOutline) {
meshMain.rotation.y += 1 * delta;
meshOutline.rotation.y += 1 * delta;
mixerMain.update(delta);
mixerOutline.update(delta);
}
}
function render(delta) {
composer.render(delta);
}
The problem is evidently due to the ShaderMaterial and/or the shader itself, as changing the second mesh's material to e.g. MeshBasicMaterial results in the expected behaviour (the two meshes staying in lockstep).
The shader was lifted from this jsfiddle posted some time ago. It uses an ancient version of three.js. I'm not entirely clear on the expected/correct way of populating the boneTexture, boneTextureWidth, and boneTextureHeight uniforms when creating the ShaderMaterial instance. I do it manually from the values in the mesh's skeleton, but I wouldn't be surprised if that's wrong.
Again, I'm just trying to understand why translating both meshes in the same way at the same time causes them to go out of sync like illustrated in the first jsfiddle example.
Edit: I observe that the mesh using ShaderMaterial (meshOutline) syncs with the other mesh (meshMain) if meshOutline is rotated exactly half as much as meshMain. E.g., in the update() function:
meshMain.rotation.y += 1 * delta;
meshOutline.rotation.y += 1 * delta / 2;
...will result in the two meshes apparently rotating in sync. The same is true if the rotation is replaced with a coordinate (e.g. x) movement:
//meshMain.rotation.y += 1 * delta;
//meshOutline.rotation.y += 1 * delta / 2;
var dx = Math.random() - 0.5;
meshMain.position.x += dx;
meshOutline.position.x += dx / 2;
...will result in both meshes moving back and forth together. But if both
are combined, that is:
meshMain.rotation.y += 1 * delta;
meshOutline.rotation.y += 1 * delta / 2;
var dx = Math.random() - 0.5;
meshMain.position.x += dx;
meshOutline.position.x += dx / 2;
They go wildly out of sync.
This clearly means that there's something I'm not understanding about how the shader is getting vertex positions from three.js. I understand that the shader is computing the vertex positions and using them because that's what happens when you use a ShaderMaterial. What I'm not understanding is how to keep the data the shader is using current with what's happening to the mesh in three.js. Which is apparently happening in the second jsfiddle example I linked above.

Answering my own question: it appears as if something involving the ShaderMaterial implementation has changed since r66 (the version used in the second---working---jsfiddle example, from which I got the shader code).
What I ended up doing was going through the ShaderChunk source to see if I could reproduce what I wanted to do using chunks of shader code from the three.js source (thinking perhaps it was just some default or whatever that was getting set in the background that I wasn't doing in the custom shader code). What I ended up with is (for the vertex shader):
vertexShader: [
"uniform float offset;",
THREE.ShaderChunk["common"],
THREE.ShaderChunk["skinning_pars_vertex"],
"void main() {",
"vec3 transformed = vec3(position + normal * offset);",
THREE.ShaderChunk["skinbase_vertex"],
THREE.ShaderChunk["skinning_vertex"],
THREE.ShaderChunk["project_vertex"],
"}"
].join( "\n" ),
The important difference being hidden away in skinning_vertex.glsl, the source for the skinning_vertex shader chunk:
#ifdef USE_SKINNING
vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );
vec4 skinned = vec4( 0.0 );
skinned += boneMatX * skinVertex * skinWeight.x;
skinned += boneMatY * skinVertex * skinWeight.y;
skinned += boneMatZ * skinVertex * skinWeight.z;
skinned += boneMatW * skinVertex * skinWeight.w;
skinned = bindMatrixInverse * skinned;
#endif
The thing that's happening there that isn't in the custom shader I had (and which wasn't in the example that was working with r66) is in the first and last lines---first multiplying by bindMatrix and then later by bindMatrixInverse. I'm a little puzzled why this is required, as according to the docs these are two uniforms that are only defined if the SkinnedMesh has bindMode set to "detached" (instead of "attached", the default).
But at any rate that change---either by using the ShaderChunk-based shader or by editing my custom shader to include the differences---produces the desired result.
That answers my question, but I'd still welcome any pointers to where the documentation covers this or an explanation of the changes from r66 to r77 that explain the different behaviour.

Related

Decompose a GLSL mat4 to original RTS values within vertex shader to calculate a View UV Offset

I need to get the rotation differences between the model and the camera, convert the values to radians/degrees, and pass it to the fragment shader.
For that I need to decompose and the Model rotation matrix and maybe the camera view matrix as well. I cannot seem to find a way to decompose mechanism suitable within a shader.
The rotation details goes into fragment shader to calculate uv offset.
original_rotation + viewing_angles to calculate a final sprite-like offset of the following texture and shown as billboards.
Ultimately UV should offset downwards (ex:H3 to A3) looking from down, upwards looking from up (ex:A3 to H3), left to right looking and viceversa looking from sides (ex: D1 to D8 and viceversa).
const vertex_shader = `
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
attribute mat4 instanceMatrix;
attribute float index;
attribute float texture_index;
uniform vec2 rows_cols;
uniform vec3 camera_location;
varying float vTexIndex;
varying vec2 vUv;
varying vec4 transformed_normal;
float normal_to_orbit(vec3 rotation_vector, vec3 view_vector){
rotation_vector = normalize(rotation_vector);
view_vector = normalize(view_vector);
vec3 x_direction = vec3(1.0,0,0);
vec3 y_direction = vec3(0,1.0,0);
vec3 z_direction = vec3(0,0,1.0);
float rotation_x_length = dot(rotation_vector, x_direction);
float rotation_y_length = dot(rotation_vector, y_direction);
float rotation_z_length = dot(rotation_vector, z_direction);
float view_x_length = dot(view_vector, x_direction);
float view_y_length = dot(view_vector, y_direction);
float view_z_length = dot(view_vector, z_direction);
//TOP
float top_rotation = degrees(atan(rotation_x_length, rotation_z_length));
float top_view = degrees(atan(view_x_length, view_z_length));
float top_final = top_view-top_rotation;
float top_idx = floor(top_final/(360.0/rows_cols.x));
//FRONT
float front_rotation = degrees(atan(rotation_x_length, rotation_z_length));
float front_view = degrees(atan(view_x_length, view_z_length));
float front_final = front_view-front_rotation;
float front_idx = floor(front_final/(360.0/rows_cols.y));
return abs((front_idx*rows_cols.x)+top_idx);
}
vec3 extractEulerAngleXYZ(mat4 mat) {
vec3 rotangles = vec3(0,0,0);
rotangles.x = atan(mat[2].z, -mat[1].z);
float cosYangle = sqrt(pow(mat[0].x, 2.0) + pow(mat[0].y, 2.0));
rotangles.y = atan(cosYangle, mat[0].z);
float sinXangle = sin(rotangles.x);
float cosXangle = cos(rotangles.x);
rotangles.z = atan(cosXangle * mat[1].y + sinXangle * mat[2].y, cosXangle * mat[1].x + sinXangle * mat[2].x);
return rotangles;
}
float view_index(vec3 position, mat4 mv_matrix, mat4 rot_matrix){
vec4 posInView = mv_matrix * vec4(0.0, 0.0, 0.0, 1.0);
// posInView /= posInView[3];
vec3 VinView = normalize(-posInView.xyz); // (0, 0, 0) - posInView
// vec4 NinView = normalize(rot_matrix * vec4(0.0, 0.0, 1.0, 1.0));
// float NdotV = dot(NinView, VinView);
vec4 view_normal = rot_matrix * vec4(VinView.xyz, 1.0);
float view_x_length = dot(view_normal.xyz, vec3(1.0,0,0));
float view_y_length = dot(view_normal.xyz, vec3(0,1.0,0));
float view_z_length = dot(view_normal.xyz, vec3(0,0,1.0));
// float radians = atan(-view_x_length, -view_z_length);
float radians = atan(view_x_length, view_z_length);
// float angle = radians/PI*180.0 + 180.0;
float angle = degrees(radians);
if (radians < 0.0) { angle += 360.0; }
if (0.0<=angle && angle<=360.0){
return floor(angle/(360.0/rows_cols.x));
}
return 0.0;
}
void main(){
vec4 original_normal = vec4(0.0, 0.0, 1.0, 1.0);
// transformed_normal = modelViewMatrix * instanceMatrix * original_normal;
vec3 rotangles = extractEulerAngleXYZ(modelViewMatrix * instanceMatrix);
// transformed_normal = vec4(rotangles.xyz, 1.0);
transformed_normal = vec4(camera_location.xyz, 1.0);
vec4 v = (modelViewMatrix* instanceMatrix* vec4(0.0, 0.0, 0.0, 1.0)) + vec4(position.x, position.y, 0.0, 0.0) * vec4(1.0, 1.0, 1.0, 1.0);
vec4 model_center = (modelViewMatrix* instanceMatrix* vec4(0.0, 0.0, 0.0, 1.0));
vec4 model_normal = (modelViewMatrix* instanceMatrix* vec4(0.0, 0.0, 1.0, 1.0));
vec4 cam_loc = vec4(camera_location.xyz, 1.0);
vec4 view_vector = normalize((cam_loc-model_center));
//float findex = normal_to_orbit(model_normal.xyz, view_vector.xyz);
float findex = view_index(position, base_matrix, combined_rot);
vTexIndex = texture_index;
vUv = vec2(mod(findex,rows_cols.x)/rows_cols.x, floor(findex/rows_cols.x)/rows_cols.y) + (uv / rows_cols);
//vUv = vec2(mod(index,rows_cols.x)/rows_cols.x, floor(index/rows_cols.x)/rows_cols.y) + (uv / rows_cols);
gl_Position = projectionMatrix * v;
// gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position, 1.0);
}
`
const fragment_shader = (texture_count) => {
var fragShader = `
precision highp float;
uniform sampler2D textures[${texture_count}];
varying float vTexIndex;
varying vec2 vUv;
varying vec4 transformed_normal;
void main() {
vec4 finalColor;
`;
for (var i = 0; i < texture_count; i++) {
if (i == 0) {
fragShader += `if (vTexIndex < ${i}.5) {
finalColor = texture2D(textures[${i}], vUv);
}
`
} else {
fragShader += `else if (vTexIndex < ${i}.5) {
finalColor = texture2D(textures[${i}], vUv);
}
`
}
}
//fragShader += `gl_FragColor = finalColor * transformed_normal; }`;
fragShader += `gl_FragColor = finalColor; }`;
// fragShader += `gl_FragColor = startColor * finalColor; }`;
// int index = int(v_TexIndex+0.5); //https://stackoverflow.com/questions/60896915/texture-slot-not-getting-picked-properly-in-shader-issue
//console.log('frag shader: ', fragShader)
return fragShader;
}
function reset_instance_positions() {
const dummy = new THREE.Object3D();
const offset = 500*4
for (var i = 0; i < max_instances; i++) {
dummy.position.set(offset-(Math.floor(i % 8)*500), offset-(Math.floor(i / 8)*500), 0);
dummy.updateMatrix();
mesh.setMatrixAt(i, dummy.matrix);
}
mesh.instanceMatrix.needsUpdate = true;
}
function setup_geometry() {
const geometry = new THREE.InstancedBufferGeometry().copy(new THREE.PlaneBufferGeometry(400, 400));
const index = new Float32Array(max_instances * 1); // index
for (let i = 0; i < max_instances; i++) {
index[i] = (i % max_instances) * 1.0 /* index[i] = 0.0 */
}
geometry.setAttribute("index", new THREE.InstancedBufferAttribute(index, 1));
const texture_index = new Float32Array(max_instances * 1); // texture_index
const max_maps = 1
for (let i = 0; i < max_instances; i++) {
texture_index[i] = (Math.floor(i / max_instances) % max_maps) * 1.0 /* index[i] = 0.0 */
}
geometry.setAttribute("texture_index", new THREE.InstancedBufferAttribute(texture_index, 1));
const textures = [texture]
const grid_xy = new THREE.Vector2(8, 8)
mesh = new THREE.InstancedMesh(geometry,
new THREE.RawShaderMaterial({
uniforms: {
textures: {
type: 'tv',
value: textures
},
rows_cols: {
value: new THREE.Vector2(grid_xy.x * 1.0, grid_xy.y * 1.0)
},
camera_location: {
value: camera.position
}
},
vertexShader: vertex_shader,
fragmentShader: fragment_shader(textures.length),
side: THREE.DoubleSide,
// transparent: true,
}), max_instances);
scene.add(mesh);
reset_instance_positions()
}
var camera, scene, mesh, renderer;
const max_instances = 64
function init() {
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight,1, 10000 );
camera.position.z = 1024;
scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
setup_geometry()
var canvas = document.createElement('canvas');
var context = canvas.getContext('webgl2');
renderer = new THREE.WebGLRenderer({
canvas: canvas,
context: context
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
var dataurl = "https://i.stack.imgur.com/accaU.png"
var texture;
var imageElement = document.createElement('img');
imageElement.onload = function(e) {
texture = new THREE.Texture(this);
texture.needsUpdate = true;
init();
animate();
};
imageElement.src = dataurl;
JSFiddle of work so far
So You got 4x4 transform matrix M used on xy plane QUAD and want to map its 4 corners (p0,p1,p2,p3) to your texture with "repaeat" like manner (crossing border from left/right/up/down will return right/left/down/up) based on direction of Z axis of the matrix.
You face 2 problems...
M rotation is 3 DOF and you want just 2 DOF (yaw,pitch) so if roll present the result might be questionable
if texture crosses borders you need to handle this in GLSL to avoid seems
so either do this in geometry shader and divide the quad to more if needed or use enlarged texture where you have the needed overlaps ...
Now if I did not miss something the conversion is like this:
const float pi=3.1415926535897932384626433832795;
vec3 d = normalize(z axis from M);
vec2 dd = normalize(d.xy);
u = atan2(dd.y,dd.x);
v = acos(d.z);
u = (u+pi)/(2.0*pi);
v = v/pi
The z axis extraction is just simple copy of 3th column/row (depends on your notation) from your matrix 'M' or transforming (1,0,0,0) by it. For more info see:
Understanding 4x4 homogenous transform matrices
In case of overlapped texture you need to add also this:
const float ov = 1.0/8.0; // overlap size
u = ov + (u/(ov+ov+1.0));
v = ov + (v/(ov+ov+1.0));
And the texture would look like:
In case your quads cover more than 1/8 of your original texture you need to enlarge the overlap ...
Now to handle the corners of QUAD instead of just axis you could translate the quad by distance l in Z+ direction in mesh local coordinates, apply the M on them and use those 4 points as directions to compute u,v in vertex shader. The l will affect how much of the texture area is used for quad ... This approach might even handle roll but did not test any of this yet...
After implementing it my fears was well grounded as any 2 euler angles affect each other so the result is OK on most of the directions but in edge cases the stuff get mirrored and or jumped in one or both axises probably due to area coverage difference between 3 DOF and 2 DOF (unless I made a bug in my code or the math was not computed correctly in vertex which happened to me before due to bug in drivers)
If you going for azimut/elevation that should be fine as its 2 DOF too the equation above shoul dwork for them too +/- some range conversion if needed.

Push particles away from mouseposition in glsl and three js

I have the following setup for my THREE.Points Object:
this.particleGeometry = new THREE.BufferGeometry()
this.particleMaterial = new THREE.ShaderMaterial(
{
vertexShader: vshader,
fragmentShader: fshader,
blending: THREE.AdditiveBlending,
depthWrite: false,
uniforms: {
uTime: new THREE.Uniform(0),
uMousePosition: this.mousePosition
}
}
)
and then some code to place points in the BufferGeometry on a sphere. That is working fine.
I also set up a Raycaster to track the mouse position intersecting a hidden plane and then update the uniform uMousePosition accordingly. That also works fine, I get the mouse position sent to my vertex shader.
Now I am trying to make the particles that are in a certain distance d to the mouse push away from it where the closest ones are pushed most of course, and also apply a gravity back to their original position to restore everything after time.
So here is what I have in my vertex shader:
void main() {
float lerp(float a, float b, float amount) {
return a + (b - a) * amount;
}
void main() {
vec3 p = position;
float dist = min(distance(p, mousePosition), 1.);
float lerpFactor = .2;
p.x = lerp(p.x, position.x * dist, lerpFactor);
p.y = lerp(p.y, position.y * dist, lerpFactor);
p.z = lerp(p.z, position.z * dist, lerpFactor);//Mouse is always in z=0
vec4 mvPosition = modelViewMatrix * vec4(p, 1.);
gl_PointSize = 30. * (1. / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
}
And here is what it looks like when the mouse is outside the sphere (added a small sphere that moves with the mouseposition to indicate the mouseposition)
And here when the mouse is inside:
Outside already looks kind of correct, but mouse inside only moves the particles closer back to their original position, where it should push them further outside instead. I guess I somehow have to determine the direction of the distance.
Also, the lerp method does not lerp, the particles directly jump to their position.
So I wonder how I get the correct distance to the mouse to always move the particles in a certain area and also how to animate the lerp / gravity effect.
That's how you could do it as a first approximation:
body{
overflow: hidden;
margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/three#0.136.0";
import {OrbitControls} from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/controls/OrbitControls.js";
import * as BufferGeometryUtils from "https://cdn.skypack.dev/three#0.136.0/examples/jsm/utils/BufferGeometryUtils.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
let marker = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 8), new THREE.MeshBasicMaterial({color: "red", wireframe: true}));
scene.add(marker);
let g = new THREE.IcosahedronGeometry(4, 20);
g = BufferGeometryUtils.mergeVertices(g);
let uniforms = {
mousePos: {value: new THREE.Vector3()}
}
let m = new THREE.PointsMaterial({
size: 0.1,
onBeforeCompile: shader => {
shader.uniforms.mousePos = uniforms.mousePos;
shader.vertexShader = `
uniform vec3 mousePos;
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
vec3 seg = position - mousePos;
vec3 dir = normalize(seg);
float dist = length(seg);
if (dist < 2.){
float force = clamp(1. / (dist * dist), 0., 1.);
transformed += dir * force;
}
`
);
console.log(shader.vertexShader);
}
});
let p = new THREE.Points(g, m);
scene.add(p);
let clock = new THREE.Clock();
renderer.setAnimationLoop( _ => {
let t = clock.getElapsedTime();
marker.position.x = Math.sin(t * 0.5) * 5;
marker.position.y = Math.cos(t * 0.3) * 5;
uniforms.mousePos.value.copy(marker.position);
renderer.render(scene, camera);
})
</script>

Shader for solid color for each triangle of plane geometry

I have been learning webgl from webglfundamentals where I came across a simple shader example where you can paint triangles with solid color. Here is link to tutorial and original demo.
I tried to create same effect in three js using plane geometry but I can't manage achieve solid color shader. When I use almost same setup in Three js, I get more like gradient effect. What am I doing wrong here? (I am noticing that my shader isn't consistent either as it renders differently on refresh) Also, is there place to learn shaders specifically for three js?
var vShader = `
precision mediump float;
precision mediump int;
attribute vec4 a_color;
varying vec4 vColor;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
vColor = a_color;
}`;
var fShader = ` precision mediump float;
precision mediump int;
varying vec4 vColor;
void main() {
vec4 color = vec4( vColor );
gl_FragColor = vColor;
}`;
var row = 1;
var col = 1;
var w = 600;
var h = 400;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 100);
renderer = new THREE.WebGLRenderer();
camera.position.z = 5;
var viewSize = getViewSize(camera);
document.body.appendChild(renderer.domElement);
renderer.setSize(w, h);
var geometry = new THREE.PlaneBufferGeometry(viewSize.width, viewSize.height, col, row);
var color = new THREE.Color();
const blossomPalette = [0xff0000, 0xff0000, 0xff0000, 0x0000ff, 0x0000ff, 0x0000ff];
var colors = new Float32Array(4 * 2 * 3 * col * row);
for (let i = 0; i < 4; i++) {
color.setHex(blossomPalette[Math.floor(Math.random() * blossomPalette.length)]);
color.toArray(colors, i * 3);
}
geometry.setAttribute('a_color', new THREE.BufferAttribute(colors, 4, false));
var material = new THREE.ShaderMaterial({
vertexShader: vShader,
fragmentShader: fShader,
transparent: true,
blending: THREE.AdditiveBlending,
depthTest: false,
vertexColors: true,
flatShading: true
});
var plane = new THREE.Mesh(geometry, material);
scene.add(plane);
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(animate)
}
animate();
function getViewSize(camera) {
var fovInRadians = (camera.fov * Math.PI) / 180;
var height = Math.abs(camera.position.z * Math.tan(fovInRadians / 2) * 2);
return {
width: height * camera.aspect,
height: height
}
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.min.js"></script>
The PlaneBufferGeometry in three.js is using indexed vertices to share vertices so there are only 4 vertices and then 6 indices to use those 4 vertices to make 2 triangles. That means you can't give each triangle different solid colors because they share 2 vertices and a vertex can only have 1 color.
Further, the code is choosing random colors for each vertex so even if you you used 6 vertices so that the 2 triangles didn't share any you still wouldn't get the result you linked to, instead you'd get this result which is further down the page on the same tutorial.
Finally the code is only generating 3 floats per color so you need to set the number of components for the color attribute to 3 instead of 4
If you want to repeat the webgl sample you'll need to provide your own 6 vertices.
var vShader = `
precision mediump float;
precision mediump int;
attribute vec4 a_color;
varying vec4 vColor;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
vColor = a_color;
}`;
var fShader = ` precision mediump float;
precision mediump int;
varying vec4 vColor;
void main() {
vec4 color = vec4( vColor );
gl_FragColor = vColor;
}`;
var row = 1;
var col = 1;
var w = 600;
var h = 400;
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 100);
renderer = new THREE.WebGLRenderer();
camera.position.z = 5;
var viewSize = getViewSize(camera);
document.body.appendChild(renderer.domElement);
renderer.setSize(w, h);
var geometry = new THREE.BufferGeometry();
var x = viewSize.width / 2;
var y = viewSize.height / 2;
var positions = new Float32Array([
-x, -y, 0,
x, -y, 0,
-x, y, 0,
-x, y, 0,
x, -y, 0,
x, y, 0,
]);
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3, false));
var color = new THREE.Color();
const blossomPalette = [
0xff0000, 0xff0000, 0xff0000,
0x0000ff, 0x0000ff, 0x0000ff,
];
var colors = new Float32Array(2 * 3 * 3 * col * row);
for (let i = 0; i < 6; i++) {
color.setHex(blossomPalette[i]);
color.toArray(colors, i * 3);
}
geometry.setAttribute('a_color', new THREE.BufferAttribute(colors, 3, false));
var material = new THREE.ShaderMaterial({
vertexShader: vShader,
fragmentShader: fShader,
transparent: true,
blending: THREE.AdditiveBlending,
depthTest: false,
vertexColors: true,
flatShading: true
});
var plane = new THREE.Mesh(geometry, material);
scene.add(plane);
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(animate)
}
animate();
function getViewSize(camera) {
var fovInRadians = (camera.fov * Math.PI) / 180;
var height = Math.abs(camera.position.z * Math.tan(fovInRadians / 2) * 2);
return {
width: height * camera.aspect,
height: height
}
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r122/build/three.min.js"></script>
See this article

Transparent point is not transparent at some degrees in THREE.js

demo link
//vertexShader
attribute float percent;
varying float vPercent;
void main() {
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * mvPosition;
gl_PointSize = 10.0;
vPercent = percent;
}
//fragmentShader
varying float vPercent;
void main() {
gl_FragColor = vec4( vec3(0.0), vPercent );
}
let points = [];
for(let i = 0; i < 1000; i++) {
points.push(new THREE.Vector3(i / 1000, 1, 0));
}
let geometry = new THREE.BufferGeometry().setFromPoints(points);
let percents = new Float32Array(1000);
for(let i = 0; i < 1000; i++) {
percents[i] = i / 1000;
}
geometry.addAttribute('percent', new THREE.BufferAttribute(percents, 1));
let line = new THREE.Points(geometry,
new THREE.ShaderMaterial({
vertexShader: shader_content["vertexShader"],
fragmentShader: shader_content["fragmentShader"],
vertexColors: THREE.VertexColors,
transparent: true
})
);
scene.add(line);
Rotate the scene, points are all black at some degrees. THREE.js 109.
Is it a bug or it is just what it supposed to be?
Screenshots may explain better.
all black picture
normal picture
What you see is a depth sorting issue that can be avoid by setting depthWrite of your shader material to false. The points do not flicker anymore and you always see the expected result (which actually corresponds to your black picture).

Transitioning vertices between 3D models with three.js

I am trying to achieve polygon blowing and reassembling effect similar to:
http://na.leagueoflegends.com/en/featured/skins/project-2016
https://tkmh.me/
In both of these examples, you can see how they are morphing / transitioning the vertices from one 3d model to another, resulting in a pretty cool effect. I have something similar working, but I can't wrap my head around how they are transitioning the vertices with velocity offsets (please refer to the first link and see how the particles don't simply map and ease to the new position, but rather do it with some angular offset):
So, I import my two models in Three.js, take the one that has bigger vert count and copy its geometry while attaching the second's model data as an attribute:
class CustomGeometry extends THREE.BufferGeometry {
constructor (geometry1, geometry2) {
super()
let { count } = geometry1.attributes.position
// this will hold
let targetArr = new Float32Array(count * 3)
let morphFactorArr = new Float32Array(count)
for (let i = 0; i < count; i += 3) {
targetArr[i + 0] = geometry2.attributes.position.array[i + 0] || 0
targetArr[i + 1] = geometry2.attributes.position.array[i + 1] || 0
targetArr[i + 2] = geometry2.attributes.position.array[i + 2] || 0
let rand = Math.random()
morphFactorArr[i + 0] = rand
morphFactorArr[i + 1] = rand
morphFactorArr[i + 2] = rand
}
this.addAttribute('a_target', new THREE.BufferAttribute(targetArr, 3))
this.addAttribute('a_morphFactor', new THREE.BufferAttribute(morphFactorArr, 1))
this.addAttribute('position', geometry1.attributes.position)
}
}
Then in my shaders I can simply transition between them like this:
vec3 new_position = mix(position, a_targetPosition, a_morphFactor);
This works, but is really dull and boring. The vertices simply map from one model to another, without any offsets, no gravity or whatever you want to throw into the mix.
Also, since I am attaching 0 to a position if there is a vert number mismatch, the unused positions simply scale to vec4(0.0, 0.0, 0.0, 1.0), which results in, again, a dull and boring effect (here morphing between a bunny and elephant models)
(notice how the unused bunny vertices simply scale down to 0)
How does one approach a problem like this?
Also, in the League of Legends link, how do they manage to
Animate the vertices internally to the model while it is active on the screen
Apply different velocity and gravity to the particles when mapping them to the next model (when clicking on the arrows and transitioning)?
Is it by passing a boolean attribute? Are they changing the targetPositions array? Any help is more than appreciated
This works, but is really dull and boring. The vertices simply map from one model to another, without any offsets, no gravity or whatever you want to throw into the mix.
So you can apply any effects you can imagine and code.
This is not the exact answer to your question, but this is the simplest motivating example of what you can do with shaders. Spoiler: the link to a working example is at the end of this answer.
Let's transform this
into this
with funny swarming particles during transition
We'll use THREE.BoxBufferGeometry() with some custom attributes:
var sideLenght = 10;
var sideDivision = 50;
var cubeGeom = new THREE.BoxBufferGeometry(sideLenght, sideLenght, sideLenght, sideDivision, sideDivision, sideDivision);
var attrPhi = new Float32Array( cubeGeom.attributes.position.count );
var attrTheta = new Float32Array( cubeGeom.attributes.position.count );
var attrSpeed = new Float32Array( cubeGeom.attributes.position.count );
var attrAmplitude = new Float32Array( cubeGeom.attributes.position.count );
var attrFrequency = new Float32Array( cubeGeom.attributes.position.count );
for (var attr = 0; attr < cubeGeom.attributes.position.count; attr++){
attrPhi[attr] = Math.random() * Math.PI * 2;
attrTheta[attr] = Math.random() * Math.PI * 2;
attrSpeed[attr] = THREE.Math.randFloatSpread(6);
attrAmplitude[attr] = Math.random() * 5;
attrFrequency[attr] = Math.random() * 5;
}
cubeGeom.addAttribute( 'phi', new THREE.BufferAttribute( attrPhi, 1 ) );
cubeGeom.addAttribute( 'theta', new THREE.BufferAttribute( attrTheta, 1 ) );
cubeGeom.addAttribute( 'speed', new THREE.BufferAttribute( attrSpeed, 1 ) );
cubeGeom.addAttribute( 'amplitude', new THREE.BufferAttribute( attrAmplitude, 1 ) );
cubeGeom.addAttribute( 'frequency', new THREE.BufferAttribute( attrFrequency, 1 ) );
and THREE.ShaderMaterial():
var vertexShader = [
"uniform float interpolation;",
"uniform float radius;",
"uniform float time;",
"attribute float phi;",
"attribute float theta;",
"attribute float speed;",
"attribute float amplitude;",
"attribute float frequency;",
"vec3 rtp2xyz(){ // the magic is here",
" float tmpTheta = theta + time * speed;",
" float tmpPhi = phi + time * speed;",
" float r = sin(time * frequency) * amplitude * sin(interpolation * 3.1415926);",
" float x = sin(tmpTheta) * cos(tmpPhi) * r;",
" float y = sin(tmpTheta) * sin(tmpPhi) * r;",
" float z = cos(tmpPhi) * r;",
" return vec3(x, y, z);",
"}",
"void main(){",
" vec3 newPosition = mix(position, normalize(position) * radius, interpolation);",
" newPosition += rtp2xyz();",
" vec4 mvPosition = modelViewMatrix * vec4( newPosition, 1.0 );",
" gl_PointSize = 1. * ( 1. / length( mvPosition.xyz ) );",
" gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n");
var fragmentShader = [
"uniform vec3 color;",
"void main(){",
" gl_FragColor = vec4( color, 1.0 );",
"}"
].join("\n");
var uniforms = {
interpolation: { value: slider.value},
radius: { value: 7.5},
color: { value: new THREE.Color(0x00ff00)},
time: { value: 0 }
}
var shaderMat = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
//wireframe: true //just in case, if you want to use THREE.Mesh() instead of THREE.Points()
});
As you can see, all the magic happens in the vertex shader and its rtp2xyz() function.
And in the end, the code of the function of animation:
var clock = new THREE.Clock();
var timeVal = 0;
render();
function render(){
timeVal += clock.getDelta();
requestAnimationFrame(render);
uniforms.time.value = timeVal;
uniforms.interpolation.value = slider.value;
renderer.render(scene, camera);
}
Oh, and yes, we have a slider control in our page:
<input id="slider" type="range" min="0" max="1" step="0.01" value="0.5" style="position:absolute;width:300px;">
Here's a snippet
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(10, 10, 20);
var renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var vertexShader = [
"uniform float interpolation;",
"uniform float radius;",
"uniform float time;",
"attribute float phi;",
"attribute float theta;",
"attribute float speed;",
"attribute float amplitude;",
"attribute float frequency;",
"vec3 rtp2xyz(){ // the magic is here",
" float tmpTheta = theta + time * speed;",
" float tmpPhi = phi + time * speed;",
" float r = sin(time * frequency) * amplitude * sin(interpolation * 3.1415926);",
" float x = sin(tmpTheta) * cos(tmpPhi) * r;",
" float y = sin(tmpTheta) * sin(tmpPhi) * r;",
" float z = cos(tmpPhi) * r;",
" return vec3(x, y, z);",
"}",
"void main(){",
" vec3 newPosition = mix(position, normalize(position) * radius, interpolation);",
" newPosition += rtp2xyz();",
" vec4 mvPosition = modelViewMatrix * vec4( newPosition, 1.0 );",
" gl_PointSize = 1. * ( 1. / length( mvPosition.xyz ) );",
" gl_Position = projectionMatrix * mvPosition;",
"}"
].join("\n");
var fragmentShader = [
"uniform vec3 color;",
"void main(){",
" gl_FragColor = vec4( color, 1.0 );",
"}"
].join("\n");
var uniforms = {
interpolation: { value: slider.value},
radius: { value: 7.5},
color: { value: new THREE.Color(0x00ff00)},
time: { value: 0 }
}
var sideLenght = 10;
var sideDivision = 50;
var cubeGeom = new THREE.BoxBufferGeometry(sideLenght, sideLenght, sideLenght, sideDivision, sideDivision, sideDivision);
var attrPhi = new Float32Array( cubeGeom.attributes.position.count );
var attrTheta = new Float32Array( cubeGeom.attributes.position.count );
var attrSpeed = new Float32Array( cubeGeom.attributes.position.count );
var attrAmplitude = new Float32Array( cubeGeom.attributes.position.count );
var attrFrequency = new Float32Array( cubeGeom.attributes.position.count );
for (var attr = 0; attr < cubeGeom.attributes.position.count; attr++){
attrPhi[attr] = Math.random() * Math.PI * 2;
attrTheta[attr] = Math.random() * Math.PI * 2;
attrSpeed[attr] = THREE.Math.randFloatSpread(6);
attrAmplitude[attr] = Math.random() * 5;
attrFrequency[attr] = Math.random() * 5;
}
cubeGeom.addAttribute( 'phi', new THREE.BufferAttribute( attrPhi, 1 ) );
cubeGeom.addAttribute( 'theta', new THREE.BufferAttribute( attrTheta, 1 ) );
cubeGeom.addAttribute( 'speed', new THREE.BufferAttribute( attrSpeed, 1 ) );
cubeGeom.addAttribute( 'amplitude', new THREE.BufferAttribute( attrAmplitude, 1 ) );
cubeGeom.addAttribute( 'frequency', new THREE.BufferAttribute( attrFrequency, 1 ) );
var shaderMat = new THREE.ShaderMaterial({
uniforms: uniforms,
vertexShader: vertexShader,
fragmentShader: fragmentShader,
//wireframe: true
});
var points = new THREE.Points(cubeGeom, shaderMat);
scene.add(points);
var clock = new THREE.Clock();
var timeVal = 0;
render();
function render(){
timeVal += clock.getDelta();
requestAnimationFrame(render);
uniforms.time.value = timeVal;
uniforms.interpolation.value = slider.value;
renderer.render(scene, camera);
}
body{
margin: 0;
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<input id="slider" type="range" min="0" max="1" step="0.01" value="0.5" style="position:absolute;width:300px;">

Resources