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);
}
}