使用 opengl 进行瓷砖渲染
Tile rendering with opengl
让我们从这个简单的片段开始:
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
#
#
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
。您可以通过切换变量 use_glut
在 glfw 或 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
或更高版本,您将开始看到一个完全搞砸的图像,如下所示:
问题:我的图块渲染代码产生错误输出的错误是什么?你会如何解决它?
此外...即使代码已修复,我尝试进行平铺渲染的方式也很幼稚,并且在处理需要从相邻通道回读的更复杂效果时,它不会很好地工作瓷砖,甚至最糟糕的是,不相邻的瓷砖。对于相邻瓷砖的情况,有人告诉我在瓷砖上添加一些填充效果会很好,但对于更一般的情况,我不知道你是如何解决这个问题的。无论如何,一步一个脚印,这个帖子的目标是修复错误片段
在第一遍中,单个图块被渲染到帧缓冲区,其大小与图块完全相同。 gl_FragCoord.xy
是 (0,0) 在磁贴的左下角。 uv
= (0,0) 必须位于 window 的左下角,而 uv
= (1, 1) 必须位于 window 的右上角。要计算 uv
相对于 window 的坐标,您必须将图块的偏移量添加到 gl_FragCoord.xy
并除以 window 的大小:
公式(伪代码):
uv = (gl_FragCoord.xy + (offset_x*x, offset_y*y)) / (window_width, window_height)
+------------------+
| |
| +----+ |
| | | |
| +----+ |
| (0,0) tile = gl_FragCoord.xy
| |
+------------------+
(0,0) window
在第一遍中,iResolution
必须是 (window_width
, window_height
) 而 iOffset
必须是 (offset_x * x
, offset_y * y
).
# 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]
mvp = proj * self.view * self.model
rp = self.passes[0]
fbo0.bind()
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
w, h = window_width, window_height
aspect = window_width / window_height
self.render_pass(rp, mvp, w, h, fbo1.colors[0], offset_x * x, offset_y * y)
在第二遍中,从纹理读取单个图块并渲染到 window(默认帧缓冲区 0)。源纹理(瓦片)具有瓦片的大小,并且必须根据瓦片纹理计算 uv
坐标。 gl_FragCoord.xy
是 window 左下角的 (0,0)。 uv
= (0,0) 必须位于图块的左下方,uv
= (1, 1) 必须位于图块的右上方。要计算 uv
坐标,必须从 gl_FragCoord.xy
中减去图块的偏移量,结果必须除以标题的大小:
公式(伪代码)
uv = (gl_FragCoord.xy - (offset_x*x, offset_y*y)) / (tile_width, tile_height)
+------------------+
| |
| +----+ |
| | | |
| +----+ |
| (0,0) tile |
| |
+------------------+
(0,0) window = gl_FragCoord.xy
在第 2 遍中,iResolution
必须是 (self.tiler.tile_width
, self.tiler.tile_height
) 而 iOffset
必须是 (-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]
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)
w, h = self.tiler.tile_width, self.tiler.tile_height
aspect = self.tiler.tile_width / self.tiler.tile_height
self.render_pass(rp, mvp, w, h, fbo0.colors[0], -offset_x * x, -offset_y * y)
编辑mcve.py
在这种情况下,渲染目标始终是具有图块大小的帧缓冲区。第二次渲染过程 ("Pass1") 从图块读取数据并存储到目标图块,因此第二次渲染过程必须是:
# Pass1: Image - Channels [BufferA, None, None, None]
for y in range(N):
for x in range(M):
fbo_dst = self.fbo_target[0][y * M + x]
fbo_src = self.fbos[0][y * M + x]
mvp = proj * self.view * self.model
rp = self.passes[1]
fbo_dst.bind()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
w, h = self.tiler.tile_width, self.tiler.tile_height
aspect = self.tiler.tile_width / self.tiler.tile_height
self.render_pass(rp, mvp, w, h, fbo_src.colors[0], 0, 0)
另一个问题是从片段着色器中的前一帧读取纹理。纹理的大小始终是瓷砖的大小。纹理的左下角坐标为 (0, 0),右上角坐标为 (1, 1)。
所以对于纹理坐标的计算(st
),必须跳过偏移量并且分辨率由纹理的大小给出(textureSize
):
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
initSpheres();
# issue is here
// vec2 st = fragCoord.xy / iResolution.xy; <--- delete
vec2 st = gl_FragCoord.xy / vec2(textureSize(iChannel0, 0));
// [...]
// Moving average (multipass code)
vec3 color = texture(iChannel0, st).rgb * float(iFrame);
// [...]
}
查看结果:
如果您不想更改 mainImage
中的着色器代码,则另一种方法是欺骗系统并通过宏将纹理查找委托给不同的函数。例如:
def shader(tileTextureLookup, text):
prefix = textwrap.dedent("""\
uniform float iTime;
uniform int iFrame;
uniform vec3 iResolution;
uniform sampler2D iChannel0;
uniform vec2 iOffset;
out vec4 frag_color;
""")
textureLookup = ""
if tileTextureLookup:
textureLookup = textwrap.dedent("""\
vec4 textureTile(sampler2D sampler, vec2 uv) {
vec2 st = (uv * iResolution.xy - iOffset.xy) / vec2(textureSize(sampler, 0));
return texture(sampler, st);
}
#define texture textureTile
""")
suffix = textwrap.dedent("""\
void main() {
mainImage(frag_color, gl_FragCoord.xy + iOffset);
}
""")
return GLSL_VERSION + prefix + textureLookup + textwrap.dedent(text) + suffix
SMALLPT_MULTIPASS = [
shader(True, """\
// All code here is by Zavie (https://www.shadertoy.com/view/4sfGDB#)
// [...]
"""),
shader(False, """\
// A simple port of Zavie's GLSL smallpt that uses multipass.
// Original source: https://www.shadertoy.com/view/4sfGDB#
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = fragCoord.xy / iResolution.xy;
vec3 color = texture(iChannel0, uv).rgb;
fragColor = vec4(pow(clamp(color, 0., 1.), vec3(1./2.2)), 1.);
}
""")
]
但请注意,texture
is an overloaded function and this approach works for 2 dimensional textures only. Furthermore there other look up functions like texelFetch
。
让我们从这个简单的片段开始:
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
#
#
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
。您可以通过切换变量 use_glut
在 glfw 或 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
或更高版本,您将开始看到一个完全搞砸的图像,如下所示:
问题:我的图块渲染代码产生错误输出的错误是什么?你会如何解决它?
此外...即使代码已修复,我尝试进行平铺渲染的方式也很幼稚,并且在处理需要从相邻通道回读的更复杂效果时,它不会很好地工作瓷砖,甚至最糟糕的是,不相邻的瓷砖。对于相邻瓷砖的情况,有人告诉我在瓷砖上添加一些填充效果会很好,但对于更一般的情况,我不知道你是如何解决这个问题的。无论如何,一步一个脚印,这个帖子的目标是修复错误片段
在第一遍中,单个图块被渲染到帧缓冲区,其大小与图块完全相同。 gl_FragCoord.xy
是 (0,0) 在磁贴的左下角。 uv
= (0,0) 必须位于 window 的左下角,而 uv
= (1, 1) 必须位于 window 的右上角。要计算 uv
相对于 window 的坐标,您必须将图块的偏移量添加到 gl_FragCoord.xy
并除以 window 的大小:
公式(伪代码):
uv = (gl_FragCoord.xy + (offset_x*x, offset_y*y)) / (window_width, window_height)
+------------------+
| |
| +----+ |
| | | |
| +----+ |
| (0,0) tile = gl_FragCoord.xy
| |
+------------------+
(0,0) window
在第一遍中,iResolution
必须是 (window_width
, window_height
) 而 iOffset
必须是 (offset_x * x
, offset_y * y
).
# 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]
mvp = proj * self.view * self.model
rp = self.passes[0]
fbo0.bind()
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
w, h = window_width, window_height
aspect = window_width / window_height
self.render_pass(rp, mvp, w, h, fbo1.colors[0], offset_x * x, offset_y * y)
在第二遍中,从纹理读取单个图块并渲染到 window(默认帧缓冲区 0)。源纹理(瓦片)具有瓦片的大小,并且必须根据瓦片纹理计算 uv
坐标。 gl_FragCoord.xy
是 window 左下角的 (0,0)。 uv
= (0,0) 必须位于图块的左下方,uv
= (1, 1) 必须位于图块的右上方。要计算 uv
坐标,必须从 gl_FragCoord.xy
中减去图块的偏移量,结果必须除以标题的大小:
公式(伪代码)
uv = (gl_FragCoord.xy - (offset_x*x, offset_y*y)) / (tile_width, tile_height)
+------------------+
| |
| +----+ |
| | | |
| +----+ |
| (0,0) tile |
| |
+------------------+
(0,0) window = gl_FragCoord.xy
在第 2 遍中,iResolution
必须是 (self.tiler.tile_width
, self.tiler.tile_height
) 而 iOffset
必须是 (-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]
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)
w, h = self.tiler.tile_width, self.tiler.tile_height
aspect = self.tiler.tile_width / self.tiler.tile_height
self.render_pass(rp, mvp, w, h, fbo0.colors[0], -offset_x * x, -offset_y * y)
编辑mcve.py
在这种情况下,渲染目标始终是具有图块大小的帧缓冲区。第二次渲染过程 ("Pass1") 从图块读取数据并存储到目标图块,因此第二次渲染过程必须是:
# Pass1: Image - Channels [BufferA, None, None, None]
for y in range(N):
for x in range(M):
fbo_dst = self.fbo_target[0][y * M + x]
fbo_src = self.fbos[0][y * M + x]
mvp = proj * self.view * self.model
rp = self.passes[1]
fbo_dst.bind()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glViewport(0, 0, self.tiler.tile_width, self.tiler.tile_height)
w, h = self.tiler.tile_width, self.tiler.tile_height
aspect = self.tiler.tile_width / self.tiler.tile_height
self.render_pass(rp, mvp, w, h, fbo_src.colors[0], 0, 0)
另一个问题是从片段着色器中的前一帧读取纹理。纹理的大小始终是瓷砖的大小。纹理的左下角坐标为 (0, 0),右上角坐标为 (1, 1)。
所以对于纹理坐标的计算(st
),必须跳过偏移量并且分辨率由纹理的大小给出(textureSize
):
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
initSpheres();
# issue is here
// vec2 st = fragCoord.xy / iResolution.xy; <--- delete
vec2 st = gl_FragCoord.xy / vec2(textureSize(iChannel0, 0));
// [...]
// Moving average (multipass code)
vec3 color = texture(iChannel0, st).rgb * float(iFrame);
// [...]
}
查看结果:
如果您不想更改 mainImage
中的着色器代码,则另一种方法是欺骗系统并通过宏将纹理查找委托给不同的函数。例如:
def shader(tileTextureLookup, text):
prefix = textwrap.dedent("""\
uniform float iTime;
uniform int iFrame;
uniform vec3 iResolution;
uniform sampler2D iChannel0;
uniform vec2 iOffset;
out vec4 frag_color;
""")
textureLookup = ""
if tileTextureLookup:
textureLookup = textwrap.dedent("""\
vec4 textureTile(sampler2D sampler, vec2 uv) {
vec2 st = (uv * iResolution.xy - iOffset.xy) / vec2(textureSize(sampler, 0));
return texture(sampler, st);
}
#define texture textureTile
""")
suffix = textwrap.dedent("""\
void main() {
mainImage(frag_color, gl_FragCoord.xy + iOffset);
}
""")
return GLSL_VERSION + prefix + textureLookup + textwrap.dedent(text) + suffix
SMALLPT_MULTIPASS = [
shader(True, """\
// All code here is by Zavie (https://www.shadertoy.com/view/4sfGDB#)
// [...]
"""),
shader(False, """\
// A simple port of Zavie's GLSL smallpt that uses multipass.
// Original source: https://www.shadertoy.com/view/4sfGDB#
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 uv = fragCoord.xy / iResolution.xy;
vec3 color = texture(iChannel0, uv).rgb;
fragColor = vec4(pow(clamp(color, 0., 1.), vec3(1./2.2)), 1.);
}
""")
]
但请注意,texture
is an overloaded function and this approach works for 2 dimensional textures only. Furthermore there other look up functions like texelFetch
。