Skip to content

Instantly share code, notes, and snippets.

@RenaKunisaki RenaKunisaki/glsl_test.py
Last active Jun 12, 2019

Embed
What would you like to do?
moderngl test
#!/usr/bin/env python
import moderngl_window as mglw
import moderngl
import struct
VERTEX_SHADER = """
#version 400
/** Shader for drawing lines of varying thickness,
* including bezier curves.
* Used with lines.geom.
*/
in vec3 in_p0;
in vec3 in_p1;
in vec3 in_c0;
in vec3 in_c1;
//input attributes
out struct {
vec3 p0, p1, c0, c1;
} v_point;
void main() {
v_point.p0 = in_p0;
v_point.p1 = in_p1;
v_point.c0 = in_c0;
v_point.c1 = in_c1;
}
"""
GEOMETRY_SHADER = """
#version 420
#define PI 3.141592654
/** Shader for drawing lines of varying thickness,
* including bezier curves.
* Used with lines.vert.
*/
//we consume points (in groups of 4) and produce quads (the lines)
//max_vertices is for the entire shader, not per primitive
//each line has 4 vertices per segment
layout(points) in;
layout(triangle_strip, max_vertices=256) out;
//input from vertex shader
in struct {
vec3 p0, p1, c0, c1;
} v_point[];
//outputs to fragment shader
out vec4 fragColor; //fragment color
out vec2 fragTexCoord; //fragment texture coordinates
//transformation matrices
uniform mat4 matProjection; //projection matrix
uniform mat4 matModelview; //modelview matrix
//types used internally
struct Point {
/** Describes one point of a line.
*/
vec4 pos; //The point's coords
vec4 color; //The point's color
vec2 texCoord; //The point's texture coord
float lineWidth; //The line width at this point
};
vec2 rotate2d(vec2 vector, float angle) {
/** Rotate 2D vector.
* vector: Input vector.
* angle: Rotation angle in radians.
* Returns `vector` rotated by `angle`.
*/
return mat2(
cos(angle), -sin(angle),
sin(angle), cos(angle)
) * vector;
}
void drawVtx(vec4 pos, vec4 color, vec2 texCoord) {
/** Draw one vertex.
* pos: Vertex coordinates.
* color: Vertex color.
* texCoord: Vertex texture coordinate.
*/
gl_Position = matProjection * matModelview * pos;
fragTexCoord = texCoord;
fragColor = color;
EmitVertex();
}
void drawCircle(vec4 pos, float radius, int nVtxs) {
/** Draw a circle.
* pos: Coordinates of the circle's centre.
* radius: Circle's radius.
* nVtxs: Number of vertices to draw. Higher values
* produce a smoother circle but take longer to draw.
* Very rough circles may have a visible gap at one
* vertex due to drawing in triangle_strip mode.
*/
//subtract 1 so that we close the circle
//(this is number of vertices, not number of segments)
float d = (2.0 * PI) / float(nVtxs-1);
for(int i=0; i<nVtxs; i++) {
float t = d * i;
float x = pos.x + (radius * cos(t));
float y = pos.y + (radius * sin(t));
drawVtx(
vec4(x, y, pos.zw),
vec4(x, y, pos.zw),
vec2(x, y)
);
}
EndPrimitive();
}
void drawPoint(vec4 pos, vec4 color, float size) {
/** Draw a square point. Mainly used for debugging.
* pos: Coordinates of the point's centre.
* color: Color of the point.
* size: Width and height of the point.
*/
float s = size / 2.0;
drawVtx(vec4(pos.x-s, pos.y-s, pos.zw), color, vec2(0,0));
drawVtx(vec4(pos.x-s, pos.y+s, pos.zw), color, vec2(0,0));
drawVtx(vec4(pos.x+s, pos.y-s, pos.zw), color, vec2(0,0));
drawVtx(vec4(pos.x+s, pos.y+s, pos.zw), color, vec2(0,0));
EndPrimitive();
}
void drawLine(Point p0, Point p1) {
/** Draw a line of arbitrary thickness.
* p0: Start point.
* p1: End point.
* The points define coordinate, color, and texture coord.
*/
//compute angle of line
vec2 d = p1.pos.xy - p0.pos.xy;
float theta = atan(d.y, d.x);
//rotate by 90 degrees and move by lineWidth
//so that the original coord is on the midpoint of the edge
//of a rect from p0 to p1
float a = mod((theta + (PI / 2.0)), (2.0 * PI));
vec2 offset0 = rotate2d(vec2(p0.lineWidth / 2, 0), -a);
vec2 offset1 = rotate2d(vec2(p1.lineWidth / 2, 0), -a);
vec4 corner0 = vec4(p0.pos.xy + offset0, p0.pos.zw);
vec4 corner1 = vec4(p0.pos.xy - offset0, p0.pos.zw);
vec4 corner2 = vec4(p1.pos.xy + offset1, p1.pos.zw);
vec4 corner3 = vec4(p1.pos.xy - offset1, p1.pos.zw);
//draw rect between the corners
//swapping the colors/texcoords of corners 1 and 2 means
//the gradient is perpendicular to the segment,
//instead of parallel.
drawVtx(corner0, p0.color, p0.texCoord);
drawVtx(corner1, p1.color, p1.texCoord);
drawVtx(corner2, p0.color, p0.texCoord);
drawVtx(corner3, p1.color, p1.texCoord);
//EndPrimitive();
//don't end primitive; the line looks better if it's one
//long triangle strip instead of independent rects.
}
void main() {
//drawCircle(vec4(100, 100, -1, 1), 8, 8);
for(int i = 0; i < gl_in.length(); i++) { //for each line
//drawCircle(vec4(100 + (20 * i), 100, -1, 1), 2, 8);
drawPoint(vec4(v_point[i].p0.xyz, 1), vec4(1, 0, 0, 1), 16);
drawPoint(vec4(v_point[i].p1.xyz, 1), vec4(0, 1, 0, 1), 16);
drawPoint(vec4(v_point[i].c0.xyz, 1), vec4(0, 0, 1, 1), 4);
drawPoint(vec4(v_point[i].c1.xyz, 1), vec4(1, 1, 0, 1), 4);
EndPrimitive();
drawLine(
Point(vec4(v_point[i].p0.xyz,1), vec4(1,0,0,1), vec2(0,0), 1),
Point(vec4(v_point[i].c0.xyz,1), vec4(1,0,0,1), vec2(0,0), 1));
EndPrimitive();
drawLine(
Point(vec4(v_point[i].p1.xyz,1), vec4(0,1,0,1), vec2(0,0), 1),
Point(vec4(v_point[i].c1.xyz,1), vec4(0,1,0,1), vec2(0,0), 1));
EndPrimitive();
drawLine(
Point(vec4(v_point[i].p0.xyz,1), vec4(0,0,1,1), vec2(0,0), 1),
Point(vec4(v_point[i].p1.xyz,1), vec4(0,0,1,1), vec2(0,0), 1));
EndPrimitive();
}
}
"""
FRAGMENT_SHADER = """
#version 400
uniform bool enableTexture = false;
uniform float minAlpha = 0.0; //discard texels where alpha < minAlpha
uniform vec4 modColor = vec4(1,1,1,1); //multiply all colors by this
uniform sampler2D inTexture;
in vec4 fragColor; //set by vertex shader
in vec2 fragTexCoord;
out vec4 outputColor; //the resulting fragment color
void main () {
vec4 color = fragColor;
if(enableTexture) {
color *= texture2D(inTexture, fragTexCoord.st).rgba;
}
color *= modColor;
if(color.a < minAlpha) discard;
outputColor = color;
//outputColor = gl_FragCoord;
//outputColor = vec4(1, 0, 0, 1);
}
"""
def checkError(obj, where):
err = obj.app.ctx.error
if err is not None and err != "GL_NO_ERROR":
print("Error:", where, err)
def makePerspectiveMatrix(left, right, bottom, top, near, far):
#names from glFrustum doc
W = (2*near) / (right-left)
H = (2*near) / (top-bottom)
A = (right+left) / (right-left)
B = (top+bottom) / (top-bottom)
C = -((far+near) / (far-near))
D = -((2*far*near) / (far-near))
return (
W, 0, 0, 0,
0, H, 0, 0,
A, B, C, -1,
0, 0, D, 0)
def makeIdentityMatrix(size):
data = []
for y in range(size):
for x in range(size):
data.append(1 if x == y else 0)
return tuple(data)
class Program:
"""Base class for shader programs."""
def __init__(self, app):
self.app = app
@property
def memUsedCpu(self):
"""Estimated amount of CPU-side memory used."""
return None # amount not known
@property
def memUsedGpu(self):
"""Estimated amount of GPU-side memory used."""
return None # amount not known
def run(self, *args, **kwargs):
"""Execute the program."""
raise NotImplementedError
class LineDraw(Program):
"""Line drawing shader program.
Draws lines of arbitrary thickness, including bezier curves.
"""
MAX_POINTS = 1024
POINT_FMT = '3f'
LINE_FMT = '4i'
def __init__(self, app):
super().__init__(app)
# load shaders
self.program = self.app.loadProgram(
vertex_shader = VERTEX_SHADER,
geometry_shader = GEOMETRY_SHADER,
fragment_shader = FRAGMENT_SHADER,
)
# vtx -> tesselate -> geom -> fragment
# set up some parameters in the shader
self.program['matModelview'] .value = makeIdentityMatrix(4)
self.program['enableTexture'].value = False
self.program['minAlpha'] .value = 0.5
self.program['modColor'] .value = (1.0, 1.0, 1.0, 1.0)
# make vertex buffer/attribute objects to hold the line data
self.pointDataSize = struct.calcsize(self.POINT_FMT)
self.lineDataSize = struct.calcsize(self.LINE_FMT)
self.vboVtxs = self.app.ctx.buffer( # the actual vertices
reserve=self.MAX_POINTS * self.pointDataSize,
dynamic=True,
)
self.iboLines = self.app.ctx.buffer( # vtx index buffer
reserve=self.MAX_POINTS * self.lineDataSize,
dynamic=True,
)
self.vao = self.app.ctx.vertex_array(self.program,
[ # inputs to the vertex shader
(self.vboVtxs, '3f 3f 3f 3f',
'in_p0', 'in_p1', 'in_c0', 'in_c1'),
],
self.iboLines,
#None,
)
#self.vboVtxs.bind_to_uniform_block(
# self.program['vertices'].location)
p0 = self.program['in_p0'].location
p1 = self.program['in_p1'].location
c0 = self.program['in_c0'].location
c1 = self.program['in_c1'].location
vbo = self.vboVtxs
checkError(self, "bind 0")
print("p0=", p0, "p1=", p1, "c0=", c0, "c1=", c1)
self.vao.bind(p0, 'f', vbo, '3f', offset=0, stride=12*4)
checkError(self, "bind 1")
self.vao.bind(p1, 'f', vbo, '3f', offset=12, stride=12*4)
checkError(self, "bind 2")
self.vao.bind(c0, 'f', vbo, '3f', offset=24, stride=12*4)
checkError(self, "bind 3")
self.vao.bind(c1, 'f', vbo, '3f', offset=36, stride=12*4)
checkError(self, "bind 4")
print("bind OK")
def setVertices(self, idx, *points):
"""Change one or more vertices in the buffer.
idx: Point index to set in the buffer.
points: Point to write.
"""
if idx < 0: idx = self.MAX_POINTS + idx
if idx < 0 or idx + len(points) >= self.MAX_POINTS:
raise IndexError(idx)
data = []
for p in points:
data.append(struct.pack(self.POINT_FMT, *p))
self.vboVtxs.write(b''.join(data),
offset = idx * self.pointDataSize)
def setLines(self, idx, *lines):
"""Change one or more lines in the buffer.
idx: Line to set.
lines: Vertex indices to write. (p0, p1, c0, c1)
"""
# XXX should we be using MAX_POINTS here?
if idx < 0: idx = self.MAX_POINTS + idx
if idx < 0 or idx + len(lines) >= self.MAX_POINTS:
raise IndexError(idx)
data = []
for line in lines:
p0, p1, c0, c1 = line
if c0 is None: c0 = p0
if c1 is None: c1 = c0
#p0 = 3
#p1 = 1
#c0 = 2
#c1 = 0
data.append(struct.pack(self.LINE_FMT, p0, p1, c0, c1))
self.iboLines.write(b''.join(data),
offset = idx * self.lineDataSize)
def run(self):
"""Draw the lines."""
#data = self.iboLines.read()
#dump = []
#for i in range(0, 0x100, 16):
# line = "%04X " % i
# for j in range(16):
# if (j&3) == 0: line += ' '
# line += "%02X " % data[i+j]
# dump.append(line)
#print("index buffer (obj %d):\n%s" % (
# self.iboLines.glo,
# '\n'.join(dump),
#))
checkError(self, "run 1")
#self.ctx.patch_vertices = 4
checkError(self, "run 2")
# update projection matrix to current viewport
x, y, width, height = self.app.ctx.viewport
self.program['matProjection'].value = \
makePerspectiveMatrix(0, width, height, 0, 1, 100)
checkError(self, "run 3")
checkError(self, "run 4")
self.vao.render(mode=moderngl.POINTS)
checkError(self, "run 5")
class Test(mglw.WindowConfig):
gl_version = (4, 0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._setup()
def render(self, time, frametime):
self.ctx.clear(0.0, 0.5, 0.5, 0.0)
query = self.ctx.query(samples=True, any_samples=False,
time=True, primitives=True)
with query:
self.lineDraw.run()
self.ctx.finish() # wait for finish, not really needed
err = self.ctx.error
if err is not None and err != "GL_NO_ERROR": print("GL ERROR:", err)
def _setup(self):
self.lineDraw = LineDraw(self)
self._updateViewport()
print("setting the buffers...")
self.lineDraw.setVertices(0,
# p0, p1, c0, c1
( 32, 64, -1),
(640, 640, -1),
( 32, 640, -1),
(640, 64, -1),
)
self.lineDraw.setLines(0,
(0, 1, 1, 1),
)
print("set OK")
def loadProgram(self, **files):
"""Load a shader program.
files: Paths to shader code files; valid keys:
- fragment_shader
- vertex_shader
- geometry_shader
- XXX others?
Returns a Program.
"""
shaders = {}
for name, path in files.items():
#with open(path, 'rt') as file:
# shaders[name] = file.read()
shaders[name] = path
return self.ctx.program(**shaders)
def _updateViewport(self):
"""Called when widget is created or resized, to update the
GL viewport to match its dimensions.
"""
pos = (0, 0)
width, height = 1024, 768
self.ctx.viewport = (pos[0], pos[1], width, height)
print("Viewport:", pos[0], pos[1], width, height)
mglw.run_window_config(Test)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.