Skip to content

Instantly share code, notes, and snippets.

@stwind
Created February 11, 2024 04:24
Show Gist options
  • Save stwind/1c1db61f64751769e05e5985d8457af5 to your computer and use it in GitHub Desktop.
Save stwind/1c1db61f64751769e05e5985d8457af5 to your computer and use it in GitHub Desktop.
moderngl_intersection.ipynb
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"collapsed_sections": [
"OS522TAwoxLc",
"AISzBVpSoy7h"
],
"authorship_tag": "ABX9TyOc+GlkuzvcGqx1YZXVdHw2",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/stwind/1c1db61f64751769e05e5985d8457af5/moderngl_intersection.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"## Setup"
],
"metadata": {
"id": "QA3u1t5CovtY"
}
},
{
"cell_type": "markdown",
"source": [
"### Dependencies"
],
"metadata": {
"id": "OS522TAwoxLc"
}
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "pbg_b2RYorTN",
"outputId": "35099754-5401-4307-d85b-1a066f8bc3af"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m11.6/11.6 MB\u001b[0m \u001b[31m129.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m4.5/4.5 MB\u001b[0m \u001b[31m217.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m38.4/38.4 MB\u001b[0m \u001b[31m123.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m44.6/44.6 kB\u001b[0m \u001b[31m176.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m267.7/267.7 kB\u001b[0m \u001b[31m268.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m51.5/51.5 kB\u001b[0m \u001b[31m162.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
"\u001b[?25h\u001b[31mERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.\n",
"lida 0.0.10 requires fastapi, which is not installed.\n",
"lida 0.0.10 requires kaleido, which is not installed.\n",
"lida 0.0.10 requires python-multipart, which is not installed.\n",
"lida 0.0.10 requires uvicorn, which is not installed.\n",
"imageio 2.31.6 requires pillow<10.1.0,>=8.3.2, but you have pillow 10.2.0 which is incompatible.\u001b[0m\u001b[31m\n",
"\u001b[0m"
]
}
],
"source": [
"!pip install --no-cache-dir -Uq matplotlib pillow scipy einops ffmpeg-python moderngl"
]
},
{
"cell_type": "markdown",
"source": [
"### Commons"
],
"metadata": {
"id": "AISzBVpSoy7h"
}
},
{
"cell_type": "code",
"source": [
"%matplotlib inline\n",
"%config InlineBackend.figure_format = 'retina'\n",
"\n",
"import os\n",
"import math\n",
"import numpy as np\n",
"import matplotlib as mpl\n",
"import matplotlib.pyplot as plt\n",
"import seaborn as sns\n",
"import cv2\n",
"import PIL\n",
"import matplotlib.font_manager as fm\n",
"import locale\n",
"from fastprogress import progress_bar\n",
"from einops import rearrange, reduce, repeat, einsum\n",
"\n",
"locale.getpreferredencoding = lambda: \"UTF-8\"\n",
"\n",
"def mpl_theme(gray=\"969696\", stroke_width=0.3, fontsize=7, facecolor='1a1a1a'):\n",
" ## category20: https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#category20\n",
" cat20 = mpl.cycler(color=[\"1f77b4\",\"ff7f0e\",\"2ca02c\",\"d62728\",\"9467bd\",\"8c564b\",\"e377c2\",\"7f7f7f\",\"bcbd22\",\"17becf\",\n",
" \"aec7e8\",\"ffbb78\",\"98df8a\",\"ff9896\",\"c5b0d5\",\"c49c94\",\"f7b6d2\",\"c7c7c7\", \"dbdb8d\", \"9edae5\"])\n",
" return {\n",
" \"font.size\": fontsize,\n",
" \"text.color\": gray,\n",
"\n",
" \"figure.dpi\": 100,\n",
" \"figure.facecolor\": facecolor,\n",
" \"figure.frameon\": False,\n",
" \"figure.figsize\": (5, 3),\n",
" \"figure.titlesize\": \"x-large\",\n",
" \"figure.titleweight\": \"bold\",\n",
" \"figure.constrained_layout.use\": True,\n",
" \"figure.constrained_layout.w_pad\": 0.05,\n",
" \"figure.constrained_layout.h_pad\": 0.05,\n",
" \"figure.constrained_layout.wspace\": 0.03,\n",
" \"figure.constrained_layout.hspace\": 0.03,\n",
"\n",
" \"axes.labelcolor\": gray,\n",
" \"axes.labelpad\": 8,\n",
" \"axes.labelsize\": \"large\",\n",
" \"axes.spines.left\": False,\n",
" \"axes.spines.bottom\": False,\n",
" \"axes.spines.top\": False,\n",
" \"axes.spines.right\": False,\n",
" \"axes.facecolor\": facecolor,\n",
" \"axes.edgecolor\": gray,\n",
" \"axes.linewidth\": stroke_width,\n",
" \"axes.axisbelow\": True,\n",
" \"axes.xmargin\": 0.02,\n",
" \"axes.ymargin\": 0.02,\n",
" \"axes.zmargin\": 0.02,\n",
" \"axes.prop_cycle\": cat20,\n",
" \"axes.titlepad\": 6,\n",
" \"axes.titlesize\": \"large\",\n",
" \"axes.titleweight\": \"semibold\",\n",
" \"axes.grid\": True,\n",
" \"axes.grid.axis\": \"both\",\n",
"\n",
" \"axes3d.grid\": False,\n",
"\n",
" \"ytick.right\": False,\n",
" \"ytick.color\": gray,\n",
" \"ytick.major.width\": stroke_width,\n",
" \"ytick.minor.left\": False,\n",
" \"xtick.minor.visible\": True,\n",
" \"xtick.minor.top\": False,\n",
" \"xtick.minor.bottom\": False,\n",
" \"xtick.color\": gray,\n",
" \"xtick.major.width\": stroke_width,\n",
"\n",
" \"grid.color\": gray,\n",
" \"grid.linewidth\": stroke_width,\n",
" \"grid.linestyle\": \"--\",\n",
" \"legend.fancybox\": False,\n",
" \"legend.edgecolor\": '0.3',\n",
" \"legend.framealpha\": 0.7,\n",
" \"legend.handletextpad\": 0.8,\n",
"\n",
" \"lines.linewidth\": 0.7\n",
" }\n",
"\n",
"def add_mpl_font(fname):\n",
" if fname not in [fe.fname for fe in fm.fontManager.ttflist]:\n",
" fm.fontManager.addfont(fname)\n",
"\n",
"def setup_overpass():\n",
" os.makedirs(\"fonts\", exist_ok=True)\n",
" for style in [\"Regular\", \"Italic\", \"SemiBold\", \"SemiBoldItalic\", \"Bold\", \"BoldItalic\"]:\n",
" ttf = f\"Overpass-{style}.ttf\"\n",
" !wget -qc \"https://github.com/RedHatOfficial/Overpass/raw/master/fonts/ttf/{ttf}\" -O \"fonts/{ttf}\"\n",
" add_mpl_font(f\"fonts/{ttf}\")\n",
" mpl.rcParams['font.sans-serif'].insert(0, \"Overpass\")\n",
"\n",
"setup_overpass()\n",
"\n",
"plt.style.use([\"dark_background\", mpl_theme()])"
],
"metadata": {
"id": "4nbknJ_Ooycy"
},
"execution_count": 1,
"outputs": []
},
{
"cell_type": "code",
"source": [
"import io\n",
"import ffmpeg\n",
"import requests\n",
"import subprocess\n",
"import IPython.display as ipd\n",
"from fastprogress import progress_bar\n",
"from einops import rearrange, reduce, repeat\n",
"from base64 import b64encode\n",
"\n",
"def to_single_rgb(img):\n",
" img = np.asarray(img)\n",
" if len(img.shape) == 4: # take first frame from animations\n",
" return img[0,:,:,:]\n",
" if len(img.shape) == 2: # convert gray to rgb\n",
" return img[:,:,np.newaxis].repeat(3, 2)\n",
" if img.shape[-1] == 4: # drop alpha\n",
" return img[:,:,:3]\n",
" else:\n",
" return img\n",
"\n",
"def imread(url, size=None, mode=None):\n",
" if url.startswith(('http:', 'https:')):\n",
" resp = requests.get(url)\n",
" if resp.status_code != 200:\n",
" return None\n",
"\n",
" f = io.BytesIO(resp.content)\n",
" else:\n",
" f = url\n",
" img = PIL.Image.open(f)\n",
" if size is not None:\n",
" img.thumbnail((size, size), PIL.Image.Resampling.LANCZOS)\n",
" if mode is not None:\n",
" img = img.convert(mode)\n",
" return img\n",
"\n",
"def imshow(img, fmt='png', retina=True, zoom=None):\n",
" if isinstance(img, str):\n",
" display(ipd.Image(filename=img, retina=retina))\n",
" return\n",
"\n",
" if len(img.shape) == 3 and img.shape[-1] == 1:\n",
" img = img.squeeze()\n",
" if img.dtype == np.float32:\n",
" img = img * 255.0\n",
" img = np.uint8(img.clip(0, 255))\n",
" if fmt in ('jpeg', 'jpg'):\n",
" img = to_single_rgb(img)\n",
"\n",
" image = PIL.Image.fromarray(img)\n",
" height, width = img.shape[:2]\n",
" if zoom is not None:\n",
" width *= zoom\n",
" height *= zoom\n",
" retina = zoom == 1\n",
" if zoom < 1:\n",
" image.resize((int(width), int(height)))\n",
"\n",
" data = io.BytesIO()\n",
" image.save(data, fmt)\n",
" display(ipd.Image(data=data.getvalue(),width=width, height=height,retina=retina))\n",
"\n",
"def ffprobe_video(path):\n",
" probe = ffmpeg.probe(path)\n",
" return next(s for s in probe['streams'] if s['codec_type'] == 'video')\n",
"\n",
"def read_frame(path, frame_no):\n",
" cap = cv2.VideoCapture(path)\n",
" cap.set(cv2.CAP_PROP_POS_FRAMES, frame_no)\n",
" ret, frame = cap.read()\n",
" if not ret:\n",
" raise RuntimeError(f\"Faild reading frame {frame_no} from {path}\")\n",
" return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n",
"\n",
"def read_frames(path, start=0, num=None):\n",
" cap = cv2.VideoCapture(path)\n",
" n_frames = num or int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
" cap.set(cv2.CAP_PROP_POS_FRAMES, start)\n",
" for i in range(n_frames):\n",
" ret, frame = cap.read()\n",
" if not ret:\n",
" raise RuntimeError(f\"Faild reading frame {i} from {path}\")\n",
" yield cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)\n",
"\n",
"def read_video_frames(path):\n",
" info = ffprobe_video(path)\n",
" out, _ = ffmpeg.input(path).output('pipe:', format='rawvideo', pix_fmt='rgb24').run(capture_stdout=True)\n",
" return np.frombuffer(out, np.uint8).reshape([-1, info['height'], info['width'], 3])\n",
"\n",
"def show_video(path):\n",
" vcap = cv2.VideoCapture(path)\n",
" width = int(vcap.get(cv2.CAP_PROP_FRAME_WIDTH))\n",
" with open(path, \"r+b\") as f:\n",
" url = f\"data:video/mp4;base64,{b64encode(f.read()).decode()}\"\n",
" return ipd.HTML(f\"\"\"<video autoplay=\"autoplay\" width={width} controls loop><source src=\"{url}\"></video>\"\"\")\n",
"\n",
"def write_video(frames, size, path=\"__temp__.mp4\", fps=30, args=[]):\n",
" height, width = size\n",
" command = ['ffmpeg','-v','error','-f','rawvideo','-vcodec','rawvideo',\n",
" '-pix_fmt','rgb24','-s',f'{width}x{height}','-r', f'{fps}',\n",
" '-i', '-','-an', '-vcodec','h264','-pix_fmt','yuv420p', *args, '-y', path]\n",
" with subprocess.Popen(command, stdin=subprocess.PIPE, stderr=subprocess.PIPE) as proc:\n",
" with proc.stdin as stdin:\n",
" for image in frames:\n",
" data = image.tobytes()\n",
" if stdin.write(data) != len(data):\n",
" proc.wait()\n",
" stderr = proc.stderr\n",
" assert stderr is not None\n",
" s = stderr.read().decode()\n",
" raise RuntimeError(f\"Error writing '{path}': {s}\")\n",
" return path\n",
"\n",
"def read_video(path):\n",
" command = ['ffmpeg','-v','error','-nostdin','-i',path,'-vcodec','rawvideo',\n",
" '-f','image2pipe','-pix_fmt','rgb24','-vsync','vfr','-']\n",
"\n",
" info = ffprobe_video(path)\n",
" num_bytes = info['height'] * info['width'] * 3 * np.dtype(np.uint8).itemsize\n",
" with subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:\n",
" stdout = proc.stdout\n",
" assert stdout is not None\n",
" data = stdout.read(num_bytes)\n",
" while data is not None and len(data) == num_bytes:\n",
" image = np.frombuffer(data, dtype=np.uint8)\n",
" yield image.reshape(info['height'], info['width'], 3)\n",
" data = stdout.read(num_bytes)\n",
"\n",
"def norm(x, a, b):\n",
" return (x - a) / (b - a)\n",
"\n",
"def saturate(x):\n",
" return np.clip(x, 0, 1)\n",
"\n",
"def lerp(a, b, t):\n",
" return a * (1.0 - t) + b * t\n",
"\n",
"def step(v, x):\n",
" return np.where(x < v, 0, 1)\n",
"\n",
"def window(x, a, b):\n",
" return step(a, x) * step(x, b)\n",
"\n",
"def satnorm(x, a, b):\n",
" return saturate(norm(x, a, b))\n",
"\n",
"def smoothstep(x):\n",
" return x * x * (3 - 2 * x)\n",
"\n",
"def smootherstep(x):\n",
" return x * x * x * (x * (x * 6 - 15) + 10)\n",
"\n",
"def plt_show(pin=mpl.rcParams['savefig.pad_inches']):\n",
" with plt.rc_context({'savefig.pad_inches': pin}):\n",
" plt.show()\n",
"\n",
"def fig_image(fig=None):\n",
" fig = fig or plt.gcf()\n",
"\n",
" buf = io.BytesIO()\n",
" fig.savefig(buf, format=\"png\", pad_inches=0, facecolor=fig.get_facecolor())\n",
" buf.seek(0)\n",
" data = np.frombuffer(buf.getvalue(), dtype=np.uint8)\n",
" buf.close()\n",
" plt.close(fig)\n",
"\n",
" return cv2.cvtColor(cv2.imdecode(data, cv2.IMREAD_COLOR), cv2.COLOR_BGR2RGB)\n",
"\n",
"class Flex(object):\n",
" def __init__(self, ratios, gap, size=None):\n",
" n, s = len(ratios), sum(ratios)\n",
" self.ratios = ratios\n",
" self.gap = gap\n",
" space = gap * n / s if size is None else gap * n / (size - gap * (n - 1))\n",
" self.h = dict(nrows=1, ncols=n, width_ratios=ratios, wspace=space)\n",
" self.v = dict(nrows=n, ncols=1, height_ratios=ratios, hspace=space)\n",
" self.size = s + gap * (n - 1) if size is None else size\n",
"\n",
"def ax_frame(ax):\n",
" ax.spines[[\"left\",\"right\",\"bottom\",\"top\"]].set_visible(True)\n",
" ax.grid(False)\n",
" ax.set(xticks=[],yticks=[])\n",
"\n",
"def ax_frames(axs):\n",
" for ax in axs.flat: ax_frame(ax)"
],
"metadata": {
"id": "X0s5acn5o1mL"
},
"execution_count": 2,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"## OpenGL"
],
"metadata": {
"id": "qMAZ84-co4my"
}
},
{
"cell_type": "code",
"source": [
"import moderngl\n",
"\n",
"def sdiv(a, b, divide=\"ignore\", invalid=\"ignore\", nan=0, posinf=0, neginf=0, **kwargs):\n",
" with np.errstate(divide=divide, invalid=invalid, **kwargs):\n",
" return np.nan_to_num(a / b, nan=nan, posinf=posinf, neginf=neginf)\n",
"\n",
"def m44_perspective(fovy, aspect, near, far, dtype=\"f4\"):\n",
" f, nf = 1.0 / np.tan(fovy * .5), 1 / (near - far)\n",
"\n",
" return np.array([[f / aspect, 0, 0, 0],\n",
" [0, f, 0, 0],\n",
" [0, 0, (far + near) * nf, 2 * far * near * nf],\n",
" [0, 0, -1, 0]], dtype=dtype)\n",
"\n",
"def m44_orthogonal(l, r, b, t, n, f, dtype=\"f4\"):\n",
" lr, bt, nf = 1 / (l - r), 1 / (b - t), 1 / (n - f)\n",
" return np.array([[-2 * lr,0,0,0],[0,-2 * bt,0,0],[0,0,2 * nf,0],\n",
" [(l + r) * lr, (b + t) * bt, (n + f) * nf,1]], dtype=dtype)\n",
"\n",
"def normalize(x, **kwargs):\n",
" return sdiv(x, np.linalg.norm(x, **kwargs))\n",
"\n",
"def m44_look_at(eye, target, up, dtype=\"f4\"):\n",
" eye, target, up = np.asarray(eye), np.asarray(target), np.asarray(up)\n",
"\n",
" vd = normalize(target - eye)\n",
" side = normalize(np.cross(vd,up))\n",
" up = normalize(np.cross(side,vd))\n",
"\n",
" return np.array([[side[0], side[1], side[2], -side @ eye],\n",
" [up[0], up[1], up[2], -up @ eye],\n",
" [-vd[0], -vd[1], -vd[2], vd @ eye],\n",
" [0, 0, 0, 1]], dtype=dtype)\n",
"\n",
"def m44_scaling(scale):\n",
" return np.diagflat([scale[0], scale[1], scale[2], 1.0]).astype(\"f4\")\n",
"\n",
"def m44_rotation_x(theta, dtype=\"f4\"):\n",
" c, s = np.cos(theta), np.sin(theta)\n",
" return np.array([[1,0,0,0],[0,c,-s,0],[0,s,c,0],[0,0,0,1]],dtype=dtype)\n",
"\n",
"def m44_rotation_y(theta, dtype=\"f4\"):\n",
" c, s = np.cos(theta), np.sin(theta)\n",
" return np.array([[c,0,s,0],[0,1,0,0],[-s,0,c,0],[0,0,0,1]],dtype=dtype)\n",
"\n",
"def m44_rotation_z(theta, dtype=\"f4\"):\n",
" c, s = np.cos(theta), np.sin(theta)\n",
" return np.array([[c,-s,0,0],[s,c,0,0],[0,0,1,0],[0,0,0,1]],dtype=dtype)\n",
"\n",
"def m44_translation(vec, dtype=\"f4\"):\n",
" return np.array([[1,0,0,vec[0]],[0,1,0,vec[1]],[0,0,1,vec[2]],[0,0,0,1]], dtype=dtype)\n",
"\n",
"def gl_primitive(attribs):\n",
" dtype = [(k, f\"({v.shape[-1] if v.ndim > 1 else 1},){v.dtype.descr[0][1]}\")\n",
" for k, v in attribs.items()]\n",
" return np.column_stack(list(attribs.values())).ravel().view(dtype)\n",
"\n",
"def prim_gnomon(size=100):\n",
" position = np.array([[0,0,0],[size,0,0],\n",
" [0,0,0],[0,size,0],\n",
" [0,0,0],[0,0,size]],dtype=\"f4\")\n",
" color = np.array([[1,0,0],[1,0,0],[0,1,0],[0,1,0],[0,0,1],[0,0,1]],dtype=\"f4\")\n",
" return gl_primitive({\"position\": position, \"color\": color})\n",
"\n",
"def prim_triangle():\n",
" position = np.array([[-1,-1,0],[1,-1,0],[0,1,0]],dtype=\"f4\")\n",
" color = np.array([[1,0,0],[0,1,0],[0,0,1]],dtype=\"f4\")\n",
" return gl_primitive({\"position\": position, \"color\": color})\n",
"\n",
"def prim_quad(size=1):\n",
" position = np.array([[-size, size], [-size, -size], [size, size], [size, -size]], dtype=\"f4\")\n",
" texcoord = np.array([[0, 1], [0, 0], [1, 1], [1, 0]], dtype=\"f4\")\n",
" return gl_primitive({\"position\": position, \"texcoord\": texcoord})\n",
"\n",
"def prim_plane(size=1):\n",
" position = np.array([[-size, size, 0], [-size, -size, 0], [size, size, 0], [size, -size, 0]], dtype=\"f4\")\n",
" texcoord = np.array([[0, 1], [0, 0], [1, 1], [1, 0]], dtype=\"f4\")\n",
" return gl_primitive({\"position\": position, \"texcoord\": texcoord})\n",
"\n",
"def prim_tube(r=1, n=32):\n",
" t = np.linspace(0, 1, n, endpoint=False) * np.pi * 2.\n",
" disc = np.c_[np.cos(t) * r, np.ones(n), np.sin(t) * r]\n",
" verts = np.vstack((disc, disc * [1,-1,1])).astype(\"f4\")\n",
"\n",
" faces = []\n",
" for i in range(n):\n",
" i1 = (i + 1) % n\n",
" faces.append((i, i1, i + n))\n",
" faces.append((i + n, i1, n + i1))\n",
" faces = np.asarray(faces, dtype=\"i4\")\n",
" return verts, faces\n",
"\n",
"def uniform_mat(uniform, mat):\n",
" uniform.write(np.ascontiguousarray(mat.T))\n",
"\n",
"class GL(object):\n",
" def __init__(self, size, **kwargs):\n",
" self.size = size\n",
" ctx = self.ctx = moderngl.create_standalone_context(**kwargs)\n",
"\n",
" self.fbo_read = ctx.framebuffer(color_attachments=ctx.texture(size, 4))\n",
"\n",
" self.fbo = self.ctx.framebuffer(\n",
" color_attachments=ctx.texture(size, 4, samples=4),\n",
" depth_attachment=ctx.depth_renderbuffer(size, samples=4)\n",
" )\n",
" self.fbo.use()\n",
"\n",
" def use(self):\n",
" self.fbo.use()\n",
"\n",
" def read(self):\n",
" self.ctx.copy_framebuffer(self.fbo_read, self.fbo)\n",
" return np.flip(np.frombuffer(self.fbo_read.read(components=4),np.uint8).reshape((*self.size, -1)),0)"
],
"metadata": {
"id": "Dr_o3mEho6Qe"
},
"execution_count": 3,
"outputs": []
},
{
"cell_type": "code",
"source": [
"gl = GL((512, 512),backend=\"egl\")\n",
"\n",
"programs = {\n",
" \"vert_color\": gl.ctx.program('''\n",
"#version 330\n",
"uniform mat4 u_model;\n",
"uniform mat4 u_projView;\n",
"\n",
"in vec3 position;\n",
"in vec3 color;\n",
"\n",
"out vec3 v_color;\n",
"\n",
"void main() {\n",
" v_color = color;\n",
" gl_Position = u_projView * u_model * vec4(position, 1.0);\n",
"}''', '''\n",
"#version 330\n",
"uniform float u_alpha;\n",
"\n",
"out vec4 fragColor;\n",
"\n",
"in vec3 v_color;\n",
"\n",
"void main() {\n",
" fragColor = vec4(v_color, u_alpha);\n",
"}'''),\n",
" \"hori\": gl.ctx.program('''\n",
"#version 330\n",
"uniform mat4 u_model;\n",
"uniform mat4 u_projView;\n",
"\n",
"in vec3 position;\n",
"in vec2 texcoord;\n",
"\n",
"out vec2 v_texcoord;\n",
"\n",
"void main() {\n",
" v_texcoord = texcoord;\n",
" gl_Position = u_projView * u_model * vec4(position, 1.0);\n",
"}''', '''\n",
"#version 330\n",
"uniform vec2 u_resolution;\n",
"\n",
"in vec2 v_texcoord;\n",
"\n",
"out vec4 fragColor;\n",
"\n",
"float sdCircle(vec2 p, float r){\n",
" return length(p) - r;\n",
"}\n",
"\n",
"float sdHline(vec2 p, float y, float w) {\n",
" w *= .5;\n",
" float d = length(fwidth(p));\n",
" float ywm = y - w, ywp = y + w;\n",
" return smoothstep(ywm-d,ywm,p.y) * smoothstep(ywp+d,ywp,p.y);\n",
"}\n",
"\n",
"float sdRing(vec2 p, float r, float w) {\n",
" w *= .5;\n",
" float d = length(fwidth(p));\n",
" return smoothstep(0.,d,sdCircle(p, r-w)) * smoothstep(d,0.,sdCircle(p,r+w));\n",
"}\n",
"\n",
"void main() {\n",
" float px = 2. / min(u_resolution.x, u_resolution.y);\n",
"\n",
" vec2 p = v_texcoord * 2. - 1.;\n",
" vec4 color = vec4(1, 1, 1, .05);\n",
" color = mix(color, vec4(1,1,1,.5), sdHline(p, 0., px));\n",
" color = mix(color, vec4(1,1,1,.5), sdRing(p, 0.79, 3. * px));\n",
" fragColor = color;\n",
"}'''),\n",
" \"vert\": gl.ctx.program('''\n",
"#version 330\n",
"uniform mat4 u_model;\n",
"uniform mat4 u_projView;\n",
"\n",
"in vec3 position;\n",
"in vec2 texcoord;\n",
"\n",
"out vec2 v_texcoord;\n",
"\n",
"void main() {\n",
" v_texcoord = texcoord;\n",
" gl_Position = u_projView * u_model * vec4(position, 1.0);\n",
"}''', '''\n",
"#version 330\n",
"uniform vec2 u_resolution;\n",
"\n",
"in vec2 v_texcoord;\n",
"\n",
"out vec4 fragColor;\n",
"\n",
"float sdVline(vec2 p, float x, float w) {\n",
" w *= .5;\n",
" float d = length(fwidth(p));\n",
" float ywm = x - w, ywp = x + w;\n",
" return smoothstep(ywm-d,ywm,p.x) * smoothstep(ywp+d,ywp,p.x);\n",
"}\n",
"\n",
"void main() {\n",
" float px = 2. / min(u_resolution.x, u_resolution.y);\n",
"\n",
" vec2 p = v_texcoord * 2. - 1.;\n",
" vec4 color = vec4(1, 1, 1, .05);\n",
" color = mix(color, vec4(1,1,1,.5), sdVline(p, .8, px));\n",
" color = mix(color, vec4(1,1,1,.5), sdVline(p, -.8, px));\n",
" fragColor = color;\n",
"}'''),\n",
" \"const\": gl.ctx.program('''\n",
"#version 330\n",
"uniform mat4 u_model;\n",
"uniform mat4 u_projView;\n",
"\n",
"in vec3 position;\n",
"\n",
"void main() {\n",
" gl_Position = u_projView * u_model * vec4(position, 1.0);\n",
"}''', '''\n",
"#version 330\n",
"uniform vec4 u_color;\n",
"\n",
"out vec4 fragColor;\n",
"\n",
"void main() {\n",
" fragColor = u_color;\n",
"}''')\n",
"}\n",
"\n",
"uniform_mat(programs[\"vert_color\"][\"u_model\"], np.eye(4, dtype=\"f4\"))\n",
"programs[\"vert_color\"][\"u_alpha\"].value = 1\n",
"uniform_mat(programs[\"const\"][\"u_model\"], np.eye(4, dtype=\"f4\"))\n",
"programs[\"const\"][\"u_color\"].write(np.array([1,1,1,.05],dtype=\"f4\"))\n",
"uniform_mat(programs[\"hori\"][\"u_model\"], np.eye(4, dtype=\"f4\"))\n",
"uniform_mat(programs[\"hori\"][\"u_resolution\"], np.asarray(gl.size, dtype=\"f4\"))\n",
"uniform_mat(programs[\"vert\"][\"u_model\"], np.eye(4, dtype=\"f4\"))\n",
"uniform_mat(programs[\"vert\"][\"u_resolution\"], np.asarray(gl.size, dtype=\"f4\"))\n",
"\n",
"gnomon = gl.ctx.simple_vertex_array(programs[\"vert_color\"], gl.ctx.buffer(prim_gnomon()), 'position', 'color')"
],
"metadata": {
"id": "AjNxvuZHpRvw"
},
"execution_count": 4,
"outputs": []
},
{
"cell_type": "code",
"source": [
"plane1 = gl.ctx.simple_vertex_array(programs[\"hori\"], gl.ctx.buffer(prim_plane()), 'position', 'texcoord')\n",
"plane2 = gl.ctx.simple_vertex_array(programs[\"vert\"], gl.ctx.buffer(prim_plane()), 'position', 'texcoord')\n",
"\n",
"verts, indices = prim_tube(.8, n=32)\n",
"tube = gl.ctx.simple_vertex_array(\n",
" programs[\"const\"],\n",
" gl.ctx.buffer(verts),\n",
" 'position',\n",
" index_buffer=gl.ctx.buffer(indices))"
],
"metadata": {
"id": "D_hhad8w610V"
},
"execution_count": 5,
"outputs": []
},
{
"cell_type": "code",
"source": [
"view = m44_look_at([1.5,1.5,4],[0,0,0],[0,1,0])\n",
"proj = m44_perspective(np.radians(45),1,.01, 100)\n",
"\n",
"# view = m44_look_at([.2,.5,1],[0,0,0],[0,1,0])\n",
"# proj = m44_orthogonal(-1.5,1.5,-1.5,1.5,-5,5)\n",
"\n",
"uniform_mat(programs[\"vert_color\"][\"u_projView\"], proj @ view)\n",
"uniform_mat(programs[\"const\"][\"u_projView\"], proj @ view)\n",
"uniform_mat(programs[\"hori\"][\"u_projView\"], proj @ view)\n",
"uniform_mat(programs[\"vert\"][\"u_projView\"], proj @ view)"
],
"metadata": {
"id": "gZVkwoSUqCn3"
},
"execution_count": 6,
"outputs": []
},
{
"cell_type": "code",
"source": [
"gl.ctx.enable(moderngl.BLEND)\n",
"gl.ctx.blend_func = moderngl.SRC_ALPHA, moderngl.ONE_MINUS_SRC_ALPHA\n",
"gl.ctx.blend_equation = moderngl.FUNC_ADD\n",
"\n",
"gl.ctx.enable(moderngl.CULL_FACE)\n",
"gl.ctx.enable(moderngl.DEPTH_TEST)\n",
"\n",
"gl.fbo.depth_mask = True\n",
"gl.ctx.clear(.1, .1, .1, 1)\n",
"\n",
"gnomon.render(mode=moderngl.LINES)\n",
"\n",
"gl.ctx.disable(moderngl.CULL_FACE)\n",
"gl.fbo.depth_mask = False\n",
"\n",
"tube.render(mode=moderngl.TRIANGLES)\n",
"\n",
"uniform_mat(programs[\"hori\"][\"u_model\"], m44_rotation_x(-np.pi/2))\n",
"plane1.render(mode=moderngl.TRIANGLE_STRIP)\n",
"\n",
"plane2.render(mode=moderngl.TRIANGLE_STRIP)\n",
"\n",
"PIL.Image.fromarray(gl.read())"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 529
},
"id": "hwAiUtDsqIGj",
"outputId": "07a53e80-b4e0-4171-c479-269210f604dc"
},
"execution_count": 7,
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"<PIL.Image.Image image mode=RGBA size=512x512>"
],
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AABrPElEQVR4Ae29WawlyZ3ed87dt6q6tXV1V3cXm2w2m80mqWFzIM0iQxoLGlmjESBDY8AGNA+jF0GARg8jjOBXA34bDAEvzxaMEfzUhh8lbxrAQ5A2bJEcCsNpdrP36q7qWm/Vrbsvx9+Xdf6n8uTNc07uGZHxXSBv5sklll9ERnzxjyX7169fH/T0JwIiECSB7T/Y7p373rkg465Ii0DoBOZCB6D4i4AIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDwBCYDgs4AAiIAIiIAIhEhAAiDEVFecRUAEREAEgicgARB8FhAAERABERCBEAlIAISY6oqzCIiACIhA8AQkAILPAgIgAiIgAiIQIgEJgBBTXXEWAREQAREInoAEQPBZQABEQAREQARCJCABEGKqK84iIAIiIALBE5AACD4LCIAIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDwBCYDgs4AAiIAIiIAIhEhAAiDEVFecRUAEREAEgicgARB8FhAAERABERCBEAlIAISY6oqzCIiACIhA8AQkAILPAgIgAiIgAiIQIgEJgBBTXXEWAREQAREInoAEQPBZQABEQAREQARCJCABEGKqK84iIAIiIALBE5AACD4LCIAIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDwBCYDgs4AAiIAIiIAIhEhAAiDEVFecRUAEREAEgicgARB8FhAAERABERCBEAlIAISY6oqzCIiACIhA8AQkAILPAgIgAiIgAiIQIgEJgBBTXXEWAREQAREInoAEQPBZQABEQAREQARCJCABEGKqK84iIAIiIALBE5AACD4LCIAIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDwBCYDgs4AAiIAIiIAIhEhAAiDEVFecRUAEREAEgicgARB8FhAAERABERCBEAlIAISY6oqzCIiACIhA8AQkAILPAgIgAiIgAiIQIgEJgBBTXXEWAREQAREInoAEQPBZQABEQAREQARCJCABEGKqK84iIAIiIALBE5AACD4LCIAIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDwBCYDgs4AAiIAIiIAIhEhAAiDEVFecRUAEREAEgicgARB8FhAAERABERCBEAlIAISY6oqzCIiACIhA8AQkAILPAgIgAiIgAiIQIgEJgBBTXXEWAREQAREInoAEQPBZQABEQAREQARCJCABEGKqK84iIAIiIALBE5AACD4LCIAIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDwBCYDgs4AAiIAIiIAIhEhAAiDEVFecRUAEREAEgicgARB8FhAAERABERCBEAlIAISY6oqzCIiACIhA8AQkAILPAgIgAiIgAiIQIgEJgBBTXXEWAREQAREInoAEQPBZQABEQAREQARCJCABEGKqK84iIAIiIALBE5AACD4LCIAIiIAIiECIBCQAQkx1xVkEREAERCB4AhIAwWcBARABERABEQiRgARAiKmuOIuACIiACARPQAIg+CwgACIgAiIgAiESkAAIMdUVZxEQAREQgeAJSAAEnwUEQAREQAREIEQCEgAhprriLAIiIAIiEDyBheAJCIAINEhgfn7+JM27SeeT92a9L/ncpN+7C7tLq6urh5Ou5z1/cnIyP+uZWffMuj7LfV0XARHIRkACIBsn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Pqeewi4CIiACItAmAW8tADbNT3P828w+8lsEREAERMBXAl5aAFj561O+vmY5hVsEREAERMAFAt4JAE7zY+WvaX4uZB+FQQREQAREwFcC3ggAjvTXHH9fs5nCLQIiIAIi4BoBLwSATfNbWvqBWv6u5SCFRwREQAREwEsCzgsA9vffu/c2Kv4/VuXvZRZToEVABERABFwk4PQsAE3zczHLKEwiIAIiIAJdIOCsBcCm+V258jv4qtoPu8BacRABERABERABZwg4aQGwyl9f83MmnyggIiACIiACHSPgnAWAlb/m+Hcslyk6IiACIiACzhFwSgBojr9z+UMBEgEREAER6CgBJwQAp/l9/vln6OvXNL+O5jNFSwREQAREwDECrQsAm+OvaX6O5QwFRwREQAREoNMEWhUA7O/XHP9O5y9FTgREQAREwFECrc0CYOW/vf0HPU3zczRnKFgiIAIiIAKdJtCKALBpfvqUb6fzliInAiIgAiLgMIHGuwCs8tccf4dzhYImAiIgAiLQeQKNCgCb5qfKv/P5ShEUAREQARFwnEBjAkCf8nU8Jyh4IiACIiACQRGoXQBojn9Q+UmRFQEREAER8IRArQJA0/w8yQUKpgiIgAiIQHAEapsFoGl+weUlRVgEREAERMAjArVYAGykv+b4e5QTFFQREAEREIGgCFRuAbDKXyP9g8pHiqwIiIAIiIBnBCq1ALDy16d8PcsBCq4IiIAIiECQBCoTADbH/9y57wUJUpEWAREQAREQAZ8IlBYAmubnU3IrrCIgAiIgAiLwlEApAaBP+SobiYAIiIAIiICfBAoLAPb361O+fia6Qi0CIiACIiAChWYBsPLXp3yVeURABERABETAXwK5BYBN89OnfP1NdIVcBERABERABHJ1AVjlrzn+yjgiIAIiIAIi4DeBzALApvmp8vc7wRV6ERABERABESCBTAJAn/JVZhEBERABERCBbhGYKgA0x79bia3YiIAIiIAIiIARmCgANM3PEGkvAiIgAiIgAt0jkDoLQNP8upfQipEIiIAIiIAIxAmcsQCo8o/j0bEIiIAIiIAIdJPAmAXApvlpjn83E1uxEgEREAEREAEjMLIAsPLXp3wNi/YiIAIiIAIi0G0CkQCwOf76lG+3E1uxEwEREAEREAEj0F9e/sFgaekHPVX+hkR7ERABERABEeg+gf6VK/8QAuCH3Y+pYigCIiACIiACIjAiMKfKf8RCByIgAiIgAiIQDIHRIMBgYqyIioAIiIAIiIAIZPsWgDiJgAiIgAiIgAh0i4AsAN1KT8VGBERABERABDIRkADIhEk3iYAIiIAIiEC3CEgAdCs9FRsREAEREAERyERAAiATJt0kAiIgAiIgAt0iIAHQrfRUbERABERABEQgEwEJgEyYdJMIiIAIiIAIdIuABEC30lOxEQEREAEREIFMBCQAMmHSTSIgAiIgAiLQLQISAN1KT8VGBERABERABDIRkADIhEk3iYAIiIAIiEC3CEgAdCs9FRsREAEREAERyERAAiATJt0kAiIgAiIgAt0iIAHQrfRUbERABERABEQgEwEJgEyYdJMIiIAIiIAIdIuABEC30lOxEQEREAEREIFMBCQAMmHSTSIgAiIgAiLQLQISAN1KT8VGBERABERABDIRkADIhEk3iYAIiIAIiEC3CEgAdCs9FRsREAEREAERyERAAiATJt0kAiIgAiIgAt0iIAHQrfRUbERABERABEQgEwEJgEyYdJMIiIAIiIAIdIuABEC30lOxEQEREAEREIFMBCQAMmHSTSIgAiIgAiLQLQISAN1KT8VGBERABERABDIRkADIhEk3iYAIiIAIiEC3CPz/gC+c55Jtez8AAAAASUVORK5CYII=\n"
},
"metadata": {},
"execution_count": 7
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "xGGcdOAoqKPc"
},
"execution_count": 8,
"outputs": []
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment