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);
});
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?
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 )
})
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.
Might be a good idea to pack two channels into one attribute:
attribute vec4 aUv23;
...
vUv2 = aUv23.xy;
vUv3 = aUv23.zw;