Adding unique shader to a dynamically loaded 3D model, possible? onBeforeCompile confusion?

Hello.

I am trying to add a vertex shader dynamically to an animated 3D model in dddance.party

It is safe to say, I have no idea what I am doing : U

function AddVertexDisplacement(shader) {

 var newUniforms = {
     time: {
         value: 0
     },
     step: {
         value: 0
     },
     x_active: {
         value: 0
     },
     x_ampl: {
         value: 0
     },
     y_active: {
         value: 0
     },
     y_ampl: {
         value: 0
     },
     z_active: {
         value: 0
     },
     z_ampl: {
         value: 0
     },
     freq:{
         value: 0
     }
 };
 $.extend(shader.uniforms, newUniforms);

 console.log(shader.uniforms);

 shader.vertexShader = 'uniform float time;\n uniform float step;\n uniform float speed, x_active, y_active, z_active, x_ampl, y_ampl, z_ampl, freq; \n' + shader.vertexShader;
 shader.vertexShader = shader.vertexShader.replace(
     '#include <begin_vertex>',
     [
         'float pi = 3.14159265358979323846;',
        //  'float theta = sin( step + abs(10.*position.x)) / 10.0;',
        //  'float theta2 = sin( step + abs(10.*position.x + pi/2.)) / 10.0;',
        //  'theta = position.x/10.;',
        //  'float c = cos( theta );',
        //  'float s = sin( theta );',
        //  'float c2 = cos( theta2 );',
        //  'float s2 = sin( theta2 );',
        //  'mat3 mx = mat3( 1, 0, 0, 0, c, -s, 0, s, c );',
        //  'mat3 my = mat3( c, 0, s, 0, 1, 0, -s, 0, c );',
        //  'mat3 mz = mat3(c2, -s2, 0, s2, c2, 0, 0, 0, 1);',
        //  'mat3 m = my;',

         'vec3 transformed = position;',
        //  'transformed = vec3( transformed ) * m*mz;',
         'float p = 1.;',
         'float q = 1.;',
        //  'time *= ',
         'float tx = step + 3.*freq*( position.x/(2.*pi));',
         'float r = cos(q*tx);',
         'float x = r*cos(p*tx);',
         'float y = r*sin(p*tx);',
         'float z = -sin(q*tx);',
         'transformed.x += x_active * x_ampl* x;',
         'transformed.y += y_active * y_ampl * y;',
         'transformed.z += z_active * z_ampl * z;',
         // 'transformed.z += z;',
         //  'transformed = transformed.xzy;',

         // 'vNormal = vNormal * m;'
     ].join('\n')
 );

}

^^ here is my shader

	var WAVY_MATERIAL0 = new THREE.MeshPhongMaterial({
	color: 0xffffff,
	shininess: 0,
	specular: 0xffffff,
	skinning: true
});
dancers[0].wavy_material = WAVY_MATERIAL0;
WAVY_MATERIAL0.onBeforeCompile = (shader) => { // trouble :(
	AddVertexDisplacement(shader);
	dancers[0].wavy_shader = shader;
};

^^ here is my code to setup shader.

	if( USE_SHADER ){ //set vertex shader on phong materials
	avatar.traverse( function ( child ) {
		if ( child instanceof THREE.Object3D ) {
			if(child.material !== undefined){
				dancers[idx].wavy_material.map = child.material.map;
				dancers[idx].wavy_material.name = child.material.name;
				child.material = dancers[idx].wavy_material;
			}
		}
	});
}

^^ here is my code to add shader to model, after it loads.

I tried a few things but get a lot of strange behavior. I know the shader works because I can add it to models that are not loaded dynamically.

This seems very complicated. Maybe someone knows an easy solution here? This onBeforeCompile is bizarre.

Thanks.

Hi, I would check the child material maps before using them.

Also just a note, perhaps is better to replace
if ( child instanceof THREE.Object3D ) {
by
if ( child.isMesh ) {

Thanks for the reply.

As I mentioned the code is working fine for models that are loaded on init, but doesn’t work for models loaded dynamically.

Did you try material.needsUpdate?

Normally it helps alot to make a live fiddle for a question, it receives more feedback.

Ah yeah, let me put an example on Glitch this weekend! : )

Yay. Finally had time to create a demo:

https://threejs-shader-dynamic.glitch.me/
https://glitch.com/edit/#!/threejs-shader-dynamic

So the questions I have:

  1. The first created model has no shader applied. The shader is undefined. Why?
  2. Despite randomizing the shader parameters all the models added after the first one will have the same shader and shader parameters. How could I make it so these dynamically loaded models each have a unique shader?

Thanks so much.

Lets see. Why are you using onBeforeCompile? You only need to create your shader and material, and then assign the material to the meshes when they load.

About your second question: You will have to clone your material once for each mesh, in order to have the material uniforms separated and configurable independently for each one.

Thanks for your response.

Do you have any examples you can point me towards?

Hi,

Just call your function directly instead of the onBeforeCompile (the variable MY_SHADER is unuseful and removed):

(script.js)
  var SHADER_MATERIAL = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    shininess: 0,
    specular: 0xffffff,
    skinning: true
  });

  AddVertexDisplacement( SHADER_MATERIAL );

  var CLONE_SHADER_MATERIAL;

Clone your SHADER_MATERIAL once per model:

(script.js)  
//load that model
    CLONE_SHADER_MATERIAL = SHADER_MATERIAL.clone();

    myLoader.load_dae( new_business_man, MGROUP, ()=>{
      
      //apply settings to that model
      new_business_man.apply_settings_to_model( _settings );
      
      //add shader
      console.log("CLONE_SHADER_MATERIAL:");
      console.log(CLONE_SHADER_MATERIAL);
      new_business_man.shader = CLONE_SHADER_MATERIAL;
      var shader_param = {
        x:         Math.random()*1,
        y:         Math.random()*1,
        z:         Math.random()*1,
        speed:     3,
        duration:  3000,
        step:      0
      };
      new_business_man.shader_param = shader_param;
      
      //add model to models
      MODELS.push( new_business_man ); 
      
    });

And use the cloned material in the model:

(class-loader.js)
  CLONE_SHADER_MATERIAL.map = child.material.map;
  CLONE_SHADER_MATERIAL.name = child.material.name;
  child.material = CLONE_SHADER_MATERIAL;