19.6.2019

Variable texture tiling and stretching(variable texture uv-coordinates in fast BufferGeometry)

In three.js is a problem, that UV-Coordinates and wrapping are stored in texture object, which leads to the necessity of using new material for each different coordinates even for the same texture. That is a performance problem even when the texture data is shared between materials.

With BufferGeometry its more problematic and using multi-materials for this purpose discards benefits.

So here is approach how to solve this problem by adding custom geometry attributes and simple shader update. Create BufferGeometry and also add parameters for UVs, tiling and shifting:

    /* ... */

    /* create geometry */
    this._geometry = new THREE.BufferGeometry();
    this._geometry.dynamic = true;

    /* coordinates */
    this._att_pos = new THREE.BufferAttribute(new Float32Array(0), 3);
    this._att_pos.setDynamic(true);

    this._geometry.addAttribute('position', this._att_pos);

    /* vertex coords indexes */
    this._att_idx = new THREE.BufferAttribute(new Uint32Array(0), 1);
    this._att_idx.setDynamic(true);

    this._geometry.setIndex(this._att_idx);

    /* custom color data */
    this._att_col = new THREE.BufferAttribute(new Float32Array(0), 3); // Uint8Array()
    this._att_col.setDynamic(true);

    this._geometry.addAttribute('color', this._att_col);

    /* UV-coordinates */
    this._att_uv = new THREE.BufferAttribute(new Float32Array(0), 2);
    this._att_uv.setDynamic(true);

    this._geometry.addAttribute('uv', this._att_uv);

    /* tiling xy coefficient */
    this._att_til = new THREE.BufferAttribute(new Float32Array(0), 2);
    this._att_til.setDynamic(true);

    this._geometry.addAttribute('texTile', this._att_til);

    /* shifting xy coefficient */
    this._att_tsh = new THREE.BufferAttribute(new Float32Array(0), 2);
    this._att_tsh.setDynamic(true);

    this._geometry.addAttribute('texTileShift', this._att_tsh);

    /* following geometry completition logic, adding to mesh etc. ... */

    /* ... */
                    

Now is needed to clone fragment and vertex shader from desired material (located in src\renderers\shaders\ShaderLib) for required extension:

Vertex shader:

    #define USE_MAP

    // ** input parameters for tiling and shifting **
    // ** (parameters from buffered geometry - the name must match) **

    attribute vec2 texTile;
    attribute vec2 texTileShift;

    // ** values for fragment shader **

    varying vec2 texTileOut;
    varying vec2 texTileShiftOut;

    #include <common>
    #include <uv_pars_vertex>
    #include <uv2_pars_vertex>
    #include <envmap_pars_vertex>
    #include <color_pars_vertex>
    #include <fog_pars_vertex>
    #include <morphtarget_pars_vertex>
    #include <skinning_pars_vertex>
    #include <logdepthbuf_pars_vertex>
    #include <clipping_planes_pars_vertex>

    void main() {

       // ** forward values to fragment shader **

       texTileOut = texTile;
       texTileShiftOut = texTileShift;

       #include <uv_vertex>
       #include <uv2_vertex>
       #include <color_vertex>
       #include <skinbase_vertex>
       #include <begin_vertex>
       #include <morphtarget_vertex>
       #include <skinning_vertex>
       #include <project_vertex>
       #include <logdepthbuf_vertex>

       #ifdef USE_ENVMAP

       #include <beginnormal_vertex>
       #include <morphnormal_vertex>
       #include <skinnormal_vertex>
       #include <defaultnormal_vertex>

       #endif

       #include <worldpos_vertex>
       #include <clipping_planes_vertex>
       #include <envmap_vertex>
       #include <fog_vertex>
    }
                    

Fragment shader:

    #define USE_MAP

    // ** parameters from previous shader **

    uniform vec3 diffuse;
    uniform float opacity;
    varying vec3 vNormal;

    // ** same values declaration **

    varying vec2 texTileOut;
    varying vec2 texTileShiftOut;

    // ** custom texture object **

    uniform sampler2D textureMap;

    #include <common>
    #include <color_pars_fragment>
    #include <uv_pars_fragment>
    #include <uv2_pars_fragment>

    // ** original map parameters fragment commented **
    // ** #include <map_pars_fragment>

    #include <alphamap_pars_fragment>
    #include <aomap_pars_fragment>
    #include <lightmap_pars_fragment>
    #include <envmap_pars_fragment>
    #include <fog_pars_fragment>
    #include <specularmap_pars_fragment>
    #include <logdepthbuf_pars_fragment>
    #include <clipping_planes_pars_fragment>

    void main() {

       #include <clipping_planes_fragment>

       vec4 diffuseColor = vec4( diffuse, opacity );

       #include <logdepthbuf_fragment>

       // ** original map parameters fragment commented **
       // ** #include <map_fragment>

       // ** usage of texTile and texTileShift parameters **
       // ** (the uv parameter is allready declared in original fragments) **

       vec4 texelColor = texture2D(textureMap, vUv * texTileOut + texTileShiftOut);

       texelColor = mapTexelToLinear( texelColor );
       diffuseColor *= texelColor;

       #include <color_fragment>
       #include <alphamap_fragment>
       #include <alphatest_fragment>
       #include <specularmap_fragment>

       ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );

       #ifdef USE_LIGHTMAP
           reflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;
       #else
           reflectedLight.indirectDiffuse += vec3( 1.0 );
       #endif

       #include <aomap_fragment>

       reflectedLight.indirectDiffuse *= diffuseColor.rgb;

       vec3 outgoingLight = reflectedLight.indirectDiffuse;

       #include <envmap_fragment>

       gl_FragColor = vec4( outgoingLight, diffuseColor.a );

       #include <premultiplied_alpha_fragment>
       #include <tonemapping_fragment>
       #include <encodings_fragment>
       #include <fog_fragment>

    }
                    

And now use it as ShaderMaterial:

    /* ... */

    /* create default uniforms (set of input parameters) */
    var uniforms = THREE.UniformsUtils.merge([
        THREE.ShaderLib.basic.uniforms
    ]);

    /* assign textureMap (located in fragment shader) */
    uniforms.textureMap = {
        type : "t",
        value : (new THREE.ImageLoader()).load('textures/tex.png')
    };

    /* and create shader material */
    var material = new THREE.ShaderMaterial({
        uniforms : uniforms,

        transparent : true,
        vertexColors : THREE.VertexColors,
        side : THREE.DoubleSide,
        depthTest : true,
        depthWrite : true,
        lights : false,

        vertexShader : `
            updated vertex shader
            ...
        `,
        fragmentShader : `
            updated fragment shader
            ...
        `
    });

    /* use geometry and shader material in the mesh */
    var mesh = new THREE.Mesh(geometry, material);

    /* ... */
                    

Each vertex in buffered geometry now may be stretched and tilled differently!

It may be combined with multi-material. But varying texture in one fragment shader is not possible - multi-material must be used.

© 2024 Dzejkob games | info@dzejkobgames.eu | YouTube channel | Itch.io