Mugichoko's blog

Mugichoko’s blog

プログラミングを中心としたメモ書き.

ModernGL奮闘記 (10) - Cubemap -

ModernGLでCubemap(キューブマップ)を使ってみる.

キューブマップテクスチャを用意して,それを立方体の6面にマッピングする.単なる動作確認が目的なので,できるだけ簡単に実装する方針.

f:id:Mugichoko:20211124084513p:plain

実装方針

  1. Framebuffer Object (FBO) を用意(line 47参照)
  2. キューブマップテクスチャを用意(各面はFBOと同サイズ)(line 50参照)
  3. FBOをクリアすることで特定の色で塗りつぶす(例えばlines 77 & 78参照)
  4. この結果をキューブマップの1面にコピー(例えばline 79参照)
  5. 3-4を6面分繰り返す(lines 75 - 99参照)

尚,モデルのcube.objここから拝借

コード (Python)

from pathlib import Path
import moderngl as mgl
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 OrbitCamera
import numpy as np
from pathlib import Path
import numpy as np
import moderngl as mgl
import moderngl_window as mglw
from moderngl_window.conf import settings
from moderngl_window import resources
from moderngl_window.resources import programs, scenes
from moderngl_window.meta import ProgramDescription, SceneDescription
from moderngl_window.scene.camera import OrbitCamera

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 = mglw.create_window_from_settings()
        self.window.print_context_info()

        # 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="cubemap_vs.glsl", fragment_shader="cubemap_fs.glsl",
            )
        )
        ## meshes
        resources.register_scene_dir((self.resource_dir / "models").resolve())
        self.scene = scenes.load(SceneDescription(path="cube.obj"))
        for mesh in self.scene.meshes:
            print(">> mesh.attributes:", mesh.attributes)
        print(">> len(self.scene.meshes):", len(self.scene.meshes))

        # FBO
        self.fbo = self.window.ctx.simple_framebuffer((256, 256), dtype="f4")

        # Cubemap
        self.cubemap = self.window.ctx.texture_cube((256, 256), components=4, dtype="f4")
        
        # Camera
        self.camera = OrbitCamera(
            aspect_ratio=width / height,
            near=0.01, far=100.0)
        self.camera.mouse_sensitivity = 0.75

        # Set mouse callback
        ## Ref: https://github.com/moderngl/moderngl-window/blob/master/examples/custom_config_class.py
        self.window.mouse_position_event_func = self.mouse_position_event
        self.window.mouse_scroll_event_func = self.mouse_scroll_event
        self.window.mouse_exclusivity = True

    def mouse_position_event(self, x, y, dx, dy) -> None:
        #print("Mouse position pos={} {} delta={} {}".format(x, y, dx, dy))
        self.camera.rot_state(dx, dy)

    def mouse_scroll_event(self, x_offset, y_offset) -> None:
        #print("mouse_scroll_event", x_offset, y_offset)
        self.camera.zoom_state(y_offset)

    def render_to_cubemap(self):
        self.window.ctx.enable(mgl.DEPTH_TEST | mgl.CULL_FACE | mgl.BLEND)

        # Render six different colors for each cubemap face
        # 0: right (R)
        self.fbo.use()
        self.fbo.clear(1.0, 0.0, 0.0, 1.0)
        self.cubemap.write(0, self.fbo.read(components=4, dtype="f4"))
        # 1: left (G)
        self.fbo.use()
        self.fbo.clear(0.0, 1.0, 0.0, 1.0)
        self.cubemap.write(1, self.fbo.read(components=4, dtype="f4"))
        # 2: up (B)
        self.fbo.use()
        self.fbo.clear(0.0, 0.0, 1.0, 1.0)
        self.cubemap.write(2, self.fbo.read(components=4, dtype="f4"))
        # 3: down (Y)
        self.fbo.use()
        self.fbo.clear(1.0, 1.0, 0.0, 1.0)
        self.cubemap.write(3, self.fbo.read(components=4, dtype="f4"))
        # 4: back (B)
        self.fbo.use()
        self.fbo.clear(0.0, 0.0, 0.0, 1.0)
        self.cubemap.write(4, self.fbo.read(components=4, dtype="f4"))
        # 5: front (W)
        self.fbo.use()
        self.fbo.clear(1.0, 1.0, 1.0, 1.0)
        self.cubemap.write(5, self.fbo.read(components=4, dtype="f4"))

        self.window.fbo.use()


    def run(self) -> None:
        self.render_to_cubemap()

        while not self.window.is_closing:
            self.window.ctx.enable_only(mgl.DEPTH_TEST | mgl.CULL_FACE)
            self.window.ctx.clear(0.96, 0.56, 0.63, 0.0)

            self.program["M"].write(np.identity(4, dtype="float32"))
            self.program["V"].write(self.camera.matrix)
            self.program["P"].write(self.camera.projection.matrix)
            self.cubemap.use(0)
            self.scene.root_nodes[0].mesh.vao.render(self.program)

            self.window.swap_buffers()

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

シェーダ (GLSL)

// cubemap_vs.glsl
#version 440

in vec3 in_position;

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

out vec3 vs_dir;

void main()
{
    vs_dir = normalize(in_position);
    gl_Position = P * V * M * vec4(in_position, 1.0);
}
// cubemap_fs.glsl
#version 440

layout (binding = 0) uniform samplerCube cubemap;

in vec3 vs_dir;
out vec4 fsColor;

void main()
{
    fsColor = texture(cubemap, vs_dir);
}