7
votes

In Three.js, is it possible to create a material (shader?) containing a number of textures, each one with its own specifics, blend them together and assign to geometry? I'd like to simulate paper surface with multiple finishing techniques applied like foil embossing or spot UV varnishing.

What I'd like to achieve is a material that would have the following characteristics:

  1. 'Base' layer:
    • paper colour (e.g. white, off white, beige, etc)
    • specularity
    • reflectivity
    • bump (smooth or textured paper)
  2. 'Image' layer (optional):
    • image texture (artwork printed on paper, multiply-blended with 'Base' colour)
  3. 'Foil' layer (optional):
    • b/w image texture to control layer transparency
    • colour (overlapping 'Base' layer)
    • specularity
    • reflectivity
  4. 'Emboss/Deboss' layer (optional):
    • bump texture (raising or lowering 'Base' bump)
  5. 'Varnish' layer (optional):
    • b/w image texture to control layer transparency
    • specularity
    • reflectivity
    • bump (slightly raising 'Base' bump)

The reason for all the separate layers is I'd like to control them individually (replace different textures and change values at runtime). Apart from the 'Image' layer, I'm not sure what blend values I should use for the other layers to achieve realistic looking simulation.

To illustrate the finishing techniques applied to paper, take a look at the following photographs (courtesy of PrintHouse Corporation):

Blind embossing (pattern) & gold foil debossing (logo, title)Blind embossing (pattern) & gold foil debossing (logo, title)

Silver foil debossing (logo) and spot UV varnish (photos)Silver foil debossing (logo) and spot UV varnish (photos)

Spot UV varnish (lines)Spot UV varnish (lines)

Is it at all possible? I couldn't find any Three.js example that would use such complex materials. If you could point me to one or explain how to approach this programmatically, I'd be grateful.

1
This is an awesome project, IMHO. @mrdoob, any ideas? I expect [at]alteredq could do this in his sleep. :-)WestLangley
There is the MeshLayerMaterial system that would allow this kind of materials, but it's still a bit to early for me to get working on it. You could do this with a custom shader and ShaderMaterial though.mrdoob
Thanks for the info and looking forward to MeshLayerMaterial thing. Is there an example of a custom shader code with similar complexity to my question and some explanation how to use/blend multiple uniforms? Cheers!ALx

1 Answers

1
votes

Like the comments say, you can do this with a custom shader. You will just have to think about how to combine the different layers yourself in the shader. So just to give you an example, you can set up your shader in your js like this:

blurTexture360 = new THREE.TextureLoader().load(imgURLBlur);
unblurTexture360 = new THREE.TextureLoader().load(imgURLUnblur);
highlight1Texture360 = new THREE.TextureLoader().load(imgURLHighlight1);
highlight2Texture360 = new THREE.TextureLoader().load(imgURLHighlight2);

blurTexture360.magFilter = THREE.LinearFilter;
blurTexture360.minFilter = THREE.LinearFilter;

const shader = THREE.ShaderLib.equirect;
uniforms = {
  u_resolution: { type: "v2", value: resolution },
  u_blur: { type: "t", value: blurTexture360 },
  u_unblur: { type: "t", value: unblurTexture360 },
  u_highlight1: { type: "t", value: highlight1Texture360 },
  u_highlight2: { type: "t", value: highlight2Texture360 },
  u_mix: { type: "f", value: 0.0 },
  u_highlightAmt: { type: "f", value: 0.0 },
  u_highlight1Bool: { type: "f", value: 1.0 },
  u_highlight2Bool: { type: "f", value: 1.0 }
};
let material360 = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: shader.vertexShader,
  fragmentShader: fragmentShader(),
  depthWrite: false,
  side: THREE.BackSide
});

then to set the uniforms elsewhere

uniforms.u_highlightAmt.value -= 0.05;

this this is an example of setting different textures to come in when u_highlight1Bool or u_highlight2Bool is set to 1 or 0. Hopefully this gives you an example of using logic to affect your shader code.

function fragmentShader() {
    return `        
uniform sampler2D u_blur;
uniform sampler2D u_unblur;
uniform sampler2D u_highlight1;
uniform sampler2D u_highlight2;

uniform vec2 u_resolution;
uniform float u_mix;
uniform float u_highlight1Bool;
uniform float u_highlight2Bool;
uniform float u_highlightAmt;

varying vec3 vWorldDirection;
#define RECIPROCAL_PI2 0.15915494309189535
#define RECIPROCAL_PI 0.3183098861837907

vec2 equirectUv( in vec3 dir ) {
    // dir is assumed to be unit length
    float u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;
    float v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;
    return vec2( u, v );
}

void main() {
    vec3 direction = normalize( vWorldDirection );
    vec2 sampleUV = equirectUv( direction );
  vec4 blur =  texture2D(u_blur, sampleUV);
  vec4 unblur =  texture2D(u_unblur,  sampleUV);
  vec4 highlight1 = texture2D(u_highlight1, sampleUV);
  vec4 highlight2 = texture2D(u_highlight2, sampleUV);

    blur = mapTexelToLinear( blur );
  unblur = mapTexelToLinear( unblur );
  highlight1 = mapTexelToLinear(highlight1);
  highlight2 = mapTexelToLinear(highlight2);
  vec4 highlight = (highlight1*u_highlight1Bool) + (highlight2 * u_highlight2Bool);

  vec4 ret = mix(blur, unblur, u_mix);
  float thresh = ceil(highlight.r - 0.09) * u_highlightAmt;
  ret = mix(ret,highlight,thresh);

  gl_FragColor = ret;
}
`;

I got this from my code here: https://glitch.com/edit/#!/hostileterrain94?path=script%2FthreeSixtyEnv.js%3A202%3A0 its not super well documented but if you wanted to see how this exists in a broader project feel free to take a peek. I dot claim this is really efficient but I know it can get the job done.