From 88f1f45b9490cf3e9629c9f372ab16b214893b18 Mon Sep 17 00:00:00 2001 From: bk_office <2680813175@qq.com> Date: Mon, 27 Apr 2026 15:28:13 +0800 Subject: [PATCH] =?UTF-8?q?manual=20=E8=A1=A5=E5=85=85=EF=BC=9B=20cpu=20?= =?UTF-8?q?=E5=86=99=E6=B3=95=E9=80=9F=E5=BA=A6=E4=B8=8D=E5=8F=AF=E6=8E=A5?= =?UTF-8?q?=E5=8F=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- konabot/docs/user/march.txt | 18 ++++ konabot/plugins/marchtoy/cpu_render.py | 109 ------------------------- 2 files changed, 18 insertions(+), 109 deletions(-) delete mode 100644 konabot/plugins/marchtoy/cpu_render.py diff --git a/konabot/docs/user/march.txt b/konabot/docs/user/march.txt index b2b3dae..67b50eb 100644 --- a/konabot/docs/user/march.txt +++ b/konabot/docs/user/march.txt @@ -14,5 +14,23 @@ 其中 `obj_ty`、`op_ty` 分别为物体类型(如 `cube`、`sphere`、`torus` 等)与变换类型(如 `pos`、`rot`)。 +# 支持的物体 +目前支持的物体有(不包含 alias): +`cube`:可选参数长宽高 +`sphere`:可选参数半径 +`torus`:可选参数半径与粗细 +`cylinder`:可选参数半径与高度 +`capsule`:可选参数高度与半径 + +特殊物体: +`camera`:可选参数焦距 + +# 支持的变换 +目前支持的变换有 +`pos` +`rot` +`color` +`lookat` + # 特殊说明 `` 不包含 scale。非正交的变换会破坏 SDF 的性质。 \ No newline at end of file diff --git a/konabot/plugins/marchtoy/cpu_render.py b/konabot/plugins/marchtoy/cpu_render.py deleted file mode 100644 index 4836d46..0000000 --- a/konabot/plugins/marchtoy/cpu_render.py +++ /dev/null @@ -1,109 +0,0 @@ -from typing import Optional - -import numpy as np -from PIL import Image - -from konabot.plugins.marchtoy.command import Scene -from konabot.plugins.marchtoy.obj import Object, Transform - - -class SDQuery: - def __init__(self, _value: float = np.inf, _obj: Optional[Object] = None): - self.value: float = _value - self.obj: Optional[Object] = _obj - - -class Rasterizer: - EPS = 0.001 - MAX_ITER = 128 - MAX_DIST = 128.0 - - def __init__(self, _scene: Scene, _res: tuple[int, int]): - self.scene = _scene - self.width, self.height = _res - self.fb_rgba = np.zeros((self.height, self.width, 4), dtype=np.float32) - - def sdf(self, p: np.ndarray) -> SDQuery: - qry = SDQuery() - for obj, _ in self.scene.canvas_objs: - sd = obj.sdf(p) - if sd < qry.value: - qry.value = sd - qry.obj = obj - return qry - - def nrm(self, p: np.ndarray) -> np.ndarray: - dx = np.array((self.EPS, 0.0, 0.0)) - dy = np.array((0.0, self.EPS, 0.0)) - dz = np.array((0.0, 0.0, self.EPS)) - grad = np.array( - [ - self.sdf(p + dx).value - self.sdf(p - dx).value, - self.sdf(p + dy).value - self.sdf(p - dy).value, - self.sdf(p + dz).value - self.sdf(p - dz).value, - ], - dtype=np.float32, - ) - return Transform.normalize(grad) - - def color(self, p: np.ndarray, obj: Object) -> np.ndarray: - normal = self.nrm(p) - light_dir = Transform.normalize(np.array((0.5, 0.8, -0.6), dtype=np.float32)) - light = 0.2 + 0.8 * max(float(np.dot(normal, light_dir)), 0.0) - base = np.array(obj.texture.color, dtype=np.float32) - return np.array((base[0] * light, base[1] * light, base[2] * light, base[3])) - - def march(self, origin: np.ndarray, direction: np.ndarray) -> np.ndarray: - p = origin.copy() - travel = 0.0 - - for _ in range(self.MAX_ITER): - qry = self.sdf(p) - if not np.isfinite(qry.value): - break - if qry.value < self.EPS and qry.obj is not None: - return self.color(p, qry.obj) - if qry.value > self.MAX_DIST or travel > self.MAX_DIST: - break - p += direction * qry.value - travel += qry.value - - return np.zeros(4, dtype=np.float32) - - def camera_basis(self) -> tuple[np.ndarray, np.ndarray]: - cam = self.scene.camera - cam_pos = cam.transform.t[0:3, 3] - cam_z = Transform.normalize( - (cam.transform.t @ np.array((0.0, 0.0, -1.0, 0.0), dtype=np.float32))[:3] - ) - world_up = ( - np.array((0.0, 0.0, 1.0), dtype=np.float32) - if abs(cam_z[1]) > 0.999 - else np.array((0.0, 1.0, 0.0), dtype=np.float32) - ) - cam_x = Transform.normalize(np.cross(cam_z, world_up)) - cam_y = Transform.normalize(np.cross(cam_x, cam_z)) - view = np.column_stack((cam_x, cam_y, cam_z)) - return cam_pos, view - - def rasterize(self) -> np.ndarray: - cam_pos, view = self.camera_basis() - resolution = np.array((self.width, self.height), dtype=np.float32) - aspect = np.array((self.width / self.height, 1.0), dtype=np.float32) - cam_focus = float(self.scene.camera.focus) - - for y, x in np.ndindex(self.height, self.width): - frag_coord = np.array((x + 0.5, y + 0.5), dtype=np.float32) - uv = 2.0 * (frag_coord / resolution - 0.5) * aspect - ray_local = Transform.normalize(np.array((uv[0], uv[1], cam_focus))) - ray_world = Transform.normalize(view @ ray_local) - self.fb_rgba[y, x] = self.march(cam_pos, ray_world) - - return self.fb_rgba - - -async def render(command: str, res: tuple[int, int]): - rasterizer = Rasterizer(Scene(command), res) - fb_output = rasterizer.rasterize() - rgba = np.clip(fb_output * 255.0, 0.0, 255.0).astype(np.uint8) - return Image.fromarray(rgba, mode="RGBA")