buffergeometry groups with individual shaders - three.js

I think showing a code example is the best. Therefore i have created a small example that shows the problem.
I want to create a buffer geometry in which each group has its own shader. although i always create a new instance in the material array, i cannot use the uniforms of the individual shaders independently. what i adjust in the uniform of one shader in the array always has the same effect on all other shaders in the material array.
Before i ask, i try to advance through research, but here i have reached a point where i can not get any further. Does anyone know why the individual shaders in the material array are dependent on each other and how to avoid this?
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 100;
var velocity = [];
var geometry;
var test1, test2, test3;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//renderer.sortObjects = true;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shaders---------------
var BasicVertexShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
var BasicFragmentShader = `
void main() {
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var VertexShader = `
varying vec3 sPos;
uniform vec3 pos;
uniform float stretch;
void main() {
float rotation = 0.0;
sPos = position;
vec3 scale;
scale.x = 1.0*stretch;
scale.y = 1.0*stretch;
scale.z = 1.0*stretch;
vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
vec3 rotatedPosition;
rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
rotatedPosition.z = alignedPosition.z;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( 0, 0, 0, 1.0 );
finalPosition.xyz += rotatedPosition;
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
// gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;
var FragmentShader = `
varying vec3 sPos;
void main() {
vec3 nDistVec = normalize(sPos);
float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0);
float magnitude = 1.0/dist * pow(4.0, 2.0);
// gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0) * magnitude;
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var uniform = {
stretch: {type: 'f', value: 1.0},
pos: { value: new THREE.Vector3(0,0,0) },
}
Shader = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//just for tests
var Shade = new THREE.ShaderMaterial( {
vertexShader: BasicVertexShader,
fragmentShader: BasicFragmentShader,
side:THREE.DoubleSide
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-10, -10, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 10, -10, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-10, 10, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 10, 10, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push(Shader);
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
plane.material[0].uniforms.stretch.value = 2;
plane.material[1].uniforms.stretch.value = 3;
test1 = Object.keys(plane.material);
test2 = plane.material[0].uniforms.stretch.value; //why this returns 3 and not 2?
test3 = plane.material[1].uniforms.stretch.value;
//the goal is that each group has its own Shader without effecting the other ones
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
function render() {
document.getElementById("demo1").innerHTML = test1;
document.getElementById("demo2").innerHTML = test2;
document.getElementById("demo3").innerHTML = test3;
for(var i = 0; i < MAX_Planes; i++){
for(var j = 0; j < 4; j++){
plane.geometry.attributes.position.array[i*12+3*j] = plane.geometry.attributes.position.array[i*12+3*j] + velocity[i].x;
plane.geometry.attributes.position.array[i*12+3*j+1] = plane.geometry.attributes.position.array[i*12+3*j+1] + velocity[i].y;
plane.geometry.attributes.position.array[i*12+3*j+2] = plane.geometry.attributes.position.array[i*12+3*j+2] + velocity[i].z;
}
}
plane.geometry.attributes.position.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------

You are pushing references to a single ShaderMaterial into every index of your materials array.
materials.push(Shader);
Because each index is a reference, changing the properties on the object in one index will naturally change the object in all other indices.
If you want each group to have its own properties, then you will need to provide a unique material to each index. You can still do this by only creating one original definition, then using Material.clone to create copies.
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push( Shader.clone() ); // creates a unique copy for each index
}

this did not leave me in peace and it occurred to me that if i prealocate the buffer geometry, i would have to do that with the shaders for each group. I would now have made this much more complicated than with ".clone ()". good that i checked again. Your advice works wonderfully. I have corrected the positioning update and that is exactly the result that i had in mind. Here is the customized code for anyone interested. I will now combine this with my particle emitter.
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 2000;
var velocity = [];
var geometry;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
//renderer.sortObjects = true;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shader---------------
var VertexShader = `
varying vec3 sPos;
uniform vec3 pos;
uniform float stretch;
void main() {
float rotation = 0.0;
sPos = position;
vec3 scale;
scale.x = 1.0*stretch;
scale.y = 1.0*stretch;
scale.z = 1.0*stretch;
vec3 alignedPosition = vec3(position.x * scale.x, position.y * scale.y, 0.0);
vec3 rotatedPosition;
rotatedPosition.x = cos(rotation) * alignedPosition.x - sin(rotation) * alignedPosition.y;
rotatedPosition.y = sin(rotation) * alignedPosition.x + cos(rotation) * alignedPosition.y;
rotatedPosition.z = alignedPosition.z;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( pos, 1.0 );
finalPosition.xyz += rotatedPosition;
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
}`;
var FragmentShader = `
varying vec3 sPos;
void main() {
vec3 nDistVec = normalize(sPos);
float dist = pow(sPos.x, 2.0) + pow(sPos.y, 2.0);
float magnitude = 1.0/dist * pow(3.0, 2.0);
float alpha = 1.0;
if(magnitude < 0.01){
alpha = 0.0;
}
gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), alpha) * magnitude;
// gl_FragColor = vec4(vec3(1.0, 1.0, 0.8), 1.0);
}`;
var uniform = {
stretch: {type: 'f', value: 1.0},
pos: { value: new THREE.Vector3(0,0,0) },
}
var Shader = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-20, -20, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 20, -20, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-20, 20, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 20, 20, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6, i );
materials.push(Shader.clone());
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
var loop = 0;
function render() {
loop = loop + 0.5;
for(var i = 0; i < MAX_Planes; i++){
var pos = new THREE.Vector3(0, 0, 0);
pos.x += velocity[i].x*loop;
pos.y += velocity[i].y*loop;
pos.z += velocity[i].z*loop;
plane.material[i].uniforms.pos.value = pos;
}
plane.geometry.attributes.position.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
I set 2000 rectangles here and am pleasantly surprised at how smoothly the code runs. Even with 5000 rectangles everything went smoothly, even though each rectangle has its own shader. buffer geometries are really cool.
Thank you TheJim01
without your advice i would have preinitialized all shaders in a for loop. Such as vertexshader[i], fragmentshader[i], uniforms[i], Shader[i]. Using
".clone()" is of course much better 👍

Related

custom bufferGeometry with texture

I did tests on particle systems some time ago. because I'm working on it again at the moment. i want to use a texture but i only get black particles. i think the problem lies in the uv coordinates but i don't know how to use it in this case.
I have described in the code where I suspect the problem.
how do i access the texture coordinates in the shader in this case
var camera, controls, scene, renderer, container;
var PI = Math.PI;
var clock = new THREE.Clock();
var plane;
var MAX_Planes = 2000;
var velocity = [];
var geometry;
function init() {
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true} );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container = document.getElementById('container');
renderer.setSize(container.clientWidth, container.clientHeight);
container.appendChild( renderer.domElement );
var aspect = container.clientWidth / container.clientHeight;
scene = new THREE.Scene();
scene.background = new THREE.Color( 0x000000 );
camera = new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 100000 );
camera.position.set(0, 0, 4000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableZoom = true;
controls.enabled = true;
controls.target.set(0, 0, 0);
//---------------shader---------------
var VertexShader = `
varying vec3 vUv;
uniform vec3 pos;
void main() {
vUv = uv;
vec4 finalPosition;
finalPosition = modelViewMatrix * vec4( pos, 1.0 );
finalPosition.xyz += vec3(position.x, position.y, 0.0);
finalPosition = projectionMatrix * finalPosition;
gl_Position = finalPosition;
}`;
var FragmentShader = `
varying vec3 vUv;
uniform sampler2D tDiffuse;
void main() {
gl_FragColor = vec4(texture2D(tDiffuse, vUv).rgb, 1.);
//gl_FragColor = vec4(1.0, 1.0, 0.8, 1.0); //just for testing
}`;
var loader = new THREE.TextureLoader();
var texture = loader.load( 'textures/test.jpg' ); //the texture is loaded correctly. I tested that with a box
var uniform = {
tDiffuse: {value: texture},
pos: { value: new THREE.Vector3(0,0,0) },
}
var material = new THREE.ShaderMaterial( {
uniforms: uniform,
vertexShader: VertexShader,
fragmentShader: FragmentShader,
transparent: true,
depthTest: false,
depthWrite: false
});
//-------------------------------------------------
//create a plane: points, normals, uv
const vertices = [
{ pos: [-20, -20, 0], norm: [ 0, 0, 1], uv: [0, 1], },
{ pos: [ 20, -20, 0], norm: [ 0, 0, 1], uv: [1, 1], },
{ pos: [-20, 20, 0], norm: [ 0, 0, 1], uv: [0, 0], },
{ pos: [ 20, 20, 0], norm: [ 0, 0, 1], uv: [1, 0], },
];
const numVertices = vertices.length;
const positionNumComponents = 3;
const normalNumComponents = 3;
const uvNumComponents = 2;
//arrays for buffergeometry
const positions = new Float32Array(numVertices * positionNumComponents * MAX_Planes);
const normals = new Float32Array(numVertices * normalNumComponents * MAX_Planes);
const uvs = new Float32Array(numVertices * uvNumComponents * MAX_Planes);
//fill arrays with vertices
var posPointer = 0;
var nrmPointer = 0;
var uvPointer = 0;
for(var i = 0; i <= MAX_Planes; i++) {
var posNdx = 0;
var nrmNdx = 0;
var uvNdx = 0;
for (const vertex of vertices) {
positions.set(vertex.pos, posNdx + posPointer);
normals.set(vertex.norm, nrmNdx + nrmPointer);
uvs.set(vertex.uv, uvNdx + uvPointer);
posNdx += positionNumComponents;
nrmNdx += normalNumComponents;
uvNdx += uvNumComponents;
}
posPointer = i * posNdx;
nrmPointer = i * nrmNdx;
uvPointer = i * uvNdx;
}
//create buffergeometry and assign the attribut arrays
geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, positionNumComponents));
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, normalNumComponents));
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, uvNumComponents));
var ndx = 0;
var indices = [];
//instead 6 vertices for the both triangles of a plane i used 4, so reindication is neccessary
for(var i = 0; i < MAX_Planes; i++){
indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3);
ndx += 4;
}
geometry.setIndex(indices);
var materials = [];
geometry.clearGroups();
for(var i = 0; i < MAX_Planes; i++){
geometry.addGroup( 6*i, 6*(i+1), i );
materials.push(material.clone());
}
plane = new THREE.Mesh(geometry, materials);
scene.add(plane);
//----------------------velocity---------------------------
for(var i = 0; i < MAX_Planes; i++){
velocity[i] = new THREE.Vector3(
Math.random()*2-1,
Math.random()*2-1,
Math.random()*2-1);
}
}//-------End init----------
function animate() {
requestAnimationFrame( animate );
render();
}//-------End animate----------
var loop = 0;
function render() {
loop = loop + 0.5;
for(var i = 0; i < MAX_Planes; i++){
var pos = new THREE.Vector3(0, 0, 0);
pos.x += velocity[i].x*loop;
pos.y += velocity[i].y*loop;
pos.z += velocity[i].z*loop;
plane.material[i].uniforms.pos.value = pos;
}
plane.geometry.attributes.position.needsUpdate = true;
plane.geometry.attributes.uv.needsUpdate = true;
camera.updateMatrixWorld();
camera.updateProjectionMatrix();
renderer.render(scene, camera);
}//-------End render----------
my initial assumption was wrong. I suspect the problem now in the regrouping. with the following line i see the texture
plane = new THREE.Mesh(geometry, material);
but that's no use to me. each group "one plane inside the bufferarray" should have its own material and for that i need the grouping but obviously i'm doing something wrong because i don't see anything with the "materials" array
ah, the problem is that the texture is not cloned when the material is cloned. cloning the material only works if I work purely with shader code without texture. if i want to have the texture i have to instantiate the material every time in the for loop

Can we use ThreeJs Helpers with InstancedMeshes?

I am newbie to Threejs and I am trying to use axesHelper to visualise the three axis of all the objects in a scene. Earlier I was using simple Meshes and all the helpers were working fine. Recently I've moved few objects to Instanced Meshes and the helpers have stopped working for them. I have confirmed this by trying with two helpers.(arrow and axes helper)
I am using react-three-fiber to write for writing my threejs in JSX style.
Here is the code which used to work for me.
<mesh
position={[geometry.position.x, geometry.position.y, geometry.position.z]}
rotation={angle}
geometry={geometry}>
<meshLambertMaterial color={'red'} />
<axesHelper />
</mesh>
Here is the new one which doesn't work.
<instancedMesh
ref={ref}
args={[(null as unknown) as BufferGeometry, (null as unknown) as Material, objects.length]}>
<coneBufferGeometry args={[0.1, 0.1]}>
<instancedBufferAttribute attachObject={['attributes', 'color']} args={[colorArray, 3]}/>
</coneBufferGeometry>
<meshLambertMaterial vertexColors />
<axesHelper />
</instancedMesh>
So now I wanted to confirm if we can use these helpers for instanced meshes or not? I tried looking into three's documentation but was not able to find any answer related to this question.
I am on Threejs r127.
As an option, you can decompose the matrices of instances and pass these values in attributes for InstancedBufferGeometry, based on geometry of AxesHelper.
It's just an example, not the ultimate solution.
body{
overflow: hidden;
margin: 0;
}
<script type="module">
console.clear();
import * as THREE from "https://threejs.org/build/three.module.js";
import {OrbitControls} from "https://threejs.org/examples/jsm/controls/OrbitControls.js";
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 1000);
camera.position.set(0, 8, 13);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
let controls = new OrbitControls(camera, renderer.domElement);
let light = new THREE.DirectionalLight(0xffffff, 1.5);
light.position.setScalar(10);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 1));
let cylGeom = new THREE.CylinderBufferGeometry(0.5, 1, 2, 8);
let cylMat = new THREE.MeshStandardMaterial({color: "red", roughness: 0.25, metalness: 0.75, polygonOffset: true, polygonOffsetFactor: 1});
let cylinder = new THREE.InstancedMesh(cylGeom, cylMat, 100);
let dummy = new THREE.Object3D();
let mat4 = new THREE.Matrix4();
let counter = 0;
let pos = [];
let rot = [];
let scl = [];
for(let z = 0; z < 10; z++){
for(let x = 0; x < 10; x++){
dummy.position.set(-4.5 + x, 0, -4.5 + z).multiplyScalar(4);
dummy.rotation.set(
Math.random() * Math.PI * 2,
Math.random() * Math.PI * 2,
Math.random() * Math.PI * 2
);
dummy.updateMatrix();
cylinder.setMatrixAt(counter, dummy.matrix);
pos.push(dummy.position.x, dummy.position.y, dummy.position.z);
rot.push(dummy.quaternion.x, dummy.quaternion.y, dummy.quaternion.z, dummy.quaternion.w);
scl.push(dummy.scale.x, dummy.scale.y, dummy.scale.z);
counter++;
}
}
cylinder.instanceMatrix.needsUpdate = true;
console.log(cylinder);
scene.add(cylinder);
let axesHelper = new THREE.AxesHelper(2);
console.log(axesHelper);
let lineGeom = new THREE.InstancedBufferGeometry().copy(axesHelper.geometry);
lineGeom.instanceCount = Infinity;
lineGeom.setAttribute("instT", new THREE.InstancedBufferAttribute(new Float32Array(pos), 3));
lineGeom.setAttribute("instR", new THREE.InstancedBufferAttribute(new Float32Array(rot), 4));
lineGeom.setAttribute("instS", new THREE.InstancedBufferAttribute(new Float32Array(scl), 3));
let lineMat = new THREE.LineBasicMaterial({
vertexColors: true,
onBeforeCompile: shader => {
shader.vertexShader = `
attribute vec3 instT;
attribute vec4 instR;
attribute vec3 instS;
// http://barradeau.com/blog/?p=1109
vec3 trs( inout vec3 position, vec3 T, vec4 R, vec3 S ) {
position *= S;
position += 2.0 * cross( R.xyz, cross( R.xyz, position ) + R.w * position );
position += T;
return position;
}
${shader.vertexShader}
`.replace(
`#include <begin_vertex>`,
`#include <begin_vertex>
transformed = trs(transformed, instT, instR, instS);
`
);
console.log(shader.vertexShader);
}
});
let lines = new THREE.LineSegments(lineGeom, lineMat);
scene.add(lines);
window.addEventListener( 'resize', onWindowResize );
let clock = new THREE.Clock();
renderer.setAnimationLoop(()=>{
let t = clock.getDelta();
for(let i = 0; i < counter; i++){
cylinder.getMatrixAt(i, mat4);
mat4.decompose(dummy.position, dummy.quaternion, dummy.scale);
dummy.rotation.x += t;
dummy.rotation.z += t*.5;
dummy.updateMatrix();
cylinder.setMatrixAt(i, dummy.matrix);
cylinder.instanceMatrix.needsUpdate = true;
linesTRS(i, dummy);
}
renderer.render(scene, camera);
})
function linesTRS(index, o){
lineGeom.attributes.instT.setXYZ(index, o.position.x, o.position.y, o.position.z);
lineGeom.attributes.instT.needsUpdate = true;
lineGeom.attributes.instR.setXYZW(index, o.quaternion.x, o.quaternion.y, o.quaternion.z, o.quaternion.w);
lineGeom.attributes.instR.needsUpdate = true;
lineGeom.attributes.instS.setXYZ(index, o.scale.x, o.scale.y, o.scale.z);
lineGeom.attributes.instS.needsUpdate = true;
}
function onWindowResize() {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( innerWidth, innerHeight );
}
</script>

Warp / curve all vertices around a pivot point / axis (Three.js / GLSL)

I'm trying to work out how to warp all coordinates in a Three.js scene around a specific pivot point / axis. The best way to describe it is as if I was to place a tube somewhere in the scene and everything else in the scene would curve around that axis and keep the same distance from that axis.
If it helps, this diagram is what I'm trying to achieve. The top part is as if you were looking at the scene from the side and the bottom part is as if you were looking at it from a perspective. The red dot / line is where the pivot point is.
To further complicate matters, I'd like to stop the curve / warp from wrapping back on itself, so the curve stops when it's horizontal or vertical like the top-right example in the diagram.
Any insight into how to achieve this using GLSL shaders, ideally in Three.js but I'll try to translate if they can be described clearly otherwise?
I'm also open to alternative approaches to this as I'm unsure how best to describe what I'm after. Basically I want an inverted "curved world" effect where the scene is bending up and away from you.
First I'd do it in 2D just like your top diagram.
I have no idea if this is the correct way to do this or even a good way but, doing it in 2D seemed easier than 3D and besides the effect you want is actually a 2D. X is not changing at all, only Y, and Z so solving it in 2D seems like it would lead to solution.
Basically we choose a radius for a circle. At that radius for every unit of X past the circle's center we want to wrap one horizontal unit to one unit around the circle. Given the radius we know the distance around the circle is 2 * PI * radius so we can easily compute how far to rotate around our circle to get one unit. It's just 1 / circumference * Math.PI * 2 We do that for some specified distance past the circle's center
const m4 = twgl.m4;
const v3 = twgl.v3;
const ctx = document.querySelector('canvas').getContext('2d');
const gui = new dat.GUI();
resizeToDisplaySize(ctx.canvas);
const g = {
rotationPoint: {x: 100, y: ctx.canvas.height / 2 - 50},
radius: 50,
range: 60,
};
gui.add(g.rotationPoint, 'x', 0, ctx.canvas.width).onChange(render);
gui.add(g.rotationPoint, 'y', 0, ctx.canvas.height).onChange(render);
gui.add(g, 'radius', 1, 100).onChange(render);
gui.add(g, 'range', 0, 300).onChange(render);
render();
window.addEventListener('resize', render);
function render() {
resizeToDisplaySize(ctx.canvas);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const start = g.rotationPoint.x;
const curveAmount = g.range / g.radius;
const y = ctx.canvas.height / 2;
drawDot(ctx, g.rotationPoint.x, g.rotationPoint.y, 'red');
ctx.beginPath();
ctx.arc(g.rotationPoint.x, g.rotationPoint.y, g.radius, 0, Math.PI * 2, false);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.fillStyle = 'black';
const invRange = g.range > 0 ? 1 / g.range : 0; // so we don't divide by 0
for (let x = 0; x < ctx.canvas.width; x += 5) {
for (let yy = 0; yy <= 30; yy += 10) {
const sign = Math.sign(g.rotationPoint.y - y);
const amountToApplyCurve = clamp((x - start) * invRange, 0, 1);
let mat = m4.identity();
mat = m4.translate(mat, [g.rotationPoint.x, g.rotationPoint.y, 0]);
mat = m4.rotateZ(mat, curveAmount * amountToApplyCurve * sign);
mat = m4.translate(mat, [-g.rotationPoint.x, -g.rotationPoint.y, 0]);
const origP = [x, y + yy, 0];
origP[0] += -g.range * amountToApplyCurve;
const newP = m4.transformPoint(mat, origP);
drawDot(ctx, newP[0], newP[1], 'black');
}
}
}
function drawDot(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x - 1, y - 1, 3, 3);
}
function clamp(v, min, max) {
return Math.min(max, Math.max(v, min));
}
function resizeToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<!-- using twgl just for its math library -->
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
Notice the only place that matches perfectly is when the radius touches a line of points. Inside the radius things will get pinched, outside they'll get stretched.
Putting that in a shader in the Z direction for actual use
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('canvas'),
});
const gui = new dat.GUI();
const scene = new THREE.Scene();
const fov = 75;
const aspect = 2; // the canvas default
const zNear = 1;
const zFar = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
function lookSide() {
camera.position.set(-170, 35, 210);
camera.lookAt(0, 25, 210);
}
function lookIn() {
camera.position.set(0, 35, -50);
camera.lookAt(0, 25, 0);
}
{
scene.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, .5));
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-1, 20, 4 - 15);
scene.add(light);
}
const point = function() {
const material = new THREE.MeshPhongMaterial({
color: 'red',
emissive: 'hsl(0,50%,25%)',
wireframe: true,
});
const radiusTop = 1;
const radiusBottom = 1;
const height = 0.001;
const radialSegments = 32;
const geo = new THREE.CylinderBufferGeometry(
radiusTop, radiusBottom, height, radialSegments);
const sphere = new THREE.Mesh(geo, material);
sphere.rotation.z = Math.PI * .5;
const mesh = new THREE.Object3D();
mesh.add(sphere);
scene.add(mesh);
mesh.position.y = 88;
mesh.position.z = 200;
return {
point: mesh,
rep: sphere,
};
}();
const vs = `
// -------------------------------------- [ VS ] ---
#define PI radians(180.0)
uniform mat4 center;
uniform mat4 invCenter;
uniform float range;
uniform float radius;
varying vec3 vNormal;
mat4 rotZ(float angleInRadians) {
float s = sin(angleInRadians);
float c = cos(angleInRadians);
return mat4(
c,-s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}
mat4 rotX(float angleInRadians) {
float s = sin(angleInRadians);
float c = cos(angleInRadians);
return mat4(
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1);
}
void main() {
float curveAmount = range / radius;
float invRange = range > 0.0 ? 1.0 / range : 0.0;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vec4 point = invCenter * mvPosition;
float amountToApplyCurve = clamp(point.z * invRange, 0.0, 1.0);
float s = sign(point.y);
mat4 mat = rotX(curveAmount * amountToApplyCurve * s);
point = center * mat * (point + vec4(0, 0, -range * amountToApplyCurve, 0));
vNormal = mat3(mat) * normalMatrix * normal;
gl_Position = projectionMatrix * point;
}
`;
const fs = `
// -------------------------------------- [ FS ] ---
varying vec3 vNormal;
uniform vec3 color;
void main() {
vec3 light = vec3( 0.5, 2.2, 1.0 );
light = normalize( light );
float dProd = dot( vNormal, light ) * 0.5 + 0.5;
gl_FragColor = vec4( vec3( dProd ) * vec3( color ), 1.0 );
}
`;
const centerUniforms = {
radius: { value: 0 },
range: { value: 0 },
center: { value: new THREE.Matrix4() },
invCenter: { value: new THREE.Matrix4() },
};
function addUniforms(uniforms) {
return Object.assign(uniforms, centerUniforms);
}
{
const uniforms = addUniforms({
color: { value: new THREE.Color('hsl(100,50%,50%)') },
});
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
});
const planeGeo = new THREE.PlaneBufferGeometry(1000, 1000, 100, 100);
const mesh = new THREE.Mesh(planeGeo, material);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
{
const uniforms = addUniforms({
color: { value: new THREE.Color('hsl(180,50%,50%)' ) },
});
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
});
const boxGeo = new THREE.BoxBufferGeometry(10, 10, 10, 20, 20, 20);
for (let x = -41; x <= 41; x += 2) {
for (let z = 0; z <= 40; z += 2) {
const base = new THREE.Object3D();
const mesh = new THREE.Mesh(boxGeo, material);
mesh.position.set(0, 5, 0);
base.position.set(x * 10, 0, z * 10);
base.scale.y = 1 + Math.random() * 2;
base.add(mesh);
scene.add(base);
}
}
}
const g = {
radius: 59,
range: 60,
side: true,
};
class DegRadHelper {
constructor(obj, prop) {
this.obj = obj;
this.prop = prop;
}
get v() {
return THREE.Math.radToDeg(this.obj[this.prop]);
}
set v(v) {
this.obj[this.prop] = THREE.Math.degToRad(v);
}
}
gui.add(point.point.position, 'z', -300, 300).onChange(render);
gui.add(point.point.position, 'y', -150, 300).onChange(render);
gui.add(g, 'radius', 1, 100).onChange(render);
gui.add(g, 'range', 0, 300).onChange(render);
gui.add(g, 'side').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'x'), 'v', -180, 180).name('rotX').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'y'), 'v', -180, 180).name('rotY').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'z'), 'v', -180, 180).name('rotZ').onChange(render);
render();
window.addEventListener('resize', render);
function render() {
if (resizeToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
if (g.side) {
lookSide();
} else {
lookIn();
}
camera.updateMatrixWorld();
point.rep.scale.set(g.radius, g.radius, g.radius);
point.point.updateMatrixWorld();
centerUniforms.center.value.multiplyMatrices(
camera.matrixWorldInverse, point.point.matrixWorld);
centerUniforms.invCenter.value.getInverse(centerUniforms.center.value);
centerUniforms.range.value = g.range;
centerUniforms.radius.value = g.radius;
renderer.render(scene, camera);
}
function resizeToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needUpdate = canvas.width !== width || canvas.height !== height;
if (needUpdate) {
renderer.setSize(width, height, false);
}
return needUpdate;
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
Honestly I have a feeling there's an easier way I'm missing but for the moment it seems to kind of be working.

shaderMaterial not work width Points

function createParticles(imgData) {
const geometry = new THREE.BufferGeometry();
var c = 0, x = 0, y = 0, positions = [], colors = [];
var data = imgData.data;
x = -imgData.width * 0.5;
y = imgData.height * 0.5;
for (var i = 0; i < imgData.width; i++) {
for (var j = 0; j < imgData.height; j++) {
positions.push(i - imgData.width * 0.5, j - imgData.height * 0.5, 0);
}
}
geometry.addAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
var material = new THREE.ShaderMaterial({ fragmentShader: document.getElementById('f-shader').textContent });
return new THREE.Points(geometry, material);
}
and this is my shader:
<script type="shader" id="f-shader">
void main(){
gl_FragColor = vec4(1.0, 0.58, 0.86, 1.0);
}
</script>
i add this shadermaterial ,but there is nothing ,and the points will show when no material
You will also need to specify a vertex shader, and the vertex shader will have to have a gl_PointSize call in it. Something like:
void main() {
gl_PointSize = 10.0;
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}

Particles follow texture

I have a sphere created with particles in three.js that works perfectly. Now I wanted to put these particles on top of a texture that I have of a world map simulating a 3D planet, I searched the internet but I did not find any information on how to do it, when I put the texture instead of it being outside it ends up getting inside each particle, how could I do that? Any idea ? Thank you all
here is my code
$( document ).ready(function() {
var globe = document.getElementById('globe')
var Maxwidth = window.innerWidth
var Maxheight = window.innerHeight
var scene = new THREE.Scene();
var renderer = new THREE.WebGLRenderer({antilias:true});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(Maxwidth,Maxheight)
globe.appendChild(renderer.domElement)
var camera = new THREE.PerspectiveCamera(60, Maxwidth / Maxheight,1,1000);
camera.position.z = 50;
var controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.25;
controls.panningMode = THREE.HorizontalPanning; // default is
THREE.ScreenSpacePanning
controls.maxPolarAngle = Math.PI / 2;
var geometry = new THREE.SphereGeometry( 200, 42, 42 );
geometry.widthSegments = 42;
var colors = [];
for( var i = 0; i < geometry.vertices.length; i++ ) {
// random color
colors[i] = new THREE.Color();
//colors[i].setHSV( Math.random(), 1.0, 1.0 );
}
geometry.colors = colors;
// texture
var texture = new THREE.Texture( generateTexture( ) );
texture.needsUpdate = true; // important
// particle system material
var material = new THREE.ParticleBasicMaterial( {
size: 5,
map: texture,
blending: THREE.AdditiveBlending, // required
depthTest: false, // required
transparent: true,
opacity: 0.7,
vertexColors: true // optional
} );
material.map = THREE.ImageUtils.loadTexture('../img/point_picker.png')
material.anisotropy = 0;
material.magFilter = THREE.NearestFilter;
material.minFilter = THREE.NearestFilter;
var union = new THREE.ParticleSystem( geometry, material );
function generateTexture( ) {
var size = 128;
var canvas = document.createElement( 'canvas' );
canvas.width = size;
canvas.height = size;
var context = canvas.getContext( '2d' );
var centerX = size / 2;
var centerY = size / 2;
var radius = size / 2;
context.beginPath();
context.arc( centerX, centerY, radius, 0, 2 * Math.PI, false );
context.fillStyle = "#FFFFFF";
context.fill();
return canvas;
}
scene.add(union)
renderer.setClearColor(0x2675AD)
renderer.render(scene,camera)
controls.update();
function render(delta){
requestAnimationFrame(render);
renderer.render(scene,camera)
union.rotation.y += 0.0009
}
render()
});
I need something like this
So, this is the option I was talking about in my comment:
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(1.25, 7, 7);
camera.lookAt(scene.position);
var renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setClearColor(0x080808);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
var geom = new THREE.SphereBufferGeometry(5, 120, 60);
var colors = [];
var color = new THREE.Color();
var q = 0xffffff * 0.25;
for (let i = 0; i < geom.attributes.position.count; i++) {
color.set(Math.random() * q + q * 3);
color.toArray(colors, i * 3);
}
geom.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
var loader = new THREE.TextureLoader();
loader.setCrossOrigin('');
var texture = loader.load('https://learningthreejs.com/data/2013-09-16-how-to-make-the-earth-in-webgl/demo/bower_components/threex.planets/images/earthspec1k.jpg');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(1, 1);
var disk = loader.load('https://threejs.org/examples/textures/sprites/circle.png');
var points = new THREE.Points(geom, new THREE.ShaderMaterial({
vertexColors: THREE.VertexColors,
uniforms: {
visibility: {
value: texture
},
shift: {
value: 0
},
shape: {
value: disk
},
size: {
value: 0.125
},
scale: {
value: window.innerHeight / 2
}
},
vertexShader: `
uniform float scale;
uniform float size;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vUv = uv;
vColor = color;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( scale / length( mvPosition.xyz ) );
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
uniform sampler2D visibility;
uniform float shift;
uniform sampler2D shape;
varying vec2 vUv;
varying vec3 vColor;
void main() {
vec2 uv = vUv;
uv.x += shift;
vec4 v = texture2D(visibility, uv);
if (length(v.rgb) > 1.0) discard;
gl_FragColor = vec4( vColor, 1.0 );
vec4 shapeData = texture2D( shape, gl_PointCoord );
if (shapeData.a < 0.5) discard;
gl_FragColor = gl_FragColor * shapeData;
}
`,
transparent: true
}));
scene.add(points);
var blackGlobe = new THREE.Mesh(geom, new THREE.MeshBasicMaterial({
color: 0x000000
}));
blackGlobe.scale.setScalar(0.99);
points.add(blackGlobe);
var clock = new THREE.Clock();
var time = 0;
render();
function render() {
requestAnimationFrame(render);
time += clock.getDelta();
points.material.uniforms.shift.value = time * 0.1;
renderer.render(scene, camera);
}
body {
overflow: hidden;
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.91.0/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.91.0/examples/js/controls/OrbitControls.js"></script>

Resources