Instanced Geometry Vertex Shader Question

In this example: https://threejs.org/examples/#webgl_buffergeometry_instancing_dynamic

The vertex shader is as such:

void main() {

	vec3 vPosition = position;

	vec3 vcV = cross( orientation.xyz, vPosition );

	vPosition = vcV * ( 2.0 * orientation.w ) + ( cross( orientation.xyz, vcV ) * 2.0 + vPosition );

	vUv = uv;

	gl_Position = projectionMatrix * modelViewMatrix * vec4( offset + vPosition, 1.0 );

}

Can someone explain this to me? I gather that vPosition is the position attribute of the “base” buffer geometry. Than we are getting the cross product of the custom orientation quaternion and the original position vector for some reason.

Oh boy… then we are multiplying that times the w value of the quaternion * 2 and… blehhhhhhhhhh.

What is happening?!

If I just want to place an instanced object at various predefined positions/rotations, is this code that I need?

Still don’t quite understand the math, but regardless, got it working =]

You can rotate a vector by a quaternion with this shader function:

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

The math is explained in this blog post: http://www.geeks3d.com/20141201/how-to-rotate-a-vertex-by-a-quaternion-in-glsl/

I’m not sure why the calculation in the example slightly differs from this code. If I use applyQuaternionToVector() in the example, I get the exact same visual output.

2 Likes

Ahh, got it!

Hello,

First of all, sorry for digging out this old post! I am new the threejs and GLSL and unfortunately, i still don’t get all the Math involved in this after going through the blog post mentioned :frowning:

@titansoftime, can you please let me know how you managed to get around this? i tried tweaking with the numbers but getting back nothing useful.

All i want is simply turn the instance by 90 degrees. Any help is greatly appreciated.

regards,

Ok, so as far as shader code goes, apply this to the top of the vertex shader (above the main void).

attribute vec3 instancePosition;
attribute vec4 instanceQuaternion;
attribute vec3 instanceScale;

// http://www.geeks3d.com/20141201/how-to-rotate-a-vertex-by-a-quaternion-in-glsl/,
vec3 applyTRS( vec3 position, vec3 translation, vec4 quaternion, vec3 scale ) {
	position *= scale;
	position += 2.0 * cross( quaternion.xyz, cross( quaternion.xyz, position ) + quaternion.w * position );
	return position + translation;
}

And then inside the main vertex shader add this at the end:

transformed = applyTRS( transformed.xyz, instancePosition, instanceQuaternion, instanceScale );

In the javascript I set up the instancedBufferGeometry like so:

function createInstance(geo,data){ // geo being your original buffergeo. data being an array of position/rotation/scale values

	var quaternion = new THREE.Quaternion();
	
	var upVector = new THREE.Vector3(0,1,0);
	
	var instancePositions = [];
	var instanceQuaternions = [];
	var instanceScales = [];	
	
	for( var i=0, len=data.length; i<len; i++ ){

		quaternion.setFromAxisAngle( upVector, data[i].rotationInRadians );

		quaternion.normalize();	// no sure if this is actually needed, but I use it just in case
		
		instancePositions.push( data[i].position.x, data[i].position.y, data[i].position.z );
		
		instanceQuaternions.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
		
		instanceScales.push(  data[i].scale.x,  data[i].scale.y,  data[i].scale.z );		

	}
	
	var instancedGeometry = new THREE.InstancedBufferGeometry();

	// copy over data from original geometry
	instancedGeometry.attributes.position = geo.attributes.position;
	instancedGeometry.attributes.uv = geo.attributes.uv;
	instancedGeometry.attributes.normal = geo.attributes.normal;
	instancedGeometry.index = geo.index;
	instancedGeometry.groups = geo.groups;	
	
	// set instance position/rotation/scale buffer attributes
	instancedGeometry.addAttribute( 'instancePosition', new THREE.InstancedBufferAttribute( new Float32Array( instancePositions ), 3 ) );
	instancedGeometry.addAttribute( 'instanceQuaternion', new THREE.InstancedBufferAttribute( new Float32Array( instanceQuaternions ), 4 ) );
	instancedGeometry.addAttribute( 'instanceScale', new THREE.InstancedBufferAttribute( new Float32Array( instanceScales ), 3 ) );	
	
	var mesh = new THREE.Mesh(instancedGeometry,someMaterial);
	
	scene.add(mesh);

}

If you only need to uniformly scale instances i’d suggest using a vec4 for the instancePosition attribute to save the additional for a 3 axes scale.

2 Likes

Oh, great idea. Thanks!

1 Like

Thank you @titansoftime for the above. But I m still facing an issue with this… basically in understanding the shader logic.

After making the changes, I see the below error in the console

WebGL: INVALID_OPERATION: useProgram: program not valid
useProgram @ three.min.js:112
k @ three.min.js:172
renderBufferDirect @ three.min.js:196
q @ three.min.js:165
m @ three.min.js:165
render @ three.min.js:203
render @ defaults.js:143
init @ defaults.js:130
(anonymous) @ v2Common.js:10
three.min.js:112

My lack of knowledge in GLSL is taking a toll on me. Below are the Vertex and fragment shader codes I implemented.

Vertex Shader:

precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec3 offset;
attribute vec2 uv;
attribute vec4 orientation;
varying vec2 vUv;
attribute vec3 instancePosition;
attribute vec4 instanceQuaternion;
attribute vec3 instanceScale;
vec3 applyTRS( vec3 position, vec3 translation, vec4 quaternion, vec3 scale ) {
    position *= scale;
    position += 2.0 * cross( quaternion.xyz, cross( quaternion.xyz, position ) + quaternion.w * position );
    return position + translation;
}
    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 );
    // vUv = uv;
    // gl_Position = projectionMatrix * modelViewMatrix * vec4( offset + vPosition, 1.0 );
    transformed = applyTRS( transformed.xyz, instancePosition, instanceQuaternion, instanceScale );
    gl_Position = projectionMatrix * modelViewMatrix * vec4( transformed, 1.0 );
}

Fragment Shader

 precision highp float;
uniform sampler2D map;
varying vec2 vUv;
void main() {
    gl_FragColor = texture2D( map, vUv );
}

And my JS:

var material = new THREE.RawShaderMaterial( {
uniforms: {
        map: { value: wallTexture }
},
side: THREE.DoubleSide,
vertexShader: document.getElementById( 'vertexShader' ).textContent,
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
} );

var bufferGeometry = new THREE.PlaneBufferGeometry( 500 , 500, 50, 50 );
var data = [
{
    position: new THREE.Vector3(-125,100,-375),
    rotationInRadians : 0,
    scale: new THREE.Vector3(1,1,1)
},
{
    position: new THREE.Vector3(125,100,-125),
    rotationInRadians : -Math.PI/2,
    scale: new THREE.Vector3(1,1,1)
},
{
    position: new THREE.Vector3(-375,100,-125),
    rotationInRadians : Math.PI/2,
    scale: new THREE.Vector3(1,1,1)
},
{
    position: new THREE.Vector3(-125,100,125),
    rotationInRadians : Math.PI,
    scale: new THREE.Vector3(1,1,1)
},
]
createInstance(bufferGeometry,data);

function createInstance(geo,data) { 
var quaternion = new THREE.Quaternion();
var upVector = new THREE.Vector3(0,1,0); 
var instancePositions = [];
var instanceQuaternions = [];
var instanceScales = [];	

for( var i=0, len=data.length; i<len; i++ ){

	quaternion.setFromAxisAngle( upVector, data[i].rotationInRadians );

	quaternion.normalize();	// no sure if this is actually needed, but I use it just in case
	
	instancePositions.push( data[i].position.x, data[i].position.y, data[i].position.z );
	
	instanceQuaternions.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
	
	instanceScales.push(  data[i].scale.x,  data[i].scale.y,  data[i].scale.z );		

}

var instancedGeometry = new THREE.InstancedBufferGeometry();

// copy over data from original geometry
instancedGeometry.attributes.position = geo.attributes.position;
instancedGeometry.attributes.uv = geo.attributes.uv;
instancedGeometry.attributes.normal = geo.attributes.normal;
instancedGeometry.index = geo.index;
instancedGeometry.groups = geo.groups;	

// set instance position/rotation/scale buffer attributes
instancedGeometry.addAttribute( 'instancePosition', new THREE.InstancedBufferAttribute( new Float32Array( instancePositions ), 3 ) );
instancedGeometry.addAttribute( 'instanceQuaternion', new THREE.InstancedBufferAttribute( new Float32Array( instanceQuaternions ), 4 ) );
instancedGeometry.addAttribute( 'instanceScale', new THREE.InstancedBufferAttribute( new Float32Array( instanceScales ), 3 ) );	

var mesh = new THREE.Mesh(instancedGeometry,material);

scene.add(mesh);
}

I am really not sure where I am making the mistake. Really appreciate the time you are taking to help with this.

Regards,

Ah, you are using RawShaderMaterial. I do not have much experience with that. I generally extend existing built in three.js materials using onBeforeCompile. I find that to be the best approach unless you really need some serious custom shaders. That way all the nice features are preserved and you do not have to rebuild from scratch.

But since you are using RawShaderMaterial the transformed variable does not exist since that is part of the built in three.js materials. I believe you will want to do something like the following above the line that calls applyTRS:

vec3 transformed = position;

That should at least get you a bit further. Again, I’m not a shader master so I could be mistaken.

2 Likes

Ahh okay! I have not experimented with onBeforeCompile yet… Let me check the above case first and if not try with onBeforeCompile if needed.

I will mark it closed once I have experimented with it.

Thanks again! @titansoftime :slight_smile:

1 Like

Hey All,

Just to close this thread, the below is how I have now implemented the instancing after reading through the blog post multiple times to understand the basic concept of rotation.

I have added the below code for the orientation in JS.

// orientations
    var half_angle = (data[i].rotationInRadians * 0.5);
    x = axis.x * Math.sin(half_angle);
    y = axis.y * Math.sin(half_angle);
    z = axis.z * Math.sin(half_angle);
    w = Math.cos(half_angle);
    vector.set( x, y, z, w ).normalize();
    orientations.push( vector.x, vector.y, vector.z, vector.w );

Thanks to @titansoftime again for taking the time to show me the direction on this :slight_smile:

1 Like