Mugichoko's blog

Mugichoko’s blog

しがない研究者のプログラミングを中心としたメモ書き.

ModernGL奮闘記 (9) - Texture Array -

Texture Arrayを実装する.上下キーを押すことでTexture Array内の2つのテクスチャを切り替える.

コード

# glfw_quad_tex_arr.py
from pathlib import Path
import moderngl as mgl
import moderngl_window
from moderngl_window.conf import settings
from moderngl_window import resources
from moderngl_window.resources import programs
from moderngl_window.meta import ProgramDescription
from moderngl_window.scene.camera import Camera
from PIL import Image
import numpy as np


class Quad:
    def __init__(self, ctx: mgl.Context, prog: mgl.Program) -> None:
        self.prog = prog

        # Quad
        vertices = np.array([
            # x, y, u, v
            -0.5, -0.5, 0.0, 0.0,  # 0: Bottom left
            +0.5, -0.5, 1.0, 0.0,  # 1: Bottom right
            +0.5, +0.5, 1.0, 1.0,  # 2: Top right
            -0.5, +0.5, 0.0, 1.0,  # 3: Top left
        ], dtype="f4")
        indices = np.array([0, 1, 2, 3], dtype="i4")

        self.vbo = ctx.buffer(vertices)
        self.ibo = ctx.buffer(indices)

        vao_content = [(self.vbo, "2f 2f", "in_vert", "in_uv")]
        self.vao = ctx.vertex_array(self.prog, vao_content, self.ibo)

    def render(
        self, camera: Camera, tex_arr: mgl.texture_array, tex_arr_idx: int
    ) -> None:
        self.prog["M"].write(np.identity(4, dtype="f4"))
        self.prog["V"].write(camera.matrix)
        self.prog["P"].write(camera.projection.matrix)
        # https://moderngl.readthedocs.io/en/latest/reference/texture.html#moderngl.Texture.use
        tex_arr.use(location=0)
        self.prog["tex_arr_idx"].write(np.array([tex_arr_idx], dtype="i4"))
        self.vao.render(mgl.TRIANGLE_FAN)


class App:
    def __init__(self, width=512, height=512) -> None:
        # Window
        # create a gl window: https://moderngl-window.readthedocs.io/en/latest/reference/settings.conf.settings.html#moderngl_window.conf.Settings.WINDOW
        settings.WINDOW["class"] = "moderngl_window.context.glfw.Window"
        settings.WINDOW["gl_version"] = (4, 1)
        settings.WINDOW["size"] = (width, height)
        settings.WINDOW["aspect_ratio"] = width / height
        self.window = moderngl_window.create_window_from_settings()

        # Resources
        self.resource_dir = Path(__file__).parent.resolve() / "resources"
        # Shaders
        resources.register_program_dir((self.resource_dir / "shaders").resolve())
        self.program = programs.load(
            ProgramDescription(
                vertex_shader="tex_arr_vs.glsl", fragment_shader="tex_arr_fs.glsl"
            )
        )
        # Textures
        # Note: In case you have a single vertically stacked image, you can use TextureDescription to load it
        # Ref: https://moderngl-window.readthedocs.io/en/latest/reference/meta/texture.html
        image_names = self.resource_dir / "textures"
        image_names = list(image_names.glob("**/*.png"))
        self.tex_arr = self.create_tex_arr(image_names, 512, 512)
        self.tex_arr_idx = 0

        # Camera
        self.camera = Camera(aspect_ratio=width / height, near=0.01, far=100.0)
        self.camera.set_position(0, 0.0, 1.5)
        self.camera.look_at(pos=(0, 0, 0))

        # Set key allback
        ## Ref: https://github.com/moderngl/moderngl-window/blob/master/examples/custom_config_class.py
        self.window.key_event_func = self.key_event

        # Quad mesh with a 2D texture array
        self.quad = Quad(self.window.ctx, self.program)

    def key_event(self, key, action, modifiers):
        keys = self.window.keys
        # Key pressed
        if action == keys.ACTION_PRESS:
            if key == keys.UP:
                self.tex_arr_idx = min(self.tex_arr_idx + 1, self.tex_arr.size[2] - 1)
                print("UP: ", self.tex_arr_idx)
            if key == keys.DOWN:
                self.tex_arr_idx = max(self.tex_arr_idx - 1, 0)
                print("Down: ", self.tex_arr_idx)

    # Ref: https://stackoverflow.com/questions/63711867/using-a-texturearray-in-moderngl
    def create_tex_arr(
        self, image_names: list[str], width: int, height: int
    ) -> mgl.texture_array:
        depth = len(image_names)

        image_data = []
        for filename in image_names:
            image = Image.open(filename)
            if width != image.size[0] or height != image.size[1]:
                raise ValueError(
                    f"Image size mismatch: {image.size[0]}x{image.size[1]}"
                )
            image = image.transpose(method=Image.FLIP_TOP_BOTTOM)
            image_data.append(image.getdata())
        image_data = np.array(image_data, np.uint8)
        tex_arr = self.window.ctx.texture_array(
            (width, height, depth), components=3, data=image_data
        )

        return tex_arr

    def run(self) -> None:
        while not self.window.is_closing:
            self.window.clear(0.5, 1.0, 0.25, 0.0)
            self.window.ctx.enable(mgl.DEPTH_TEST | mgl.CULL_FACE)
            self.quad.render(self.camera, self.tex_arr, self.tex_arr_idx)
            self.window.swap_buffers()


if __name__ == "__main__":
    app = App()
    app.run()

以下,シェーダ.

#version 330

uniform mat4 M; // model matrix
uniform mat4 V; // view matrix
uniform mat4 P; // projection matrix

in vec2 in_vert;
in vec2 in_uv;

out vec2 vs_uv;

void main()
{
    vs_uv = in_uv;
    gl_Position = P * V * M * vec4(in_vert, 0.0, 1.0);
}
#version 420

layout (binding = 0) uniform sampler2DArray tex_arr;

uniform int tex_arr_idx;    // set by a key binding (up / down)

in vec2 vs_uv;
out vec4 fs_color;

void main()
{
    vec4 color = texture(tex_arr, vec3(vs_uv, tex_arr_idx));
    fs_color = color;
}

結果

f:id:Mugichoko:20211120073338p:plain
この2枚の画像を交互に表示する.