0

I am trying to use more than two map channels in three.js to assign textures to my models. To my surprise no native materials in three.js support multiple sets of uvs.. Thus I have to use ShaderMaterial and write my own vertex/fragment shaders to have access to multiple map channels.

Now I did manage to get multi channel uvs to work with a simple code I worte:

<script id="vertex_shh" type="x-shader/x-vertex">

    //VERTEX SHADER//

        varying vec2 vUv;
        attribute vec2 uv2;
        varying vec2 vUv2;
        attribute vec2 uv3;
        varying vec2 vUv3;
        attribute vec2 uv4;
        varying vec2 vUv4;
        attribute vec2 uv5;
        varying vec2 vUv5;


        void main()
        {
            vUv = uv;
            vUv2 = uv2;
            vUv3 = uv3;
            vUv4 = uv4;
            vUv5 = uv5;                 

            vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
            gl_Position = projectionMatrix * mvPosition;
        }

    // END //

    </script>       



    <script id="fragment_shh" type="x-shader/x-fragment">   

    //FRAGMENT SHADER//

        uniform sampler2D tCh1;
        uniform sampler2D tCh2;
        uniform sampler2D tCh3;
        uniform sampler2D tCh4;         
        uniform sampler2D tCh5;

        varying vec2 vUv;
        varying vec2 vUv2;
        varying vec2 vUv3;
        varying vec2 vUv4;
        varying vec2 vUv5;


        void main(void)
        {
            vec3 DiffuseComp;
            vec4 TexCh1 = texture2D(tCh1, vUv);
            vec4 TexCh2 = texture2D(tCh2, vUv2);
            vec4 TexCh3 = texture2D(tCh3, vUv3);
            vec4 TexCh4 = texture2D(tCh4, vUv4);
            vec4 TexCh5 = texture2D(tCh5, vUv5);

            DiffuseComp = ((TexCh1.rgb * (1.0 - TexCh4.rgb) + (TexCh2.rgb * TexCh4.rgb)) * (1.0 - TexCh5.rgb)) + (TexCh3.rgb * TexCh5.rgb);
            gl_FragColor= vec4(DiffuseComp, 1.0);   
        }

    // END //

        /* SHADER */
        var vertShader = document.getElementById('vertex_shh').innerHTML;
        var fragShader = document.getElementById('fragment_shh').innerHTML;

        var attributes = {};

        var uniforms = {
        tCh1: { type: "t", value: Textura1},
        tCh2: { type: "t", value: Textura2},
        tCh3: { type: "t", value: Textura3},
        tCh4: { type: "t", value: Textura4},
        tCh5: { type: "t", value: Textura5}
        };          


        /* MATERIAL */
        var multitexsh = new THREE.ShaderMaterial({
        uniforms: uniforms,
        attributes: attributes,
        vertexShader: vertShader,
        fragmentShader: fragShader
        });

However, my problem is that I basically need a shader with this functionality AND some basic features of the Phong shader: lighting (from scene lights) and specular highlights. I am totally lost here. I've tried looking for ways to either add this functionality to native shaders or add the mentioned features from phong material to my own shader, but found no info that would be sufficient to do that with my very basic coding skills.. Could anybody step in and help me out here?


EDIT:

Thanks to pailhead got it working with onBeforeCompile. This is on r89, updated code bellow:

        /* MANAGERS, LOADERS */
        var manager = new THREE.LoadingManager();
        var loader = new THREE.FBXLoader( manager );
        var textureLoader = new THREE.TextureLoader( manager );

        /*TEXTURE */
        var prodtex ='textures/testing/cubetex.jpg';
        var prodtex2 ='textures/testing/cube_blendmap.jpg';

        var Textura1 = textureLoader.load (prodtex);
        var Textura2 = textureLoader.load (prodtex);
        var Textura3 = textureLoader.load (prodtex2);

        /* MATERIAL */
        var MAT = new THREE.MeshPhongMaterial();
        MAT.map = Textura1;     

        /* MY SHADER CHANGES*/
        const uv2_chunkToReplaceVS = 'attribute vec2 uv2; varying vec2 vUv2; attribute vec2 uv3; varying vec2 vUv3;'
        const uv2_chunkToReplaceVSV = 'vUv2 = uv2; vUv3 = uv3;'
        const uv2_chunkToReplaceFS = 'uniform sampler2D tCh1; uniform sampler2D tCh2; uniform sampler2D tCh3; varying vec2 vUv2; varying vec2 vUv3;'
        const map_chunkToReplaceFSV = 'vec4 TexCh1 = texture2D(tCh1, vUv); vec4 TexCh2 = texture2D(tCh2, vUv2); vec4 TexCh3 = texture2D(tCh3, vUv3); vec4 texelColor = texture2D( map, vUv ); texelColor = mapTexelToLinear( texelColor ); TexCh1 = mapTexelToLinear( TexCh1 ); TexCh2 = mapTexelToLinear( TexCh2 ); TexCh2 = mapTexelToLinear( TexCh2 ); diffuseColor *= TexCh1 * (1.0 - TexCh3) + (TexCh2 * TexCh3);'

        MAT.onBeforeCompile = shader=>{
        shader.uniforms.tCh1= {type: "t", value: Textura1};
        shader.uniforms.tCh2= {type: "t", value: Textura2};
        shader.uniforms.tCh3= {type: "t", value: Textura3};

        shader.fragmentShader = shader.fragmentShader.replace('#include <map_fragment>', map_chunkToReplaceFSV);
        shader.vertexShader = shader.vertexShader.replace('#include <uv2_pars_vertex>', uv2_chunkToReplaceVS );
        shader.vertexShader = shader.vertexShader.replace('#include <uv2_vertex>', uv2_chunkToReplaceVSV );
        shader.fragmentShader = shader.fragmentShader.replace('#include <uv2_pars_fragment>', uv2_chunkToReplaceFS);
        };


        /* MODEL */
        loader.load( ('models/' + 'cube' + '.FBX'), function( object ) {
            //set material//
            object.traverse( function ( child ) {
                if ( child instanceof THREE.Mesh ) {
                    child.material = MAT;
                };
            } );                
            scene.add (object);
        });
AGogel
  • 21
  • 5
  • searching for "extending threejs materials" might give some results: https://stackoverflow.com/questions/34853621/extending-three-meshbasicmaterial/34862278 & https://gist.github.com/karimbeyrouti/6c97dec164a11ca16bf3 (both a bit older, but should be close) – Don McCurdy Jan 23 '18 at 20:20

1 Answers1

2

You can use an undocumented feature of the material called onBeforeCompile.

It is a callback that is called before three parses the shader templates and before the shader is compiled.

A template may look like this

//someTemplate.vert

#include <common>
#include <some_pars_vertex>

void main(){
  #include <some_vertex>
  #include <another_vertex>

  gl_Position = projectionMatrix * foobar;
}

Let's say you want to add more uv channels. You'd make some snippets/functions in GLSL. This has to happen in the global scope (above void main(){...}):

//myBeforeVoidMain.vert

attribute vec2 aUV2;
attribute vec2 aUV3;

varying vec2 vUv2;
varying vec2 vUv3;

Then this has to happen in the main function:

//myUv.vert
vUv2 = aUV2;
vUv3 = aUV3;

You can inject this code, but where and how?

How:

const chunkToReplace = THREE.ShaderChunk.uv_pars_vertex

myMaterial = new THREE.MeshPhongMaterial()
myMaterial.onBeforeCompile( shader=>{
  shader.uniforms.myUniform = {value:foo} 
  shader.vertexShader = shader.vertexShader.replace( '#include <uv_pars_vertex>', chunkToReplace + myChunk )
})

Where:

You need to be familiar with both: https://github.com/mrdoob/three.js/tree/dev/src/renderers/shaders/ShaderChunk https://github.com/mrdoob/three.js/tree/dev/src/renderers/shaders/ShaderLib

And you need to give it some thought. One can note that <common> is present in the global scope of almost every lib. However, because it's included in both vert and frag shaders you can't have a mention of attribute in it, the frag shader will fail to compile.

One good chunk to add attributes, uniforms and varyings to the vertex shader is uv_pars_vertex because it is also present in almost all the shaders.

For this particular problem, you also have to extend the fragment shader. Again the varyings would be placed in uv_pars_fragment, and then you have to find the rest of the logic where you would utilize these uv channels.

note:

Might be a good idea to pack two channels into one attribute:

attribute vec4 aUv23;

...
  vUv2 = aUv23.xy;
  vUv3 = aUv23.zw;
pailhead
  • 5,162
  • 2
  • 25
  • 46
  • hello pailhead, thanks for such an in-depth answer. I guess this is one way to do it. I have not tried this yet, since before I read your answer I had started working on another approach. I'm trying to build a shader from chunks. I started by copying all the chunks from phong and then deleting the ones I don't need. Then with just a few chucks left, I would replace the UV_pars etc with my own and edit the gl_fragcolor to implement multiple uvs. However, my Shader (though it's a copy of PhongMaterial) does not work like the phongshader. is this approach even legit? what could I be doing wrong? – AGogel Jan 25 '18 at 21:02
  • hi again. I've tried doing it with onBeforeCompile.. must've missed something. could you please have a look at the original post? it's been edited with my current code – AGogel Jan 26 '18 at 15:26
  • in `onBeforeCompile( shader=> shader.uniforms.tch1 = { value: tex } )` – pailhead Jan 27 '18 at 23:11
  • pailhead, thanks for your input. I do think however, that there must be something wrong with the syntax here.. should't replacing #include with 'blah' mess the phongshader up? :) cause that's what I did and nothing happened: Mat.onBeforeCompile(shader=>{shader.vertexShader = shader.vertexShader.replace('#include ', 'blah');}) – AGogel Jan 29 '18 at 11:56
  • It should, what version of three are you on? – pailhead Jan 29 '18 at 16:21
  • The latest, 2017 dec 19 release – AGogel Jan 29 '18 at 20:02
  • Pailhead, **thank you very much for your help!!** finally got it working just the way I wanted! – AGogel Jan 30 '18 at 09:52
  • Thanks for the include list - it helps to know where to add the custom code. Although it's guesswork instead of being properly documented. – Yin Cognyto Apr 12 '22 at 22:13