Давайте начнем с рассмотрения этого простого фрагмента:
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
# /8008426/sdelaite-snimok-okna-opengl-s-ochen-bolshim-razresheniem-izobrazheniya
#
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()
Для запуска этого кода вам необходимо установить numpy
, pyopengl
, glfw
, PyGLM
. Вы можете переключаться между glfw или glut, переключая переменную use_glut
. Я добавил эту опцию, так как кажется, что в некоторых случаях запуск glut на macosx может быть сложным.
В любом случае, цель этого потока состоит в том, чтобы выяснить, как исправить ошибочный фрагмент кода для правильного рендеринга плитки, так какВы можете видеть, что сейчас реализована очень наивная попытка.
В блоке main
вы можете указать, хотите ли вы использовать метод рендеринга с использованием плиток или нет (переменная use_tiles
), если вы выберетеиспользуя плитки, вам нужно указать их количество (num_tiles_x
, num_tiles_y
).
Случаи:
Если вызапустите его с "use_tiles": False
, и вы увидите следующее:
, что вывод правильный
Если вы запустите его с "use_tiles": True, "num_tiles_x": 2, "num_tiles_y": 2
, вы должны увидеть тот же результат, что и 1). Также исправьте
Но если вы запустите его с "use_tiles": True, "num_tiles_x": 4, "num_tiles_y": 4
или выше, вы увидите полностью испорченное изображение, как показано ниже:
ВОПРОС: В чем ошибка моего кода рендеринга плитки, который выдает неправильный вывод? Как бы вы это исправили?
Также ... Даже если код исправлен, способ, которым я пытаюсь сделать рендеринг плиток, довольно наивен, и он не будет работать очень хорошо при работе с более сложными эффектами, гдепроходы должны считываться с соседних плиток или даже с худшими, не смежными плитками. Для случая смежных плиток мне сказали, что добавление некоторого отступа к плиткам будет работать очень хорошо, но для более общего случая я понятия не имею, как вы решили эту проблему. В любом случае, один шаг за раз, целью этой темы будет исправление глючного фрагмента