On the Fog of War Some of the issues

Seems a bit smaller than I expected.

Another good test would be to replace that test loop with:

for( int i = 0; i < 1024; i++ ) {
    for( int j = 0; j < 1024; j++ ) { 
        float f = (1f - i/1024) * (1f - j/1024);
        ColorRGBA color = new ColorRGBA(f, f, f, 1.0);
        raster.setPixel(i, j, color);
    }
}

…that should be a gradient across the whole map.

    #ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv.xy *= vec2(1024, 1024);

    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);
   
    // adjust lighting by the fog of war value
    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif
    public Texture2D image() {
        
        
      // Create the raw FOW overlay image
        int size = 1024;
        ByteBuffer data = BufferUtils.createByteBuffer(size * size * 4); // square image, four bytes per color
        Image image = new Image(Image.Format.ABGR8, size, size, data, ColorSpace.Linear);
        // Create a raster that we can draw to
        ImageRaster raster = ImageRaster.create(image);
           for( int i = 0; i < 1024; i++ ) {
            for( int j = 0; j < 1024; j++ ) { 
                float f = (1f - i/1024) * (1f - j/1024);
                ColorRGBA color = new ColorRGBA(f, f, f, 1.0f);
                raster.setPixel(i, j, color);
            }
        }
        // Create the texture to set to the FowMap on materials
        Texture2D fowMap = new Texture2D(image);

        return fowMap;
  
    }

Something is wrong I seem to only see grey like this not the gradient

I don’t know about image blending and what you’ve provided has given me a lot of insight, but now I can’t pinpoint what the problem is and I need your help. :sweat: :sweat: :sweat:

You still haven’t provided your “where is your map and how big is it?” values.

Is your ground just a big quad? Show the code for how you create the ground.

1 Like

Also, another typo in my code

Should be:
fowUv /= vec2(X_SIZE, Z_SIZE);

Edit: or in your case:
fowUv /= vec2(1024.0, 1024.0);

// Define vertices for the grid 
        //定义网格顶点
        int width = 1024;
        int height = 1024;
        float spacing = 1f;
        float[] vertices = new float[width * height * 3];
        float[] texCoords = new float[height * width * 2]; // Added UV coordinates
 
        for (int x = 0; x < height; x++) {
            for (int z = 0; z < width; z++) {
                vertices[(x * width + z) * 3] = z * spacing;
                vertices[(x * width + z) * 3 + 1] = 0;
                vertices[(x * width + z) * 3 + 2] = x * spacing;
                // Calculate UV coordinates
                texCoords[(x * width + z) * 2] = (float) x / (height - 1);
                texCoords[(x * width + z) * 2 + 1] = (float) z / (height - 1);
            }
        }



        // Define indices to create triangles
        int[] indices = new int[(width - 1) * (height - 1) * 6];
        int idx = 0;
        for (int x = 0; x < width - 1; x++) {
            for (int z = 0; z < height - 1; z++) {
                int topLeft = x * height + z;
                int topRight = topLeft + 1;
                int bottomLeft = (x + 1) * height + z;
                int bottomRight = bottomLeft + 1;

                // First triangle 第一个三角形
                indices[idx++] = topLeft;
                indices[idx++] = bottomLeft;
                indices[idx++] = topRight;

                // Second triangle 第二个三角形
                indices[idx++] = topRight;
                indices[idx++] = bottomLeft;
                indices[idx++] = bottomRight;
            }
        }

        // Create a Mesh
        Mesh mesh = new Mesh();
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indices));
        mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoords)); // Set UV coordinates
        mesh.updateBound();
        int width = 1024;
        int height = 1024;

This is the length and width of the map.
The length of each cell is “spacing”.

I use this approach to generate maps

Perfect. And presumably, mesh is then put into a geometry that’s located at 0,0,0?

Anyway, fix the line I messed up in my earlier code and you should see something different.

Yes!

    #ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);

    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);
   
    // adjust lighting by the fog of war value
    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif

This is the vertex shader code I’m currently using
I fixed that line you were talking about.

But nothing seems to have changed.

Do you have any filter post processing or anything set?

I’m kind of at a loss. It’s probably something simple but I’m not sure what. Now is the part where I would do some debugging to try to figure out what’s really happening.

Maybe after this line:

fowUv /= vec2(1024.0, 1024.0);

Temporarily add:

fowUv = vec2(0.0, 0.0);

…see what happens. I expect everything to be black.
Then try instead:

fowUv = vec2(1.0, 1.0);

…see what happens. I expect everything to be the normal terrain color.

What is the normal terrain color, by the way?

After that, you could try confirming that the uv math is right.
After:

DiffuseSum *= vec4(fow.rgb, 1.0);

Add a line like:

DiffuseSum = vec4(fowUv.x, fowUv.y, 0.0, 1.0);

I’d expect that to be a very colorful gradient over the whole map.

// already covered by paul

Oh yeah you reminded me I don’t seem to have added a picture of the terrain yet

package com.mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState.FaceCullMode;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.debug.Arrow;
import com.jme3.scene.shape.Box;

import com.jme3.system.AppSettings;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace;
import com.jme3.texture.image.ImageRaster;

import com.jme3.util.BufferUtils;
import com.jme3.util.SkyFactory;
import java.nio.ByteBuffer;


public class SimpleFogGridApp extends SimpleApplication {
    Spatial sky2;

    Material material;
    boolean booleanMat=true;
    public static void main(String[] args) {
        SimpleFogGridApp app = new SimpleFogGridApp();
        AppSettings setting = new AppSettings(true);
        setting.setWindowSize(1920, 1080);
        app.setSettings(setting);
        app.start();
    }

    @Override
    public void simpleInitApp() {
        sky2 = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap);
        rootNode.attachChild(sky2);
        // 创建X、Y、Z方向的箭头,作为参考坐标系。
        createArrow(new Vector3f(5, 0, 0), ColorRGBA.Green);
        createArrow(new Vector3f(0, 5, 0), ColorRGBA.Red);
        createArrow(new Vector3f(0, 0, 5), ColorRGBA.Blue);


        // Define vertices for the grid 
        //定义网格顶点
        int width = 1024;
        int height = 1024;
        float spacing = 1f;
        float[] vertices = new float[width * height * 3];
        float[] texCoords = new float[height * width * 2]; // Added UV coordinates
 
        for (int x = 0; x < height; x++) {
            for (int z = 0; z < width; z++) {
                vertices[(x * width + z) * 3] = z * spacing;
                vertices[(x * width + z) * 3 + 1] = 0;
                vertices[(x * width + z) * 3 + 2] = x * spacing;
                // Calculate UV coordinates
                texCoords[(x * width + z) * 2] = (float) x / (height - 1);
                texCoords[(x * width + z) * 2 + 1] = (float) z / (height - 1);
            }
        }



        // Define indices to create triangles
        int[] indices = new int[(width - 1) * (height - 1) * 6];
        int idx = 0;
        for (int x = 0; x < width - 1; x++) {
            for (int z = 0; z < height - 1; z++) {
                int topLeft = x * height + z;
                int topRight = topLeft + 1;
                int bottomLeft = (x + 1) * height + z;
                int bottomRight = bottomLeft + 1;

                // First triangle 第一个三角形
                indices[idx++] = topLeft;
                indices[idx++] = bottomLeft;
                indices[idx++] = topRight;

                // Second triangle 第二个三角形
                indices[idx++] = topRight;
                indices[idx++] = bottomLeft;
                indices[idx++] = bottomRight;
            }
        }

        // Create a Mesh
        Mesh mesh = new Mesh();
        mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices));
        mesh.setBuffer(Type.Index, 3, BufferUtils.createIntBuffer(indices));
        mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoords)); // Set UV coordinates
        mesh.updateBound();

        // Create a Geometry to display the mesh
        Geometry gridGeometry;  gridGeometry = new Geometry("Grid", mesh);

        // Create a material and set its color "Shaders/FogOfWar/Fog.j3md"
        if(booleanMat){
         material = new Material(assetManager, "Shaders/FogOfWar/FOWLighting.j3md");
        material.setBoolean("HasFow", true);
        material.setTexture("FowMap", image());
        material.setTexture("LightMap", assetManager.loadTexture("Textures/map/dirt.png"));
        //material.getAdditionalRenderState().setWireframe(true);
        material.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off);
        }else{
         material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
         material.setTexture("ColorMap", image());
        }

        
        
        
        
        gridGeometry.setMaterial(material);
        // Attach the gridGeometry to the rootNode
        rootNode.attachChild(gridGeometry);

        flyCam.setMoveSpeed(100f);
        
//        // 定向光
//        DirectionalLight sun = new DirectionalLight();
//        sun.setDirection(new Vector3f(-1, -2, -3));

        // 环境光
        AmbientLight ambient = new AmbientLight();

        // 调整光照亮度
        ColorRGBA lightColor = new ColorRGBA();
        //sun.setColor(lightColor.mult(0.8f));
        ambient.setColor(lightColor.mult(0.2f));
        
        //rootNode.addLight(sun);
        rootNode.addLight(ambient);
    }

    @Override
    public void simpleUpdate(float tpf) {
        
    }

    /**
     * 创建一个箭头
     *
     * @param vec3 箭头向量
     * @param color 箭头颜色
     */
    private void createArrow(Vector3f vec3, ColorRGBA color) {
        // 创建材质,设定箭头的颜色
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", color);
        mat.getAdditionalRenderState().setWireframe(true);
        // 创建几何物体,应用箭头网格。
        Geometry geom = new Geometry("arrow", new Arrow(vec3));
        geom.setMaterial(mat);

        // 添加到场景中
        rootNode.attachChild(geom);
    }

    public Texture2D image() {
        
        
      // Create the raw FOW overlay image
        int size = 1024;
        ByteBuffer data = BufferUtils.createByteBuffer(size * size * 4); // square image, four bytes per color
        Image image = new Image(Image.Format.ABGR8, size, size, data, ColorSpace.Linear);
        // Create a raster that we can draw to
        ImageRaster raster = ImageRaster.create(image);
           for( int i = 0; i < 1024; i++ ) {
            for( int j = 0; j < 1024; j++ ) { 
                float f = (1f - i/1024) * (1f - j/1024);
                ColorRGBA color = new ColorRGBA(f, f, f, 1.0f);
                raster.setPixel(i, j, color);
            }
        }
        // Create the texture to set to the FowMap on materials
        Texture2D fowMap = new Texture2D(image);

        return fowMap;
  
    }
    
    

}

Here’s the example I’m currently using I’ve added a terrain image

I’m not sure if it’s something in my code that conflicts with what you’ve provided I’ve put the example I’m using above.

DiffuseSum = vec4(fowUv.x, fowUv.y, 0.0, 1.0);

Nothing seems to change when I try this either, which makes me wonder if it’s a problem with my UVs
But when I look at the LightMap I added on there doesn’t seem to be an error.

material.setTexture("LightMap", assetManager.loadTexture("Textures/map/dirt.png"));

If I add this texture

I tried to use the method you provided
But all three attempts resulted in the same picture.
I turned off the terrain texture (which seems to make it more visible).
Here are the results of the attempt

    #ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);
    fowUv = vec2(0, 0);
    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);

    // adjust lighting by the fog of war value

    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif
      
    #ifdef VERTEX_COLOR
      AmbientSum *= inColor.rgb;
      DiffuseSum *= inColor;
    #endif

#ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);
    fowUv = vec2(1, 1);
    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);

    // adjust lighting by the fog of war value

    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif

#ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);
    //fowUv = vec2(1, 1);
    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);

    // adjust lighting by the fog of war value
    DiffuseSum = vec4(fowUv.x, fowUv.y, 0.0, 1.0);
    //DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif

Why are you using a light map to show dirt? Dirt is not a light. Dirt is a texture.

I think you should be using DiffuseMap for this.

material.setTexture("DiffuseMap", assetManager.loadTexture("Textures/map/dirt.png"));
    #ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);
    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);

    // adjust lighting by the fog of war value
    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif
      
    #ifdef VERTEX_COLOR
      AmbientSum *= inColor.rgb;
      DiffuseSum *= inColor;
    #endif

I don’t know if Big this is what you expected (I’m guessing not)

Let’s go back a few steps.

After this block:

    #ifdef VERTEX_COLOR
      AmbientSum *= inColor.rgb;
      DiffuseSum *= inColor;
    #endif

Add this:

DiffuseSum = vec4(1.0, 0.0, 0.0, 1.0);

…which is one of the material wiring tests I had you do before and I’d like to confirm the outcome.

Edit: and if that makes everything red then take that line out and put back the other test line at the end of the HAS_FOW block:

DiffuseSum = vec4(fowUv.x, fowUv.y, 0.0, 1.0);
    #ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);
    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);

    // adjust lighting by the fog of war value
    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif

    DiffuseSum = vec4(1.0, 0.0, 0.0, 1.0);

    #ifdef VERTEX_COLOR
      AmbientSum *= inColor.rgb;
      DiffuseSum *= inColor;
    #endif

It didn’t turn red.

DiffuseSum = vec4(1.0, 0.0, 0.0, 1.0);

The result of this paragraph is the same as the graph above with little change

Then something else is strange about your scene setup and I don’t know anymore.

…the fact that you were for some reason using a LightMap for your texture already points to being in a really weird place to begin with.

Try a simple scene with a big JME box and the FowLighting material. Post the scene setup.

Edit: with the current FowLighting.vert debug stuff still in it.

    #ifdef HAS_FOW
    // Get the x,z location of the vertex in world space
    vec2 worldXz = (TransformWorld(modelSpacePos)).xz;
    // Change the world space coordinate to texture space 0..1
    vec2 fowUv = worldXz - vec2(0, 0); // use your actual values here in format like 0.0
    fowUv /= vec2(1024.0, 1024.0);
    // Sample the texture
    vec4 fow = texture2D(m_FowMap, worldXz);

    // adjust lighting by the fog of war value
    DiffuseSum *= vec4(fow.rgb, 1.0);
    AmbientSum *= fow.rgb;
    SpecularSum *= fow.rgb;
    #endif

    #ifdef VERTEX_COLOR
      AmbientSum *= inColor.rgb;
      DiffuseSum *= inColor;
    #endif
    
    DiffuseSum = vec4(1.0, 0.0, 0.0, 1.0);
package com.mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Box;
import com.jme3.texture.Image;
import com.jme3.texture.Texture2D;
import com.jme3.texture.image.ColorSpace;
import com.jme3.texture.image.ImageRaster;
import com.jme3.util.BufferUtils;
import java.nio.ByteBuffer;

/**
 * 你的第一个jME3程序
 * @author yanmaoyuan
 */
public class JmeBox extends SimpleApplication {

	/**
	 * 初始化3D场景,显示一个方块。
	 */
	@Override
	public void simpleInitApp() {
		

        Mesh box = new Box(1, 1, 1);



        Material mat = new Material(assetManager, "Shaders/FogOfWar/FOWLighting.j3md");
        mat.setBoolean("HasFow", true);
        mat.setTexture("FowMap", image());

        Geometry geom = new Geometry("Box");
        geom.setMesh(box);
        geom.setMaterial(mat);



        // 环境光
        AmbientLight ambient = new AmbientLight();

        rootNode.attachChild(geom);
        rootNode.addLight(ambient);
	}

	public static void main(String[] args) {
		// 启动jME3程序
		JmeBox app = new JmeBox();
		app.start();
	}
        
            public Texture2D image() {
        
        
      // Create the raw FOW overlay image
        int size = 1024;
        ByteBuffer data = BufferUtils.createByteBuffer(size * size * 4); // square image, four bytes per color
        Image image = new Image(Image.Format.ABGR8, size, size, data, ColorSpace.Linear);
        // Create a raster that we can draw to
        ImageRaster raster = ImageRaster.create(image);
           for( int i = 0; i < 1024; i++ ) {
            for( int j = 0; j < 1024; j++ ) { 
                float f = (1f - i/1024) * (1f - j/1024);
                ColorRGBA color = new ColorRGBA(f, f, f, 1.0f);
                raster.setPixel(i, j, color);
            }
        }
        // Create the texture to set to the FowMap on materials
        Texture2D fowMap = new Texture2D(image);

        return fowMap;
  
    }

}

It didn’t turn red.
I repeated the previous test with no changes as shown in this screenshot :sweat_drops: :sweat_drops:

You shouldn’t have to do this but I see that something got cut off in my original post:

Scroll down to " Technique {" and then to " Defines { " and add:

HAS_FOW: FowMap

…which will then automatically set HAS_FOW if you have set a FowMap.

It’s not why it’s not red but it could cause problems if that’s not setup right.

Yeah, it could be because you have no directional light. Diffuse lighting only works with a DirectionalLight.

1 Like

Okay, I’ve made some changes.

Technique {

        LightMode MultiPass

        VertexShader GLSL310 GLSL300 GLSL100 GLSL150:   Shaders/FogOfWar/FOWLighting.vert
        FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Shaders/FogOfWar/FOWLighting.frag

        WorldParameters {
            WorldViewProjectionMatrix
            NormalMatrix
            WorldViewMatrix
            ViewMatrix
            CameraPosition
            WorldMatrix
            ViewProjectionMatrix            
        }

        Defines {
            VERTEX_COLOR : UseVertexColor
            VERTEX_LIGHTING : VertexLighting            
            MATERIAL_COLORS : UseMaterialColors
            DIFFUSEMAP : DiffuseMap
            NORMALMAP : NormalMap
            SPECULARMAP : SpecularMap
            PARALLAXMAP : ParallaxMap
            NORMALMAP_PARALLAX : PackedNormalParallax
            STEEP_PARALLAX : SteepParallax
            ALPHAMAP : AlphaMap
            COLORRAMP : ColorRamp
            LIGHTMAP : LightMap
            SEPARATE_TEXCOORD : SeparateTexCoord
            DISCARD_ALPHA : AlphaDiscardThreshold
            USE_REFLECTION : EnvMap
            SPHERE_MAP : EnvMapAsSphereMap  
            NUM_BONES : NumberOfBones                        
            INSTANCING : UseInstancing
            NUM_MORPH_TARGETS: NumberOfMorphTargets
            NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers
            
            // fog - jayfella
            USE_FOG : UseFog
            FOG_LINEAR : LinearFog
            FOG_EXP : ExpFog
            FOG_EXPSQ : ExpSqFog

            //add
            HAS_FOW : FowMap
        }
    }

I tried adding DirectionalLight and nothing seems to have changed.

I took a few steps backwards.

           for( int i = 0; i < 1023; i++ ) {
            for( int j = 0; j < 1023; j++ ) { 
                float f = (1f - i/1023) * (1f - j/1023);
                ColorRGBA color = new ColorRGBA(f, f, f, 1.0f);
                raster.setPixel(i, j, color);
            }
        }


This confuses me a bit even though I’m size 1023 it still only has one corner.

If I set it to 1024 it looks like this.

additions:
Although I know relatively little about shaders, I’ve come up with a relatively simple way to deal with it that is to expand the resolution to make 1024*8
He looks like this.


But bringing the camera closer still shows the jaggies still seems to require a shader scheme to remove this part of the jaggies :face_exhaling:

You habe to validate the texture coordinates first. as long as printing the uv does not produces a nice gradient you end up working around the primary issue

I can’t see that code so I can’t help.

At this point, you should be able to post a complete cube-based single-class (+materials) example.