Use geometry shaders to (hopefully) lighten CPU workload

Also tweak color palette for better contrast
This commit is contained in:
Nguyễn Gia Phong 2019-06-24 15:08:05 +07:00
parent c7a280191d
commit 94ecb1b119
9 changed files with 122 additions and 74 deletions

View File

@ -23,21 +23,11 @@ from random import choices, shuffle
import numpy
from pkg_resources import resource_filename
TANGO = {'Butter': ('fce94f', 'edd400', 'c4a000'),
'Orange': ('fcaf3e', 'f57900', 'ce5c00'),
'Chocolate': ('e9b96e', 'c17d11', '8f5902'),
'Chameleon': ('8ae234', '73d216', '4e9a06'),
'Sky Blue': ('729fcf', '3465a4', '204a87'),
'Plum': ('ad7fa8', '75507b', '5c3566'),
'Scarlet Red': ('ef2929', 'cc0000', 'a40000'),
'Aluminium': ('eeeeec', 'd3d7cf', 'babdb6',
'888a85', '555753', '2e3436')}
COLOR_NAMES = ['Butter', 'Orange', 'Chocolate', 'Chameleon',
'Sky Blue', 'Plum', 'Scarlet Red']
NEIGHBORS = set(chain.from_iterable(
map(permutations, combinations_with_replacement((-1, 0, 1), 3))))
# map.npy is generated by ../tools/mapgen
SPACE = numpy.load(resource_filename('axuy', 'map.npy'))
COLORS = tuple(numpy.float32(color) for color in permutations((1.0, 0.5, 0.0)))
def abspath(resource_name):
@ -45,9 +35,9 @@ def abspath(resource_name):
return resource_filename('axuy', resource_name)
def color(name, idx=0):
def color(code, value):
"""Return NumPy float32 array of RGB colors from color name."""
return numpy.float32([i / 255 for i in bytes.fromhex(TANGO[name][idx])])
return COLORS[code] * (value + 1) * 0.5
def mapidgen(replacement=False):

View File

@ -16,7 +16,7 @@ vec2 fringe(vec2 vert, float delta)
void main(void)
{
vec2 vert = in_text * 2.0 - 1.0;
f_color = texture(la, in_text) * 0.42 + vec4(
f_color = texture(la, in_text) + vec4(
texture(tex, fringe(vert, -invfov)).r,
texture(tex, fringe(vert, invfov)).g,
texture(tex, in_text).b, 1.0);

32
axuy/shaders/line.geom Normal file
View File

@ -0,0 +1,32 @@
#version 330 core
layout (lines) in;
layout (line_strip, max_vertices = 54) out;
uniform float visibility;
uniform vec3 camera;
uniform mat4 vp;
out float intensity;
void translate(inout vec4 delta)
{
vec4 vert;
float dist;
for (int n = 0; n < 2; ++n) {
vert = gl_in[n].gl_Position + delta;
dist = distance(camera, vec3(vert));
intensity = 1 / (1 + dist * dist / visibility);
gl_Position = vp * vert;
EmitVertex();
}
EndPrimitive();
}
void main()
{
float i, j, k;
for (i = -12.0; i < 12.3; i += 12.0)
for (j = -12.0; j < 12.3; j += 12.0)
for (k = -9.0; k < 12.3; k += 9.0)
translate(vec4(i, j, k, 0.0));
}

View File

@ -1,11 +0,0 @@
#version 330
uniform vec3 color;
in float intensity;
out vec4 f_color;
void main()
{
f_color = vec4(color, intensity);
}

View File

@ -1,16 +1,11 @@
#version 330
uniform mat4 vp;
uniform mat4 model;
uniform vec3 camera;
uniform float visibility;
uniform vec3 pos;
uniform mat4 rot;
in vec3 in_vert;
out float intensity;
in vec4 in_vert;
void main()
{
vec4 vert = model * vec4(in_vert, 1.0);
gl_Position = vp * vert;
intensity = 1 / (1 + pow(distance(camera, vec3(vert)), 2) / visibility);
gl_Position = rot * in_vert + vec4(pos, 0.0);
}

View File

@ -7,9 +7,7 @@ out vec4 f_color;
void main(void)
{
f_color = texture(tex, in_text);
f_color = texture(tex, in_text) * 0.5;
float r = f_color.r, g = f_color.g, b = f_color.b;
float c = r + g + b;
float p = sqrt(r * (r - g) + g * (g - b) + b * (b - r)) * 2.0;
f_color *= sign(floor(p / (c + p) * 6.9));
f_color *= abs(r - g) + abs(g - b) + abs(b - r);
}

View File

@ -0,0 +1,32 @@
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 81) out;
uniform float visibility;
uniform vec3 camera;
uniform mat4 vp;
out float intensity;
void translate(inout vec4 delta)
{
vec4 vert;
float dist;
for (int n = 0; n < 3; ++n) {
vert = gl_in[n].gl_Position + delta;
dist = distance(camera, vec3(vert));
intensity = 1 / (1 + dist * dist / visibility);
gl_Position = vp * vert;
EmitVertex();
}
EndPrimitive();
}
void main()
{
float i, j, k;
for (i = -12.0; i < 12.3; i += 12.0)
for (j = -12.0; j < 12.3; j += 12.0)
for (k = -9.0; k < 12.3; k += 9.0)
translate(vec4(i, j, k, 0.0));
}

View File

@ -20,7 +20,7 @@ __doc__ = 'Axuy module for map class'
from itertools import product
from math import sqrt
from random import choice
from random import randint
import glfw
import moderngl
@ -28,14 +28,15 @@ import numpy as np
from PIL import Image
from pyrr import Matrix44
from .pico import Picobot
from .misc import COLOR_NAMES, abspath, color, neighbors, sign
from .pico import SHARD_LIFE, Picobot
from .misc import abspath, color, neighbors, sign
FOV_MIN = 30
FOV_MAX = 120
FOV_INIT = (FOV_MIN+FOV_MAX) / 2
FOV_INIT = (FOV_MIN+FOV_MAX) // 2
CONWAY = 1.303577269034
MOUSE_SPEED = 1/8
EDGE_BRIGHTNESS = 0.5
QUAD = np.float32([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]).tobytes()
OXY = np.float32([[0, 0, 0], [1, 0, 0], [1, 1, 0],
@ -55,9 +56,10 @@ OCTOINDECIES = np.int32([0, 1, 2, 0, 1, 4, 3, 0, 2, 3, 0, 4,
2, 1, 5, 4, 1, 5, 2, 5, 3, 4, 5, 3])
with open(abspath('shaders/map.vert')) as f: MAP_VERTEX = f.read()
with open(abspath('shaders/map.frag')) as f: MAP_FRAGMENT = f.read()
with open(abspath('shaders/world.frag')) as f: WORLD_FRAGMENT = f.read()
with open(abspath('shaders/pico.vert')) as f: PICO_VERTEX = f.read()
with open(abspath('shaders/pico.frag')) as f: PICO_FRAGMENT = f.read()
with open(abspath('shaders/line.geom')) as f: LINE_GEOMETRY = f.read()
with open(abspath('shaders/triangle.geom')) as f: TRIANGLE_GEOMETRY = f.read()
with open(abspath('shaders/tex.vert')) as f: TEX_VERTEX = f.read()
with open(abspath('shaders/sat.frag')) as f: SAT_FRAGMENT = f.read()
@ -114,12 +116,12 @@ class View:
Processed executable code in GLSL for map rendering.
mapva : moderngl.VertexArray
Vertex data of the map.
prog : moderngl.Program
prog2, prog3 : moderngl.Program
Processed executable code in GLSL
for rendering picobots and their shards.
pva : moderngl.VertexArray
pva2, pva3 : moderngl.VertexArray
Vertex data of picobots.
sva : moderngl.VertexArray
sva2, sva3 : moderngl.VertexArray
Vertex data of shards.
pfilter : moderngl.VertexArray
Vertex data for filtering highly saturated colors.
@ -156,7 +158,7 @@ class View:
# Attributes for event-handling
self.camera = camera
self.picos = {address: camera}
self.colors = {address: choice(COLOR_NAMES)}
self.colors = {address: randint(0, 5)}
self.last_time = glfw.get_time()
self.lock = lock
@ -174,7 +176,7 @@ class View:
# Create OpenGL context
self.context = context = moderngl.create_context()
context.enable_only(moderngl.BLEND | moderngl.DEPTH_TEST)
context.enable_only(moderngl.DEPTH_TEST)
# Map creation
self.space, vertices = space, []
@ -188,20 +190,27 @@ class View:
# GLSL program and vertex array for map rendering
self.maprog = context.program(vertex_shader=MAP_VERTEX,
fragment_shader=MAP_FRAGMENT)
self.maprog['color'].write(color('Aluminium').tobytes())
fragment_shader=WORLD_FRAGMENT)
self.maprog['color'].write(bytes((0, 0, 128, 63) * 3))
mapvb = context.buffer(np.stack(vertices).astype(np.float32).tobytes())
self.mapva = context.simple_vertex_array(self.maprog, mapvb, 'in_vert')
# GLSL programs and vertex arrays for picos and shards rendering
self.prog = context.program(vertex_shader=PICO_VERTEX,
fragment_shader=PICO_FRAGMENT)
pvb = [(context.buffer(TETRAVERTICES.tobytes()), '3f', 'in_vert')]
pib = context.buffer(TETRAINDECIES.tobytes())
self.pva = context.vertex_array(self.prog, pvb, pib)
svb = [(context.buffer(OCTOVERTICES.tobytes()), '3f', 'in_vert')]
sib = context.buffer(OCTOINDECIES.tobytes())
self.sva = context.vertex_array(self.prog, svb, sib)
self.prog2 = context.program(vertex_shader=PICO_VERTEX,
geometry_shader=LINE_GEOMETRY,
fragment_shader=WORLD_FRAGMENT)
self.pva2 = context.vertex_array(self.prog2, pvb, pib)
self.sva2 = context.vertex_array(self.prog2, svb, sib)
self.prog3 = context.program(vertex_shader=PICO_VERTEX,
geometry_shader=TRIANGLE_GEOMETRY,
fragment_shader=WORLD_FRAGMENT)
self.pva3 = context.vertex_array(self.prog3, pvb, pib)
self.sva3 = context.vertex_array(self.prog3, svb, sib)
self.pfilter = context.simple_vertex_array(
context.program(vertex_shader=TEX_VERTEX,
@ -311,38 +320,38 @@ class View:
@fps.setter
def fps(self, fps):
self.camera.fps = fps
#print(fps)
def is_pressed(self, *keys) -> bool:
"""Return whether given keys are pressed."""
return any(glfw.get_key(self.window, k) == glfw.PRESS for k in keys)
def prender(self, obj, va, col, bright=0):
def prender(self, obj, va2, va3, col, bright=1.0):
"""Render the obj and its images in bounded 3D space."""
vsqr = self.visibility ** 2
rotation = Matrix44.from_matrix33(obj.rot)
i, j, k = map(sign, self.pos - obj.pos)
for position in product(*zip(obj.pos, obj.pos + [i*12, j*12, k*9])):
if sum((self.pos-position) ** 2) > vsqr: continue
model = rotation @ Matrix44.from_translation(position)
self.prog['model'].write(model.astype(np.float32).tobytes())
self.prog['color'].write(color('Aluminium', -1).tobytes())
self.prog['color'].write(color(col, -1).tobytes())
va.render(moderngl.LINES)
self.prog['color'].write(color(col, bright).tobytes())
va.render(moderngl.TRIANGLES)
rotation = Matrix44.from_matrix33(obj.rot).astype(np.float32).tobytes()
position = obj.pos.astype(np.float32).tobytes()
self.prog2['rot'].write(rotation)
self.prog3['rot'].write(rotation)
self.prog2['pos'].write(position)
self.prog3['pos'].write(position)
self.prog2['color'].write(color(col, bright*EDGE_BRIGHTNESS).tobytes())
self.prog3['color'].write(color(col, bright).tobytes())
va2.render(moderngl.LINES)
va3.render(moderngl.TRIANGLES)
def render_pico(self, pico):
"""Render pico and its images in bounded 3D space."""
self.prender(pico, self.pva, self.colors[pico.addr])
self.prender(pico, self.pva2, self.pva3, self.colors[pico.addr])
def render_shard(self, shard):
"""Render shard and its images in bounded 3D space."""
self.prender(shard, self.sva, self.colors[shard.addr], -shard.power)
self.prender(shard, self.sva2, self.sva3,
self.colors[shard.addr], shard.power/SHARD_LIFE)
def add_pico(self, address, position, rotation):
"""Add picobot from addr at pos with rot."""
self.picos[address] = Pico(address, self.space, position, rotation)
self.colors[address] = choice(COLOR_NAMES)
self.colors[address] = randint(0, 5)
def render(self):
"""Render the scene before post-processing."""
@ -359,9 +368,12 @@ class View:
self.mapva.render(moderngl.TRIANGLES)
# Render picos and shards
self.prog['visibility'].value = visibility
self.prog['camera'].write(self.pos.tobytes())
self.prog['vp'].write(vp)
self.prog2['visibility'].value = visibility
self.prog3['visibility'].value = visibility
self.prog2['camera'].write(self.pos.tobytes())
self.prog3['camera'].write(self.pos.tobytes())
self.prog2['vp'].write(vp)
self.prog3['vp'].write(vp)
self.lock.acquire(blocking=False)
for pico in self.picos.values():