diff --git a/1.png b/1.png new file mode 100644 index 0000000..349983f Binary files /dev/null and b/1.png differ diff --git a/konabot/plugins/sksl/marchtoy/command.py b/konabot/plugins/sksl/marchtoy/command.py index fd59ce0..5982b47 100644 --- a/konabot/plugins/sksl/marchtoy/command.py +++ b/konabot/plugins/sksl/marchtoy/command.py @@ -116,7 +116,6 @@ class Scene: else: raise Exception(f"{op_id} is not a valid operation.") - # 判断是否可以绘制,有点蹩脚但是省事 try: sdf_block = obj_instance.sdf_block() self.canvas_objs.append((obj_instance, sdf_block)) @@ -128,33 +127,36 @@ class Scene: return ", ".join([str(type(obj)) for obj in self.canvas_objs]) def compile(self) -> str: - path = ( - pathlib.Path("konabot") - / "plugins" - / "sksl" - / "marchtoy" - / "shaders" - / "raymarch.sksl" - ) - with path.open(encoding="utf-8") as f: + PATH = pathlib.Path(__file__).parent / "shaders" / "raymarch.sksl" + with PATH.open(encoding="utf-8") as f: content = f.read() sdf_block: str = "" + color_block: str = "" index = 0 for canvas_item in self.canvas_objs: - sdf_block += f"float sd{index} = {canvas_item[1]};" - sdf_block += f"if(qry.value < sd{index})" + obj, sdf_expr = canvas_item + sdf_block += f"float sd{index} = {sdf_expr};" + sdf_block += f"if(sd{index} < qry.value)" sdf_block += "{" + f"qry.value = sd{index}; qry.obj_id = {index}; " + "}\n" + color = obj.texture.color + color_block += ( + f"if(obj_id == {index}) return float4(" + f"{color[0]}, {color[1]}, {color[2]}, {color[3]});\n" + ) index += 1 content = content.replace("", sdf_block) - + content = content.replace("", color_block) + cam_pos = self.camera.transform.t[0:3, 3] cam_focus = self.camera.focus cam_dir = self.camera.transform.t @ np.array((0.0, 0.0, -1.0, 0.0)) - content = content.replace("", f"{cam_pos[0]}, {cam_pos[1]}, {cam_pos[2]}") + content = content.replace( + "", f"{cam_pos[0]}, {cam_pos[1]}, {cam_pos[2]}" + ) content = content.replace("", str(cam_focus)) - content = content.replace("", f"{cam_dir[0]}, {cam_dir[1]}, {cam_dir[2]}") + content = content.replace( + "", f"{cam_dir[0]}, {cam_dir[1]}, {cam_dir[2]}" + ) return content - - diff --git a/konabot/plugins/sksl/marchtoy/compiler.py b/konabot/plugins/sksl/marchtoy/compiler.py deleted file mode 100644 index f435aad..0000000 --- a/konabot/plugins/sksl/marchtoy/compiler.py +++ /dev/null @@ -1,33 +0,0 @@ -from pathlib import Path - -from .obj import Scene -from .utilities import format_float, format_float3 - - -TEMPLATE_PATH = Path(__file__).parent / "passes" / "pass1.sksl" -TEMPLATE = TEMPLATE_PATH.read_text("utf-8") - - -def compile_scene_to_shader(scene: Scene) -> str: - object_blocks = "\n".join( - obj.compile_block(index) - for index, obj in enumerate(scene.objects, start=1) - ) - - return ( - TEMPLATE.replace("/**/", object_blocks) - .replace("/**/", ", ".join(format_float(v) for v in scene.camera.position)) - .replace( - "/**/", - ", ".join(format_float(v) for v in scene.camera.target), - ) - .replace("/**/", format_float(scene.camera.fov)) - .replace( - "/**/", - ", ".join(format_float(v) for v in scene.light.direction), - ) - .replace( - "/**/", - format_float3((0.454545, 0.454545, 0.454545)), - ) - ) diff --git a/konabot/plugins/sksl/marchtoy/obj.py b/konabot/plugins/sksl/marchtoy/obj.py index 3a35325..6cba20f 100644 --- a/konabot/plugins/sksl/marchtoy/obj.py +++ b/konabot/plugins/sksl/marchtoy/obj.py @@ -9,7 +9,7 @@ def make_obj(name: str, aliases: list[str] = []): def decorator(cls): OBJECT_ENTRIES[name] = cls for alias in aliases: - OBJECT_ENTRIES[id] = cls + OBJECT_ENTRIES[alias] = cls return cls return decorator @@ -62,12 +62,13 @@ class Transform: ): p = self.t[0:3, 3] q = np.array([x, y, z]) - yaxis = up zaxis = Transform.normalize(q - p) - xaxis = Transform.normalize(np.cross(zaxis, yaxis)) + xaxis = Transform.normalize(np.cross(zaxis, up)) yaxis = Transform.normalize(np.cross(xaxis, zaxis)) - V = np.array([xaxis, yaxis, zaxis]) - self.t[:3, :3] = V + # 约定本地 -Z 为“朝前”,这样和 shader 中使用的相机射线方向保持一致。 + self.t[:3, 0] = xaxis + self.t[:3, 1] = yaxis + self.t[:3, 2] = -zaxis return self def p_expr(self) -> str: @@ -113,7 +114,7 @@ class Sphere(Object): return f"sdSphere({self.transform.p_expr()}, {self.radius})" -@make_obj("camera") +@make_obj("camera", ["cam"]) class Camera(Object): def __init__(self, _focus: float = 1.0) -> None: super().__init__() diff --git a/konabot/plugins/sksl/marchtoy/op.py b/konabot/plugins/sksl/marchtoy/op.py index f28d896..cfea952 100644 --- a/konabot/plugins/sksl/marchtoy/op.py +++ b/konabot/plugins/sksl/marchtoy/op.py @@ -36,12 +36,17 @@ def lookat(obj: Object, args: list[str]): @make_operation("color", ["col", "texture"]) def color(obj: Object, args: list[str]): try: - if len(args) == 3: + if len(args) == 1: + col = ArgParser.as_literal_color(args) + obj.texture.color = (col[0], col[1], col[2], col[3]) + elif len(args) == 3: col = ArgParser.as_vec3(args) obj.texture.color = (col[0], col[1], col[2], 1.0) elif len(args) == 4: col = ArgParser.as_vec4(args) - obj.texture.color = (col[0], col[1], col[2], col[4]) + obj.texture.color = (col[0], col[1], col[2], col[3]) + else: + raise Exception("unknown color") except: raise Exception("unknown color") diff --git a/konabot/plugins/sksl/marchtoy/shaders/raymarch.sksl b/konabot/plugins/sksl/marchtoy/shaders/raymarch.sksl index 9614df0..ec85341 100644 --- a/konabot/plugins/sksl/marchtoy/shaders/raymarch.sksl +++ b/konabot/plugins/sksl/marchtoy/shaders/raymarch.sksl @@ -1,12 +1,11 @@ -uniform float u_time; uniform float2 u_resolution; const float EPS = 0.001; -const float MAX_ITER = 64; +const int MAX_ITER = 64; struct sdQuery { float value; int obj_id; -} +}; float sdCube(float3 p, float3 b) { p = abs(p) - b; @@ -14,36 +13,46 @@ float sdCube(float3 p, float3 b) { } float sdSphere(float3 p, float r) { - return length(p) - r + return length(p) - r; } sdQuery sd(float3 p) { sdQuery qry; qry.value = 100000000.0; + qry.obj_id = -1; + return qry; } float3 nrm(float3 p) { float2 d = float2(EPS, 0.0); - return normalize(float4( - sd(p + d.xyy) - sd(p), - sd(p + d.yxy) - sd(p), - sd(p + d.yyx) - sd(p), + return normalize(float3( + sd(p + d.xyy).value - sd(p - d.xyy).value, + sd(p + d.yxy).value - sd(p - d.yxy).value, + sd(p + d.yyx).value - sd(p - d.yyx).value )); } -float4 color(float3 p) { - - return float4(nrm(p), 1.0); +float4 materialColor(int obj_id) { + + return float4(1.0); +} + +float4 color(float3 p, int obj_id) { + float3 normal = nrm(p); + float3 light_dir = normalize(float3(0.5, 0.8, -0.6)); + float light = 0.2 + 0.8 * max(dot(normal, light_dir), 0.0); + float4 base = materialColor(obj_id); + return float4(base.rgb * light, base.a); } float4 march(float3 p, float3 r) { sdQuery qry; - float4 col; + float4 col = float4(0.0); for(int i = 0; i < MAX_ITER; ++i) { qry = sd(p); if(qry.value < EPS){ - col = color(p); + col = color(p, qry.obj_id); break; } p += qry.value * r; @@ -55,10 +64,10 @@ half4 main(float2 fragCoord) { float2 uv = -2. * (fragCoord / u_resolution - .5) * float2(u_resolution.x / u_resolution.y, 1.); float3 c_p = float3(); float3 c_z = normalize(float3()); - float3 c_y = float3(0., 1., 0.); - float3 c_x = normalize(cross(c_z, c_y)); + float3 world_up = abs(c_z.y) > 0.999 ? float3(0., 0., 1.) : float3(0., 1., 0.); + float3 c_x = normalize(cross(c_z, world_up)); + float3 c_y = normalize(cross(c_x, c_z)); float3x3 view = float3x3(c_x, c_y, c_z); - float3 r = normalize(float3(u, v, )); + float3 r = normalize(float3(uv, )); return half4(march(c_p, view * r)); } - diff --git a/konabot/plugins/sksl/marchtoy/test.py b/konabot/plugins/sksl/marchtoy/test.py new file mode 100644 index 0000000..8c45091 --- /dev/null +++ b/konabot/plugins/sksl/marchtoy/test.py @@ -0,0 +1,39 @@ +from command import Scene +import skia +import struct +from PIL import Image +import numpy as np + +# 暂时先照抄小帕的了,之后有空再单独封装一下 + +width = 480 +height = 480 +scene = Scene("sphere(1).pos(0, 0, 0) camera(4.0).pos(1).lookat(0.0)") +surface = skia.Surface(width, height) +sksl_code = scene.compile() + +runtime_effect = skia.RuntimeEffect.MakeForShader(scene.compile()) +if runtime_effect is None: + raise Exception("cannot compile sksl shader") + +uv_uniform = struct.pack("ff", float(width), float(height)) +uniform_data = skia.Data.MakeWithCopy(uv_uniform) +shader = runtime_effect.makeShader(uniform_data, None, 0) +canvas = surface.getCanvas() +canvas.clear(skia.Color(0, 0, 0, 0)) +paint = skia.Paint() +paint.setShader(shader) +canvas.drawRect(skia.Rect.MakeWH(width, height), paint) +image = surface.makeImageSnapshot() +target_info = skia.ImageInfo.Make( + image.width(), + image.height(), + skia.ColorType.kBGRA_8888_ColorType, + skia.AlphaType.kPremul_AlphaType, +) +pixel_data = bytearray(image.width() * image.height() * 4) # 4 bytes per pixel (BGRA) +success = image.readPixels(target_info, pixel_data, target_info.minRowBytes()) +img_array = np.frombuffer(pixel_data, dtype=np.uint8).reshape((height, width, 4)) +rgb_array = img_array[:, :, [2, 1, 0]] +pil_img = Image.fromarray(rgb_array) +pil_img.save("1.png") \ No newline at end of file diff --git a/konabot/plugins/sksl/marchtoy/utilities.py b/konabot/plugins/sksl/marchtoy/utilities.py index 1e746c0..ed2ed55 100644 --- a/konabot/plugins/sksl/marchtoy/utilities.py +++ b/konabot/plugins/sksl/marchtoy/utilities.py @@ -13,7 +13,9 @@ class SkslFormatter: v_3 = ", ".join([str(x) for x in m[3]]) return f"float4x4(float4({v_0}), float4({v_1}), float4({v_2}), float4({v_3}))" - +""" +TODO: 除零出现 nan 情况的单独处理 +""" class ArgParser: @staticmethod def as_float(args: list[str], default: float = 0.0) -> float: @@ -49,8 +51,8 @@ class ArgParser: try: if len(args) == 1: x = float(args[0]) - return np.array((x, x, x)) - elif len(args) == 3: + return np.array((x, x, x, x)) + elif len(args) == 4: x = float(args[0]) y = float(args[1]) z = float(args[2])