使用opengl进行瓷砖渲染

问题描述 投票:1回答:1

让我们开始考虑以下简单代码段:

import ctypes
import textwrap
import time

import glfw
import numpy as np
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import glm

GLSL_VERSION = "#version 440\n"
CONTEXT_VERSION = (4, 1)


def vs_shader(text):
    return GLSL_VERSION + textwrap.dedent(text)


def shader(text):
    prefix = textwrap.dedent("""\
        uniform float iTime;
        uniform int iFrame;
        uniform vec3 iResolution;
        uniform sampler2D iChannel0;
        uniform vec2 iOffset;
        out vec4 frag_color;
    """)
    suffix = textwrap.dedent("""\
        void main() {
            mainImage(frag_color, gl_FragCoord.xy + iOffset);
        }
    """)

    return GLSL_VERSION + prefix + textwrap.dedent(text) + suffix


VS = vs_shader("""\
    layout(location = 0) in vec3 in_position;

    uniform mat4 mvp;

    void main()
    {
        gl_Position = mvp * vec4(in_position, 1.0f);
    }
""")

SIMPLE = [
    shader("""
        void mainImage( out vec4 fragColor, in vec2 fragCoord )
        {
            vec2 uv = fragCoord.xy / iResolution.xy;
            float tile_size = 4;
            vec2 g = floor(vec2(tile_size, tile_size) * uv);
            float c = mod(g.x + g.y, 2.0);
            if (uv.x<0.5 && uv.y<0.5)
                fragColor = vec4(mix(vec3(c), vec3(1), vec3(1,0,1)), 1.0);
            else if (uv.x>=0.5 && uv.y<0.5)
                fragColor = vec4(mix(vec3(c), vec3(1), vec3(1,0,0)), 1.0);
            else if (uv.x<0.5 && uv.y>=0.5)
                fragColor = vec4(mix(vec3(c), vec3(1), vec3(0,1,0)), 1.0);
            else if (uv.x>=0.5 && uv.y>=0.5)
                fragColor = vec4(mix(vec3(c), vec3(1), vec3(0,0,1)), 1.0);
        }
    """),
    shader("""
        void mainImage( out vec4 fragColor, in vec2 fragCoord )
        {
            vec2 uv = fragCoord/iResolution.xy;
            fragColor = vec4(texture(iChannel0, uv).rgb,1.0);
        }
    """)
]


# -------- MINIFRAMEWORK --------
class Tiler:

    def __init__(self, scene_width, scene_height):
        self.scene_width = scene_width
        self.scene_height = scene_height

    @classmethod
    def from_num_tiles(cls, scene_width, scene_height, num_tiles_x, num_tiles_y):
        obj = cls(scene_width, scene_height)
        obj.num_tiles_x = num_tiles_x
        obj.num_tiles_y = num_tiles_y
        obj.tile_width = obj.scene_width // num_tiles_x
        obj.tile_height = obj.scene_height // num_tiles_y
        return obj

    @classmethod
    def from_size(cls, scene_width, scene_height, tile_width, tile_height):
        obj = cls(scene_width, scene_height)
        obj.num_tiles_x = obj.scene_width // tile_width
        obj.num_tiles_y = obj.scene_height // tile_height
        obj.tile_width = tile_width
        obj.tile_height = tile_height
        return obj

    @property
    def num_tiles(self):
        return self.num_tiles_y * self.num_tiles_x


class TextureF32():

    def __init__(self, width, height):
        target = GL_TEXTURE_2D
        self.target = target
        self.identifier = glGenTextures(1)

        glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
        glBindTexture(target, self.identifier)
        glTexImage2D(target, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, None)
        glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        self.set_filter()

        glBindTexture(target, 0)

    def set_filter(self):
        glTexParameteri(self.target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(self.target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)

    def bind(self):
        glBindTexture(self.target, self.identifier)

    def unbind(self):
        glBindTexture(self.target, 0)


class FboF32():

    def __init__(self, width, height):
        self.target = GL_FRAMEBUFFER
        self.identifier = glGenFramebuffers(1)
        glBindFramebuffer(GL_FRAMEBUFFER, self.identifier)

        # Color attachments
        tex = TextureF32(width, height)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex.identifier, 0)
        glDrawBuffers(1, [GL_COLOR_ATTACHMENT0])
        self.colors = [tex]

        self.width = width
        self.height = height

        if glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE:
            raise Exception(
                f"ERROR::FRAMEBUFFER:: Framebuffer {self.identifier} is not complete!"
            )

        glBindFramebuffer(GL_FRAMEBUFFER, 0)

    def delete(self):
        self.glDeleteFramebuffers(self.identifier)

    def rect(self):
        return [0, 0, self.width, self.height]

    def bind(self):
        glBindFramebuffer(GL_FRAMEBUFFER, self.identifier)


def set_uniform1f(prog, name, v0):
    glUniform1f(glGetUniformLocation(prog, name), v0)


def set_uniform1i(prog, name, v0):
    glUniform1i(glGetUniformLocation(prog, name), v0)


def set_uniform2i(prog, name, v0, v1):
    glUniform2i(glGetUniformLocation(prog, name), v0, v1)


def set_uniform2f(prog, name, v0, v1):
    glUniform2f(glGetUniformLocation(prog, name), v0, v1)


def set_uniform3f(prog, name, v0, v1, v2):
    glUniform3f(glGetUniformLocation(prog, name), v0, v1, v2)


def set_uniform_mat4(prog, name, mat):
    glUniformMatrix4fv(glGetUniformLocation(prog, name), 1, GL_FALSE, glm.value_ptr(mat))


def set_uniform_texture(prog, name, resource, unit_texture):
    glActiveTexture(GL_TEXTURE0 + unit_texture)
    resource.bind()
    resource.set_filter()
    glUniform1i(glGetUniformLocation(prog, name), 0 + unit_texture)


def create_quad(x0, y0, x1, y1):
    data = np.array([
        x0, y0, 0,
        x1, y0, 0,
        x0, y1, 0,

        x1, y0, 0,
        x1, y1, 0,
        x0, y1, 0,
    ], dtype=np.float32)

    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, data, GL_STATIC_DRAW)

    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    glVertexAttribPointer(0, 3, GL_FLOAT, False, 0, ctypes.c_void_p(0))
    glEnableVertexAttribArray(0)

    return vao


def compile(shader_type, source):
    identifier = glCreateShader(shader_type)
    glShaderSource(identifier, source)
    glCompileShader(identifier)

    if not glGetShaderiv(identifier, GL_COMPILE_STATUS):
        for i, l in enumerate(source.splitlines()):
            print(f"{i+1}: {l}")
        raise Exception(glGetShaderInfoLog(identifier).decode("utf-8"))

    return identifier


def create_program(vs, fs):
    vs_identifier = compile(GL_VERTEX_SHADER, vs)
    fs_identifier = compile(GL_FRAGMENT_SHADER, fs)

    program = glCreateProgram()
    glAttachShader(program, vs_identifier)
    glAttachShader(program, fs_identifier)
    glLinkProgram(program)
    if not glGetProgramiv(program, GL_LINK_STATUS):
        raise RuntimeError(glGetProgramInfoLog(program))

    return program


# -------- Glut/Glfw --------
class Effect:

    def __init__(self, w, h, num_tiles_x, num_tiles_y, passes):
        self.fbos = []
        self.needs_updating = True
        self.allocations = 0
        self.tiler = Tiler.from_num_tiles(w, h, num_tiles_x, num_tiles_y)

        self.passes = [create_program(VS, rp) for rp in passes]
        self.iframe = 0
        self.start_time = time.time()

        self.quad = create_quad(-1, -1, 1, 1)
        self.view = glm.lookAt(
            glm.vec3(0, 0, 10),
            glm.vec3(0, 0, 0),
            glm.vec3(0, 1, 0)
        )
        self.model = glm.mat4(1)
        glEnable(GL_DEPTH_TEST)

        # print("GL_MAX_VIEWPORT_DIMS:", glGetIntegerv(GL_MAX_VIEWPORT_DIMS))
        # print("GL_MAX_TEXTURE_SIZE:", glGetIntegerv(GL_MAX_TEXTURE_SIZE))
        # print("GL_MAX_RENDERBUFFER_SIZE:", glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE))

    def mem_info(self):
        GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX = 0x9048
        GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX = 0x9049
        total_mem_kb = glGetIntegerv(GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX)
        cur_avail_mem_kb = glGetIntegerv(GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX)
        return f"total_mem_kb={total_mem_kb} cur_avail_mem_kb={cur_avail_mem_kb}"

    def create_fbo(self, tiler):
        return [
            FboF32(width=tiler.tile_width, height=tiler.tile_height)
            for i in range(tiler.num_tiles)
        ]

    def make_ortho(self, x, y, num_tiles_x, num_tiles_y, left, right, bottom, top, near, far):
        # References
        #
        # https://www.opengl.org/archives/resources/code/samples/advanced/advanced97/notes/node20.html
        # https://stackoverflow.com/questions/6490728/capture-snapshot-of-opengl-window-with-very-large-image-resolution
        #
        offset_x = (right - left) / num_tiles_x
        offset_y = (top - bottom) / num_tiles_y
        l = left + offset_x * x
        r = left + offset_x * (x + 1)
        b = bottom + offset_y * y
        t = bottom + offset_y * (y + 1)
        n = near
        f = far
        print(f"x={x} y={y} left={l} right={r} bottom={b} top={t}")
        return glm.ortho(l, r, b, t, n, f)

    def render_pass(self, rp, mvp, w, h, channel0, offset_x=0, offset_y=0):
        t = time.time() - self.start_time

        glBindVertexArray(self.quad)
        glUseProgram(rp)
        set_uniform_mat4(rp, "mvp", mvp)
        set_uniform1f(rp, "iTime", t)
        set_uniform1i(rp, "iFrame", self.iframe)
        set_uniform3f(rp, "iResolution", w, h, w / h)
        set_uniform2f(rp, "iOffset", offset_x, offset_y)
        if channel0:
            set_uniform_texture(rp, "iChannel0", channel0, self.active_texture)
            self.active_texture += 1
        glDrawArrays(GL_TRIANGLES, 0, 6)

    # No tile rendering
    def render_no_tiles(self, window_width, window_height):
        self.active_texture = 0

        if self.needs_updating:
            if not self.fbos:
                print(f"Creating fbos, allocations={self.allocations} {self.mem_info()}")
                self.fbos = [
                    FboF32(width=window_width, height=window_height),
                    FboF32(width=window_width, height=window_height)
                ]

        # clear buffers
        if self.iframe == 0:
            for fbo in self.fbos:
                fbo.bind()
                glViewport(*fbo.rect())
                glClearColor(0, 0, 0, 0)
                glClear(GL_COLOR_BUFFER_BIT)

        proj = glm.ortho(-1, 1, -1, 1, -100, 100)
        mvp = proj * self.view * self.model

        # Pass0: BufferA - Channels [BufferA, None, None, None]
        fbo0 = self.fbos[0]
        fbo1 = self.fbos[1]
        w, h = fbo0.width, fbo0.height
        rp = self.passes[0]
        fbo0.bind()
        glViewport(0, 0, w, h)
        self.render_pass(rp, mvp, w, h, fbo1.colors[0])

        # Pass1: Image - Channels [BufferA, None, None, None]
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        fbo0 = self.fbos[0]
        w, h = window_width, window_height
        rp = self.passes[1]
        glViewport(0, 0, w, h)
        self.render_pass(rp, mvp, w, h, fbo0.colors[0])

        # ping-pong
        self.fbos.reverse()

        self.iframe += 1

    # Tile rendering
    def render_tiles(self, window_width, window_height):
        M = self.tiler.num_tiles_x
        N = self.tiler.num_tiles_y
        offset_x = window_width // M
        offset_y = window_height // N
        proj = glm.ortho(-1, 1, -1, 1, -100, 100)

        # -------- Test --------
        # glBindFramebuffer(GL_FRAMEBUFFER, 0)
        # glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        # self.active_texture = 0

        # for y in range(N):
        #     for x in range(M):
        #         w, h = window_width, window_height
        #         mvp = proj * self.view * self.model
        #         glViewport(offset_x * x, offset_y * y, self.tiler.tile_width, self.tiler.tile_height)
        #         self.render_pass(self.passes[0], mvp, w, h, None, offset_x * x, offset_y * y)

        # return

        # -------- Test2 --------
        self.active_texture = 0

        if self.needs_updating:
            if not self.fbos:
                print(f"Creating fbos, allocations={self.allocations} {self.mem_info()}")
                self.fbos = [
                    self.create_fbo(self.tiler),
                    self.create_fbo(self.tiler),
                ]

        # clear buffers
        if self.iframe == 0:
            for fbo_tiles in self.fbos:
                for fbo in fbo_tiles:
                    fbo.bind()
                    glViewport(*fbo.rect())
                    glClearColor(0, 0, 0, 0)
                    glClear(GL_COLOR_BUFFER_BIT)

        # Pass0: BufferA - Channels [BufferA, None, None, None]
        for y in range(N):
            for x in range(M):
                fbo0 = self.fbos[0][y * M + x]
                fbo1 = self.fbos[1][y * M + x]
                w, h, aspect = fbo0.width, fbo0.height, fbo0.width / fbo0.height
                mvp = proj * self.view * self.model
                rp = self.passes[0]
                fbo0.bind()
                glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
                self.render_pass(rp, mvp, w, h, fbo1.colors[0], offset_x * x, offset_y * y)

        # Pass1: Image - Channels [BufferA, None, None, None]
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        for y in range(N):
            for x in range(M):
                fbo0 = self.fbos[0][y * M + x]
                fbo1 = self.fbos[1][y * M + x]
                w, h, aspect = window_width, window_height, window_width / window_height
                mvp = proj * self.view * self.model
                rp = self.passes[1]
                glViewport(offset_x * x, offset_y * y, self.tiler.tile_width, self.tiler.tile_height)
                self.render_pass(rp, mvp, w, h, fbo0.colors[0], 0, 0)

        # ping-pong
        self.fbos.reverse()

        self.iframe += 1


class WindowGlut:

    def __init__(self, w, h, use_tiles, num_tiles_x, num_tiles_y, passes):
        glutInit()
        glutInitContextVersion(*CONTEXT_VERSION)
        glutInitContextProfile(GLUT_CORE_PROFILE)
        glutInitContextFlags(GLUT_FORWARD_COMPATIBLE)
        glutSetOption(GLUT_MULTISAMPLE, 16)
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE)
        glutInitWindowSize(w, h)
        glutCreateWindow('Mcve')
        glutReshapeFunc(self.reshape)
        glutKeyboardFunc(self.keyboard_func)
        glutKeyboardUpFunc(self.keyboard_up_func)
        glutDisplayFunc(self.display)
        glutIdleFunc(self.idle_func)
        self.keys = {chr(i): False for i in range(256)}
        self.effect = Effect(w, h, num_tiles_x, num_tiles_y, passes)

        self.start_time = time.time()
        self.num_frames = 0

        if use_tiles:
            print("TILE RENDERING ENABLED")
            self.render = self.effect.render_tiles
        else:
            print("TILE RENDERING DISABLED")
            self.render = self.effect.render_no_tiles

    def keyboard_func(self, *args):
        self.keys[args[0].decode("utf8")] = True

    def keyboard_up_func(self, *args):
        self.keys[args[0].decode("utf8")] = False

    def display(self):
        if self.keys['r']:
            self.effect.iframe = 0

        self.render(self.window_width, self.window_height)

        glutSwapBuffers()
        self.num_frames += 1

        t = time.time() - self.start_time
        if t >= 1:
            glutSetWindowTitle(f"Fps: {self.num_frames}")
            self.start_time = time.time()
            self.num_frames = 0

    def run(self):
        glutMainLoop()

    def idle_func(self):
        glutPostRedisplay()

    def reshape(self, w, h):
        glViewport(0, 0, w, h)
        self.window_width = w
        self.window_height = h


class WindowGlfw:

    def __init__(self, w, h, use_tiles, num_tiles_x, num_tiles_y, passes):
        # Initialize the library
        if not glfw.init():
            return

        # Create a windowed mode window and its OpenGL context
        glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, CONTEXT_VERSION[0])
        glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, CONTEXT_VERSION[1])
        glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, GL_TRUE)
        glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
        window = glfw.create_window(w, h, "Mcve", None, None)
        if not window:
            glfw.terminate()
            return

        glfw.set_window_size_callback(window, self.reshape)
        glfw.set_key_callback(window, self.keyboard_func)

        # Make the window's context current
        glfw.make_context_current(window)
        self.window = window

        self.keys = {chr(i): False for i in range(256)}
        self.effect = Effect(w, h, num_tiles_x, num_tiles_y, passes)
        self.window_width = w
        self.window_height = h

        if use_tiles:
            print("TILE RENDERING ENABLED")
            self.render = self.effect.render_tiles
        else:
            print("TILE RENDERING DISABLED")
            self.render = self.effect.render_no_tiles

    def keyboard_func(self, window, key, scancode, action, mods):
        self.keys[chr(key)] = action

    def display(self):
        if self.keys['R']:
            self.iframe = 0

        self.render(self.window_width, self.window_height)

    def run(self):
        window = self.window

        while not glfw.window_should_close(window):
            self.display()
            glfw.swap_buffers(window)
            glfw.poll_events()

        glfw.terminate()

    def reshape(self, window, w, h):
        glViewport(0, 0, w, h)
        self.window_width = w
        self.window_height = h


if __name__ == '__main__':
    params = {
        "w": 320,
        "h": 240,
        "use_tiles": True,
        "num_tiles_x": 2,
        "num_tiles_y": 2,
        "passes": SIMPLE
    }
    use_glut = True
    WindowGlut(**params).run() if use_glut else WindowGlfw(**params).run()

要运行此代码,您需要安装numpypyopenglglfw。您可以通过切换变量use_glut在glfw或glut之间切换。我添加了此选项,因为在某些情况下在macosx上运行过剩似乎很棘手。

无论如何,该线程的目标是弄清楚如何修复越野车片段以进行适当的图块渲染,如您所见,现在已经实施了非常幼稚的尝试。

main块中,您可以指定是否要使用图块使用渲染方法(use_tiles变量),如果选择使用图块,则需要指定它们的数量(num_tiles_xnum_tiles_y)。

案例:

  1. 如果使用"use_tiles": False运行它,您将看到以下输出:

    enter image description here

    输出正确

  2. 如果使用"use_tiles": True, "num_tiles_x": 2, "num_tiles_y": 2运行它,则应该看到与a)相同的输出。也正确

  3. 但是如果您使用"use_tiles": True, "num_tiles_x": 4, "num_tiles_y": 4或更高版本运行它,您将开始看到如下所示完全扭曲的图像:

    enter image description here

问题:我的图块渲染代码产生错误输出的错误是什么?您将如何解决?

也...即使代码是固定的,我尝试制作图块渲染的方式也很幼稚,并且在处理更复杂的效果(其中通行证需要从相邻图块甚至是回读的效果)时,它也无法很好地工作最差的,非相邻的图块。对于相邻的瓦片,有人告诉我在瓦片上添加一些填充效果会很好,但对于更一般的情况,我不知道您如何解决该问题。无论如何,一次只能执行一个步骤,此线程的目标是修复越野车代码段

python opengl rendering tile pyopengl
1个回答
0
投票
在您的着色器代码中,tile_size是一个常数(float tile_size = 4)。对瓦片数使用统一变量:

uniform vec2 tile_size;

vec2 g = floor(tile_size.xy * uv);
通过瓷砖的两倍大小设置制服:

set_uniform2f(rp, "tile_size", self.tiler.num_tiles_x*2, self.tiler.num_tiles_y*2)

© www.soinside.com 2019 - 2024. All rights reserved.