Suggestion: Material Auto Texture Array

More of a test case and an interesting idea for discussion than anything, this class automatically converts multiple textures into a texture array for PBR (at least the logic is there for scrutiny) using bitmasking.

We basically set a bit to 1 or 0 depending on whether or not that texture was set, which we can use in the shader to determine the index of the texture (just add up all the bits that are “set” up to the index of our texture).

As outlined in the remarks: If we typically use a basecolormap, metallic, roughness and normal map, worst case is we use 2 textures instead of 4. If we use all 7 slots, we convert 7 textures into 2.

I’m more interested in what you think of the idea and where you feel it may be better suited (e.g. terrain materials usually have a large texture count).

package com.jayfella.test;

import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.TextureArray;
import com.jme3.texture.image.ColorSpace;

import java.util.ArrayList;
import java.util.List;

/**
 * A class to automatically convert a PBR texture into 2 texture arrays (sRGB and Linear) using bitmasking.
 */
public class TestPbrArray {

    // these bitmasks are going to tell us which textures have been set in the shader.
    private byte srgb = 0;
    private byte linear = 0;

    private TextureArray srgbTextureArray = null;
    private TextureArray linearTextureArray = null;

    // srgb
    private Texture2D baseColorMap;
    private Texture2D emissiveMap;

    // linear
    private Texture2D metallicMap;
    private Texture2D roughnessMap;
    private Texture2D metallicRoughnessMap;
    private Texture2D normalMap;
    private Texture2D parallaxMap;

        /*
            PBR Lighting has the following materials:

            - 0 BaseColorMap
            - 1 EmissiveMap

            - 0 MetallicMap - Linear
            - 1 RoughnessMap - Linear
            - 2 MetallicRoughnessMap - Linear
            - 3 NormalMap - Linear
            - 4 ParallaxMap - Linear

            That's 7 textures, so we can use a byte (8 bits in a byte)
            All but 2 are linear, so we'll have 2 bitmasks.

            We'll have to remember this layout to determine what's what in the shader.

            So if we typically use a basecolormap, metallic, roughness and normal map, best case is we use 2 textures
            instead of 4. If we use all 7 slots, we convert 7 textures into 2.

            Typical usecase:
                - srgb:     10000000; // basecolormap only
                - linear:   11010000: // metallic, roughness, normal

         */

    public TestPbrArray() {

    }

    public void setBaseColorMap(Texture2D texture2D) {
        baseColorMap = texture2D;
        createSrgbTextureArray();
    }

    public void setEmissiveMap(Texture2D emissiveMap) {
        this.emissiveMap = emissiveMap;
        createSrgbTextureArray();
    }

    public void setMetallicMap(Texture2D metallicMap) {
        this.metallicMap = metallicMap;
        createLinearTextureArray();
    }

    public void setRoughnessMap(Texture2D roughnessMap) {
        this.roughnessMap = roughnessMap;
        createLinearTextureArray();
    }

    public void setMetallicRoughnessMap(Texture2D metallicRoughnessMap) {
        this.metallicRoughnessMap = metallicRoughnessMap;
        createLinearTextureArray();
    }

    public void setNormalMap(Texture2D normalMap) {
        this.normalMap = normalMap;
        createLinearTextureArray();
    }

    public void setParallaxMap(Texture2D parallaxMap) {
        this.parallaxMap = parallaxMap;
        createLinearTextureArray();
    }

    private void createSrgbTextureArray() {

        byte bitmask = 0;

        List<Image> images = new ArrayList<>();

        if (baseColorMap != null) {
            images.add(baseColorMap.getImage());
            bitmask = set(bitmask, 0);
        }

        if (emissiveMap != null) {
            images.add(emissiveMap.getImage());
            bitmask = set(bitmask, 1);
        }

        srgb = bitmask;

        TextureArray textureArray = null;

        if (images.size() > 0) {
            textureArray = new TextureArray(images);
        }

        srgbTextureArray = textureArray;
    }

    private void createLinearTextureArray() {

        byte bitmask = 0;

        List<Image> images = new ArrayList<>();

        if (metallicMap != null) {
            images.add(metallicMap.getImage());
            bitmask = set(bitmask, 0);
        }

        if (roughnessMap != null) {
            images.add(roughnessMap.getImage());
            bitmask = set(bitmask, 1);
        }

        if (metallicRoughnessMap != null) {
            images.add(metallicRoughnessMap.getImage());
            bitmask = set(bitmask, 2);
        }

        if (normalMap != null) {
            images.add(normalMap.getImage());
            bitmask = set(bitmask, 3);
        }

        if (parallaxMap != null) {
            images.add(parallaxMap.getImage());
            bitmask = set(bitmask, 4);
        }

        linear = bitmask;

        TextureArray textureArray = null;

        if (images.size() > 0) {
            textureArray = new TextureArray(images);
            textureArray.getImage().setColorSpace(ColorSpace.Linear);
        }

        linearTextureArray = textureArray;
    }


    private byte set(byte bitmask, int bit) {
        return bitmask |= 1 << bit;
    }

    private byte unset(byte bitmask, int bit) {
        return bitmask &= ~(1 << bit);
    }


}
2 Likes

What was the motivation for wanting to do this?

Edit: or is this for terrain or something? I’m just missing something from the description I think.

To improve performance on materials with high texture counts like the terrain materials. I used pbr as an example since most have used it.

I’m not sure that’s as true as you might think.

I thought it was mostly a notational convenience more than a performance consideration. Like arrays in material parameters, it’s still multiple material parameters internally.

I could be wrong, though.

I’d think any extra logic in the shader to figure out the texture index would gobble up any gains for a regular shader like PBR.

1 Like