Textures not correctly applied to cube faces in ModernGL rendering

42 Views Asked by At

I'm working on a Python project using the moderngl library for OpenGL rendering. I have a class that is responsible for rendering a textured cube, and I'm facing an issue where the textures are not correctly applied to the cube faces. The rendered result does not resemble a cube with distinct textures on each face.

import moderngl
from PIL import Image
import numpy as np
from pathlib import Path
from typing import Dict, Tuple, Optional

class BlockRender:
    class BlockDataType:
        def __init__(self, textures: Dict[str, str]) -> None:
            if not isinstance(textures, dict):
                raise ValueError("Textures must be a dictionary.")
            required_keys = {'top', 'bottom', 'back', 'front', 'left', 'right'}
            if not set(textures.keys()).issuperset(required_keys):
                raise ValueError("Textures dictionary must have keys: top, left, bottom, right, back, front.")
            self.textures = textures

        def read_block_data(self, ctx) -> Dict[str, moderngl.Texture]:
            image_data = {}
            for key, path in self.textures.items():
                try:
                    image = Image.open(path)
                    data = np.array(image.convert('RGBA'))
                    texture = ctx.texture(image.size, 4, data.tobytes())
                    texture.build_mipmaps()
                    image_data[key] = texture
                except Exception as e:
                    raise ValueError(f"Error reading '{key}' texture: {e}")
            return image_data

        def verify_path_and_extension(self) -> bool:
            for key, path in self.textures.items():
                try:
                    if not (Path(path).is_file() and Path(path).suffix.lower() in {'.jpg', '.jpeg', '.png', '.gimp'}):
                        raise ValueError(f"Invalid or unsupported file for '{key}' texture: {path}")
                except Exception as e:
                    raise ValueError(f"Error verifying path for '{key}' texture: {e}")
            return True

    def __init__(self, blockdata: BlockDataType, position: Optional[Tuple[float, float, float]] = (0, 0, 0)) -> None:
        if not isinstance(blockdata, self.BlockDataType):
            raise ValueError("blockdata must be an instance of BlockDataType.")
        self.blockdata = blockdata
        self.position = position

    def render_matrices_and_vertices(self, ctx: moderngl.Context) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
        vertices = np.array([
            # Front face
            [-0.5, -0.5, 0.5],  # 0
            [-0.5, 0.5, 0.5],  # 1
            [0.5, 0.5, 0.5],  # 2
            [0.5, -0.5, 0.5],  # 3
            # Back face
            [-0.5, -0.5, -0.5],  # 4
            [-0.5, 0.5, -0.5],  # 5
            [0.5, 0.5, -0.5],  # 6
            [0.5, -0.5, -0.5],  # 7
            # Top face
            [-0.5, 0.5, 0.5],  # 8
            [0.5, 0.5, 0.5],  # 9
            [0.5, 0.5, -0.5],  # 10
            [-0.5, 0.5, -0.5],  # 11
            # Bottom face
            [-0.5, -0.5, 0.5],  # 12
            [0.5, -0.5, 0.5],  # 13
            [0.5, -0.5, -0.5],  # 14
            [-0.5, -0.5, -0.5],  # 15
            # Left face
            [-0.5, -0.5, 0.5],  # 16
            [-0.5, 0.5, 0.5],  # 17
            [-0.5, 0.5, -0.5],  # 18
            [-0.5, -0.5, -0.5],  # 19
            # Right face
            [0.5, -0.5, 0.5],  # 20
            [0.5, 0.5, 0.5],  # 21
            [0.5, 0.5, -0.5],  # 22
            [0.5, -0.5, -0.5],  # 23
        ], dtype=np.float32)

        # Read textures from BlockDataType
        textures = self.blockdata.read_block_data(ctx)

        # Define texture coordinates for each face based on loaded textures
        texture_coords = np.zeros((vertices.shape[0], 2), dtype=np.float32)
        for key in ['top', 'bottom', 'back', 'front', 'left', 'right']:
            texture = textures[key]
            # Calculate texture coordinates based on texture size
            width, height = texture.width, texture.height
            texture_coords[:, 0] = np.interp(vertices[:, 0], [-0.5, 0.5], [0, width])
            texture_coords[:, 1] = np.interp(vertices[:, 1], [-0.5, 0.5], [height, 0])
            texture_coords /= np.array([width, height], dtype=np.float32)

        # Define the indices of the triangles
        indices = np.array([
            # Front face
            0, 1, 2, 0, 2, 3,
            # Back face
            4, 5, 6, 4, 6, 7,
            # Top face
            8, 9, 10, 8, 10, 11,
            # Bottom face
            12, 13, 14, 12, 14, 15,
            # Left face
            16, 17, 18, 16, 18, 19,
            # Right face
            20, 21, 22, 20, 22, 23,
        ], dtype=np.uint32)

        return vertices, texture_coords, indices


class Block:
    def __init__(self, textures: Dict[str, str], position: Optional[Tuple[float, float, float]] = (0, 0, 0)) -> None:
        block_data_type = BlockRender.BlockDataType(textures=textures)
        self.block_render = BlockRender(blockdata=block_data_type, position=position)

    def set_position(self, position: Tuple[float, float, float]) -> None:
        self.block_render.position = position

    def render(self, ctx: moderngl.Context) -> None:
        vertices, texture_coords, indices = self.block_render.render_matrices_and_vertices(ctx)

        # Create a shader program
        program = ctx.program(
            vertex_shader="""
            #version 330

            uniform mat4 model;
            in vec3 in_position;
            in vec2 in_texture;

            out vec2 frag_texture;

            void main() {
                gl_Position = model * vec4(in_position, 1.0);
                frag_texture = in_texture;
            }
            """,
            fragment_shader="""
            #version 330

            uniform sampler2D texture_sampler;
            in vec2 frag_texture;
            out vec4 color;

            void main() {
                color = texture(texture_sampler, frag_texture);
            }
            """
        )

        # Create vertex buffer
        vbo = ctx.buffer(vertices.tobytes())
        vao = ctx.simple_vertex_array(program, vbo, 'in_position', 'in_texture')

        # Set model matrix
        model_matrix = np.identity(4, dtype=np.float32)
        model_matrix[:3, 3] = self.block_render.position
        program['model'].write(model_matrix)

        # Render
        for key in ['top', 'bottom', 'back', 'front', 'left', 'right']:
            texture = self.block_render.blockdata.read_block_data(ctx)[key]
            texture.build_mipmaps()
            texture.use()
            vao.render(moderngl.TRIANGLES)

I suspect that there might be an issue with how I'm calculating the texture coordinates for each face, but I'm not sure where the problem lies. I've tried a few different approaches, including adjusting the texture coordinates based on the vertices' x and y coordinates, but none seem to produce the correct result.

0

There are 0 best solutions below