Autor projekta:
fnovacki@foi.hrfilip@novacki.orggithub.com/filipnovackiBiblioteke:
U ovoj projektnoj dokumentaciji neće biti opisano toliko specifičnost OpenGL-a, već će naglasak biti stavljen na Python i na to kako su pojedini problemi riješeni.
Skripte korištene u projektu: https://github.com/filipnovacki/fictional-lamp
Instalacija svih paketa trebala bi funkcionirati pokretanjem naredbi u terminalu:
$ python -m venv env
$ source env/bin/activate
(env) $ pip install -r requirements.txt
Kod rada sa shaderima bilo je problema vezano za verzije OpenGL-a tako da nije nemoguće da i tamo može zapesti.
Verzije OpenGL-a:
$ glxinfo | grep "OpenGL"
OpenGL ES profile version string: OpenGL ES 3.0 Mesa 20.3.2
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.00
GLFW je biblioteka za stvaranje prozora. U Pythonu radi jednako kao i u sklopu OpenGL-a, jedina je razlika u tome što je nazivlje malo promijenjeno tako da je prilagođeno Pythonu. Tako se koriste rijeci_s_donjom_crticom umjesto camelCase sintakse.
screen_width = 1366
screen_heigh = 768
window = glfw.create_window(screen_width, screen_height, "PyOpenGL", None, None)
# (...)
if not window:
glfw.terminate()
glfw.make_context_current(window)
# (...)
# shaders, binding...
# (...)
while not glfw.window_should_close(window):
glfw.poll_events()
# render, draw...
glfw.swap_buffers(window)
Shaderi rade na vrlo sličan način kao i u drugim programskim jezicima za OpenGL. U Pythonu je možda malo kraći put za kompajliranje jer se shaderi u program mogu učinkovito pretvoriti u samo jednoj liniji koda.
FRAGMENT_SHADER = """
// ...
"""
VERTEX_SHADER = """
// ...
"""
shader = OpenGL.GL.shaders.compileProgram(
OpenGL.GL.shaders.compileShader(VERTEX_SHADER, GL_VERTEX_SHADER),
OpenGL.GL.shaders.compileShader(FRAGMENT_SHADER, GL_FRAGMENT_SHADER)
)
# binding
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(
GL_ARRAY_BUFFER,
circle.itemsize * len(circle), # numpy array
circle, # numpy array
GL_STATIC_DRAW
)
position = glGetAttribLocation(shader, 'position')
glVertexAttribPointer(position, 2, GL_FLOAT, GL_FALSE, 0, None)
glEnableVertexAttribArray(position)
glUseProgram(shader)
glClearColor(1.0, 1.0, 1.0, 0.0)
Kod crtanja potrebno je definirati točke u kojima će se nalaziti vrhovi te između kojih će se povući bridovi. Jedan od načina da se to napravi je ručno popisivati, odnosno
triangle = [
0, 0.5,
-0.5, -0.5,
0.5, -0.5
]
Jasno, kad koristimo računala, takav pristup je spor i neučinkovit. Iz tog je razloga
stvoreno nekoliko funkcija, odnosno modul objects. On služi za automatizirano stvaranje
objekata koji se trebaju iscrtati.
Glavni alati koji se koriste kod generiranja točaka su funkcije iz paketa
itertools koji dolazi s Pythonom.
Ovako je definirana funkcija za crtanje kvadra:
def draw_cube(side_a=0.5, side_b=0.6, side_c=0.7, colors=False):
if not colors:
return_vertices = np.array(list(chain(*product([1, -1], repeat=3))), dtype=np.float32)
else:
return_vertices = np.array([], dtype=np.float32)
for point in np.array(list((product([1, -1], repeat=3)))) * list((repeat([side_a, side_b, side_c], times=8))):
return_vertices = np.append(return_vertices, np.append(point, [np.random.random(), 0.299, 0.499]))
return return_vertices
Cijeli ovaj kod radi na taj način da funkcija product generira sve kombinacije
zadanih brojeva, odnosno Kartezijev produkt brojeva -1 i 1. Ukoliko je potreban
dvodimenzionalan objekt, daje se argument repeat=2, a ako je potreban trodimenzionalni,
daje se argument repeat=3.
Biblioteka Numpy ima izvrsno uređeno mmnoženje i zbrajanje svih vrsta objekata. Zbog
načina na koji Numpy zna množiti arraye, oblik u kojem smo definirali kvadar
pomoću -1 i 1 je odličan jer možemo jednostavno koordinate vrhova kvadra
pomnožiti dužinama njihovih stranica kako bismo dobili kvadar s različitim
veličinama stranica.
Među tim funkcijama korištena je i funkcija chain koja vraća elemente lista
raspakirane u jednu listu, odnosno spojene. Takav zapis točaka je praktičan kod
iscrtavanja.
Ostale funkcije za generiranje točaka:
def draw_circle(segments=10, r=0.5, colors=False):
segment_len = np.math.tau / segments
array = np.array([], dtype=np.float32)
if not colors:
array = np.append(array, [0.0, 0.0])
else:
array = np.append(array, [0.0, 0.0, 0.0, 0.0, 0.5])
for x in range(segments + 1):
x_coord = np.cos(x * segment_len)
y_coord = np.sin(x * segment_len)
if colors:
row = [r * x_coord, r * y_coord, x_coord, y_coord, 0.5]
else:
row = [r * x_coord, r * y_coord]
array = np.append(array, row)
return array
Numpy array je vrlo praktična struktura i zbog načina na koji se učitavaju
točke u buffer.
U ovom isječku koda iskorišten je numpy array spremljen u varijabli circle.
U tom tipu podataka spremljena su mnoga svojstva, između kojih i itemsize koji
govori koliko je velik tip podataka spremljen u array. Kad se pomnoži s brojem
elemenata strukture (len()), dobije se točno veličina objekta koja se treba
pospremiti u buffer. Primjer:
VBO = glGenBuffers(1)
glBindBuffer(GL_ARRAY_BUFFER, VBO)
glBufferData(
GL_ARRAY_BUFFER,
circle.itemsize * len(circle),
circle,
GL_STATIC_DRAW
)
pyrr¶Za transformacije u prostoru koristi se biblioteka pyrr. Ona
na jednostavan pruža operacije nad matricama, pa tako i
transformacije po osima, od kojih su nama najzanimljivije
transformacije po x i po y osima.
rot_x = pyrr.Matrix44.from_x_rotation(0.2 * glfw.get_time())
rot_y = pyrr.Matrix44.from_y_rotation(0.3 * glfw.get_time())
transformLoc = glGetUniformLocation(shader, "transform")
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, rot_x * rot_y)
Argument u metodi from_x_rotation() i from_y_rotation()
iskazan je u radijanima. glfw mjeri vrijeme od kad je
program pokrenut pa se rotacija može na jednostavan način
dobiti rotacija davanjem vremena kao argumenta. Ovdje se vrijeme
množi brojem manjim od 1 kako bi se rotacija usporila.
Kod perspektivne projekcije pyrr se koristi za stvaranje matrica transformacija:
view = pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0, 0.0, -3.0]))
projection = pyrr.matrix44.create_perspective_projection(45.0, _SCREEN_WIDTH / _SCREEN_HEIGHT, 0.1, 100.0)
model = pyrr.matrix44.create_from_translation(pyrr.Vector3([0.0, 0.0, 0.0]))
Kod povezivanja Pythona s OpenGL-om korisna je i Pythonova biblioteka ctypes. Ona
inače služi za pozivanje vanjskih funkcija (ponajviše iz C-a), a ovdje možemo
koristiti za određivanje veličine tipova. Tako kod povezivanja varijabli iz shadera
moramo unijeti "pomak" u strukturi što se može vrlo lako napraviti uz asocijaciju
nekih od ctypes i njihovim brojem. Primjer:
color = glGetAttribLocation(shader, 'color')
glVertexAttribPointer(
color, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12)
)
glEnableVertexAttribArray(color)
U ovom primjeru postoji i bug koji nastaje u fullscreen prikazu
(env) $ python simple_circle_with_colours.py



Literatura i korisne poveznice: