Show HN: 一个 CSS 3D 引擎(无 WebGL)
Show HN: A CSS 3D Engine (no WebGL)

原始链接: https://github.com/LayoutitStudio/polycss

PolyCSS 是一个用于 DOM 的 3D 引擎,它通过 CSS `matrix3d()` 将多边形网格渲染为原生 HTML 元素。它使开发者能够使用 React、Vue 或原生 JavaScript 将 3D 模型(OBJ、MTL、GLB、VOX)和几何体直接集成到 Web 界面中。 **主要特性:** * **渲染:** 支持光照、阴影、纹理和动画。 * **灵活性:** 提供用于相机、场景和交互式控件(轨道、地图、第一人称)的组件。 * **可访问性:** 通过渲染为 DOM 元素,多边形支持标准事件处理(例如 `onClick`),从而实现交互式 3D UI 组件。 * **性能:** 使用智能 CSS 原语——将实体形状优化为简单的盒子或裁剪,并为复杂几何体使用纹理图集。 * **可导出性:** 包含一个 `exportPolySceneSnapshot` 工具,可从渲染场景中生成独立的 HTML 文件。 PolyCSS 采用模块化设计,提供核心逻辑、原生自定义元素及特定框架的包。它基于 MIT 协议开源,非常适合在 Web 应用中添加高性能的 CSS 原生 3D 图形。如需查看文档、示例和安装说明,请访问 [polycss.com](https://polycss.com)。

最近 Hacker News 的一场讨论引起了对 **PolyCSS** 的关注,这是一个使用 CSS 变换(CSS transforms)而非 WebGL 来渲染 3D 场景的 3D 引擎。 尽管批评者认为 WebGL 在效率和性能上明显更胜一筹,但该项目的创建者解释称,该引擎旨在用于小型、低多边形或基于体素的场景,对于这些场景而言,使用完整的 3D 库显得大材小用。通过利用 DOM 和 CSS,该引擎能够与标准的网页样式、布局以及原生事件处理无缝集成——例如在 3D 面上触发简单的 `onClick`,这在 WebGL 中通常需要复杂的射线检测(raycasting)才能实现。 社区指出,这种方法实现了“零 JavaScript”渲染,即仅通过 HTML 和 CSS 即可完成 3D 模型渲染甚至动态摄像机运动。尽管用户承认其性能局限性——将其比作“在 Excel 单元格中运行《毁灭战士》”——但普遍的共识是,这是一项引人入胜的技术实验。它最适合轻量级、交互式的 Web 组件,在这些场景中,原生 DOM 集成比原始渲染速度更有价值。
相关文章

原文

A CSS polygon mesh library. A 3D engine for the DOM. Renders OBJ/MTL, GLB and VOX as real HTML elements transformed with CSS matrix3d(...). Supports colors, textures, lighting, shadows, shapes and animations. Works with React, Vue or plain JavaScript.

Visit polycss.com for docs and model examples.

PolyCSS primitives banner
# Vanilla
npm install @layoutit/polycss

# React
npm install @layoutit/polycss-react

# Vue
npm install @layoutit/polycss-vue

You can also load PolyCSS directly from a CDN. Here is a minimal custom-element scene:

<script type="module" src="https://esm.sh/@layoutit/polycss/elements"></script>

<poly-camera rot-x="65" rot-y="45">
  <poly-scene>
    <poly-orbit-controls drag wheel></poly-orbit-controls>
    <poly-box size="100" color="#ffd166"></poly-box>
  </poly-scene>
</poly-camera>
PolyCSS intro

React and Vue expose the same component model. <PolyCamera> owns the viewpoint, <PolyScene> owns lighting and atlas options, and <PolyMesh> loads or receives polygon data.

import { PolyCamera, PolyScene, PolyOrbitControls, PolyMesh } from "@layoutit/polycss-react";

export default function App() {
  return (
    <PolyCamera rotX={65} rotY={45}>
      <PolyScene textureLighting="dynamic">
        <PolyOrbitControls drag wheel />
        <PolyMesh src="/gallery/obj/cottage.obj" mtl="/gallery/obj/cottage.mtl" />
      </PolyScene>
    </PolyCamera>
  );
}
  • rotX, rotY control the orbit angle in degrees.
  • zoom scales the projected scene.
  • target pans the camera target in world coordinates.
  • distance adds dolly pull-back.
  • PolyCamera is the orthographic default. Use PolyPerspectiveCamera when you want perspective depth.
  • polygons renders a static Polygon[] directly.
  • directionalLight and ambientLight control scene lighting.
  • textureLighting chooses "baked" or "dynamic".
  • textureQuality controls atlas raster budget.
  • strategies can disable selected render strategies for diagnostics.
  • autoCenter rotates around the rendered mesh bounds instead of world origin.
  • src loads .obj, .gltf, .glb, or .vox files.
  • mtl loads companion OBJ materials.
  • polygons accepts pre-parsed geometry.
  • position, scale, and rotation transform the mesh wrapper.
  • autoCenter shifts the mesh bbox center to local origin.
  • meshResolution chooses "lossy" (default) or "lossless" optimization.
  • castShadow emits CSS-projected shadows in dynamic lighting mode.
  • <PolyOrbitControls> adds drag orbit, shift-drag pan, wheel zoom, and optional auto-rotate.
  • <PolyMapControls> uses pan-first map-style input.
  • <PolyFirstPersonControls> provides keyboard and pointer-look navigation.
  • <PolyTransformControls> adds translate/rotate gizmos for selected mesh handles.

The vanilla package exports exportPolySceneSnapshot(target). It clones the current rendered .polycss-camera / .polycss-scene DOM, injects only the PolyCSS CSS needed by that snapshot, inlines CSS url(...) image assets as data:image/...;base64,..., strips scripts and inline event handlers, and returns a standalone HTML document string with no PolyCSS runtime import. It works with rendered React/Vue scenes too; import it from @layoutit/polycss and pass the rendered camera or scene element.

import { exportPolySceneSnapshot } from "@layoutit/polycss";

const html = await exportPolySceneSnapshot(scene.host);

If any referenced asset cannot be inlined, the function throws PolySceneSnapshotError with code: "ASSET_INLINE_FAILED".

Each polygon describes one renderable face:

const polygons = [
  {
    vertices: [[0, 0, 0], [60, 0, 0], [0, 60, 0]],
    color: "#f97316",
  },
  {
    vertices: [[0, 0, 0], [60, 0, 0], [60, 60, 0], [0, 60, 0]],
    texture: "/texture.png",
    uvs: [[0, 0], [1, 0], [1, 1], [0, 1]],
  },
];

Render polygons directly when you need per-face DOM events or custom styling:

<PolyCamera>
  <PolyScene>
    {polygons.map((polygon, index) => (
      <Poly
        key={index}
        {...polygon}
        onClick={() => console.log("clicked polygon", index)}
        className="my-polygon"
      />
    ))}
  </PolyScene>
</PolyCamera>

Use loadMesh() to parse supported model formats:

import { createPolyCamera, createPolyScene, loadMesh } from "@layoutit/polycss";

const host = document.getElementById("polycss")!;
const camera = createPolyCamera({ rotX: 65, rotY: 45 });
const scene = createPolyScene(host, { camera });

const mesh = await loadMesh("https://polycss.com/gallery/obj/cottage.obj", {
  mtlUrl: "https://polycss.com/gallery/obj/cottage.mtl",
});

scene.add(mesh);

Supported formats:

  • OBJ + MTL, including map_Kd textures and UV coordinates.
  • glTF / GLB, including embedded images and TEXCOORD_0.
  • MagicaVoxel .vox, with direct voxel fast paths when eligible.
  • Generated primitives: box, plane, ring, sphere, torus, cylinder, cone, and Platonic solids.

PolyCSS renders through the DOM, so performance is mostly shaped by two things: the number of mounted leaves, and the amount of texture atlas area the browser has to paint. The renderer tries to keep the common cases cheap. Simple surfaces stay as solid CSS elements, while textured, irregular, or high-detail geometry falls back to atlas-backed slices only when needed.

Each visible polygon is emitted as one leaf element; the renderer chooses the least expensive CSS primitive that can represent the polygon, then uses matrix3d(...) to place that primitive in 3D space.

  • <b> uses background: currentColor on a fixed box for solid rectangles and stable quads.
  • <u> uses corner-shape for stable triangles and beveled-corner solids, with a border-width triangle fallback when needed.
  • <i> clips solid polygons with border-shape: polygon(...) when the browser supports it.
  • <s> maps a packed texture-atlas slice with background-image, and is the fallback for textured or unsupported shapes.
Package Description
@layoutit/polycss-core Pure math, parsers, lighting, camera helpers, mesh optimization. Zero browser globals.
@layoutit/polycss Vanilla custom elements and imperative createPolyScene API.
@layoutit/polycss-react React components, hooks, controls, and core re-exports.
@layoutit/polycss-vue Vue 3 components, composables, controls, and core re-exports.

Layoutit Voxels -> A CSS Voxel editor

layoutit-voxels

Layoutit Terra -> A CSS Terrain Generator

layoutit-terra

MIT.

联系我们 contact @ memedata.com