manual 补充; cpu 写法速度不可接受
This commit is contained in:
@ -14,5 +14,23 @@
|
|||||||
|
|
||||||
其中 `obj_ty`、`op_ty` 分别为物体类型(如 `cube`、`sphere`、`torus` 等)与变换类型(如 `pos`、`rot`)。
|
其中 `obj_ty`、`op_ty` 分别为物体类型(如 `cube`、`sphere`、`torus` 等)与变换类型(如 `pos`、`rot`)。
|
||||||
|
|
||||||
|
# 支持的物体
|
||||||
|
目前支持的物体有(不包含 alias):
|
||||||
|
`cube`:可选参数长宽高
|
||||||
|
`sphere`:可选参数半径
|
||||||
|
`torus`:可选参数半径与粗细
|
||||||
|
`cylinder`:可选参数半径与高度
|
||||||
|
`capsule`:可选参数高度与半径
|
||||||
|
|
||||||
|
特殊物体:
|
||||||
|
`camera`:可选参数焦距
|
||||||
|
|
||||||
|
# 支持的变换
|
||||||
|
目前支持的变换有
|
||||||
|
`pos`
|
||||||
|
`rot`
|
||||||
|
`color`
|
||||||
|
`lookat`
|
||||||
|
|
||||||
# 特殊说明
|
# 特殊说明
|
||||||
`<op_ty>` 不包含 scale。非正交的变换会破坏 SDF 的性质。
|
`<op_ty>` 不包含 scale。非正交的变换会破坏 SDF 的性质。
|
||||||
@ -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")
|
|
||||||
Reference in New Issue
Block a user