mcpField
W
webgl-3d-object

webgl-3d-object

github.com/mengto设计与 UI7.1k1.6k 次安装3 天前更新
代码仓库

关于 webgl-3d-object

面向网页设计的技能,指导使用 Three.js 在布局中呈现真实的 3D 物体,涵盖几何体、材质、光照、动画与清理。

主要功能

  1. 01Three.js 几何体与透视相机
  2. 02PBR 材质和光照设置
  3. 03轻微变换动画
  4. 04尺寸调整与清理指导

适用场景

  1. 01为主视觉区域添加 3D 物体
  2. 02展示多面体产品特征
  3. 03制作带真实光照的悬浮物体

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mengto/skills --skill webgl-3d-object

安装后在 Claude Code 中输入/webgl-3d-object确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

demo/
index.html
PROMPT.md
source.json

详细介绍

WebGL 3D 对象

适用场景

  • 主视觉、功能区块或产品展示需要一个醒目的 3D 对象。
  • 视觉效果应展现真实的几何形体、光照、高光和边缘。
  • 多面体网格应在网页布局中轻微悬浮或旋转。
  • CSS 变换、SVG 视觉效果或纯色渐变无法满足需求。

规则

  1. 使用真实的 3D 几何体:IcosahedronGeometry、DodecahedronGeometry、BoxGeometry、自定义 BufferGeometry 或 glTF 网格。
  2. 使用透视相机,让对象呈现深度和比例感。
  3. 使用 PBR 材质:MeshStandardMaterial 或 MeshPhysicalMaterial。
  4. 调整 metalness、roughness 和 emissive,以匹配品牌氛围。
  5. 至少使用一个方向光,并搭配环境光或半球光进行补光。
  6. 仅为变换添加动画:轻微旋转、上下浮动或视差。
  7. 处理窗口大小调整,并在销毁时释放几何体、材质和渲染器。

HTML 和 CSS

<div class="webgl-object-shell">
  <canvas class="webgl-object-canvas" data-webgl-3d-object></canvas>
</div>
.webgl-object-shell {
  position: relative;
  width: min(100%, 720px);
  aspect-ratio: 1 / 1;
}

.webgl-object-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

Three.js 对象配置示例

import * as THREE from "three";

function initWebGL3DObject(canvas, options = {}) {
  if (!canvas) return () => {};

  const renderer = new THREE.WebGLRenderer({
    canvas,
    antialias: true,
    alpha: true,
  });
  renderer.setClearColor(0x000000, 0);
  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, options.maxDpr || 1.75));
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = options.exposure || 1.05;
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  const scene = new THREE.Scene();
  const camera = new THREE.PerspectiveCamera(38, 1, 0.1, 100);
  camera.position.set(0, 0.15, 5.2);

  const geometry = new THREE.IcosahedronGeometry(options.radius || 1.35, options.detail || 1);
  const material = new THREE.MeshStandardMaterial({
    color: options.color || 0x8aa4ff,
    metalness: options.metalness ?? 0.48,
    roughness: options.roughness ?? 0.34,
    emissive: options.emissive || 0x101833,
    emissiveIntensity: options.emissiveIntensity ?? 0.22,
    flatShading: true,
  });

  const object = new THREE.Mesh(geometry, material);
  object.castShadow = true;
  object.receiveShadow = true;
  scene.add(object);

  const ambient = new THREE.AmbientLight(0xffffff, 0.38);
  scene.add(ambient);

  const key = new THREE.DirectionalLight(0xffffff, 2.15);
  key.position.set(3.4, 4.2, 4.8);
  key.castShadow = true;
  key.shadow.mapSize.set(1024, 1024);
  scene.add(key);

  const rim = new THREE.DirectionalLight(options.rimColor || 0x7dd3fc, 0.82);
  rim.position.set(-4.2, 1.2, -2.8);
  scene.add(rim);

  const shadowPlane = new THREE.Mesh(
    new THREE.PlaneGeometry(5.2, 5.2),
    new THREE.ShadowMaterial({ opacity: 0.18 })
  );
  shadowPlane.position.set(0, -1.65, 0);
  shadowPlane.rotation.x = -Math.PI / 2;
  shadowPlane.receiveShadow = true;
  scene.add(shadowPlane);

  const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
  let rafId = 0;

  function resize() {
    const width = Math.max(1, canvas.clientWidth);
    const height = Math.max(1, canvas.clientHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, options.maxDpr || 1.75));
    renderer.setSize(width, height, false);
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }

  function render(time = 0) {
    const t = time * 0.001;
    object.rotation.x = -0.16 + Math.sin(t * 0.45) * 0.06;
    object.rotation.y = t * 0.28;
    object.rotation.z = Math.sin(t * 0.32) * 0.08;
    object.position.y = reduceMotion ? 0 : Math.sin(t * 0.8) * 0.08;

    renderer.render(scene, camera);
    if (!reduceMotion) rafId = requestAnimationFrame(render);
  }

  function handleResize() {
    cancelAnimationFrame(rafId);
    resize();
    render();
  }

  resize();
  render();
  window.addEventListener("resize", handleResize);

  return () => {
    cancelAnimationFrame(rafId);
    window.removeEventListener("resize", handleResize);
    geometry.dispose();
    material.dispose();
    shadowPlane.geometry.dispose();
    shadowPlane.material.dispose();
    renderer.dispose();
  };
}

const cleanupObject = initWebGL3DObject(
  document.querySelector("[data-webgl-3d-object]"),
  {
    color: 0x8aa4ff,
    rimColor: 0x7dd3fc,
    metalness: 0.48,
    roughness: 0.34,
    emissive: 0x101833,
  }
);

材质默认值

  • 高级金属质感:metalness: 0.45-0.7、roughness: 0.25-0.45。
  • 柔和陶瓷质感:metalness: 0.0-0.15、roughness: 0.38-0.62。
  • 带光晕色调的科技对象:使用较低的 emissive,并设置 emissiveIntensity: 0.12-0.35。
  • 多面体对象:设置 flatShading: true;平滑产品对象:设置为 false。

光照默认值

  • 主光:方向光,位于前侧上方,且为最强光源。
  • 环境补光:强度较低,确保阴影仍然清晰可见。
  • 轮廓光:从后方照射的品牌色调光或冷色光,用来凸显边缘。
  • 阴影:仅在对象需要落地感时启用;阴影贴图尺寸保持适中。

动效默认值

  • 旋转:缓慢、持续,且不喧宾夺主。
  • 浮动:Y 轴方向移动 0.04 到 0.12 个单位。
  • 减少动态效果:渲染静止帧,或仅允许直接交互。
  • 除非对象是主要交互元素,否则避免移动相机。

避免

  • 用 CSS 3D 变换伪装 WebGL 效果。
  • 在需求强调真实光照和深度时使用无光照材质。
  • 用带渐变的平面代替真实几何体。
  • 使用过强的泛光或粒子效果,以免掩盖对象形态。
  • 在移动设备上使用过高的 DPR、过大的阴影贴图或过多的灯光。
  • 让对象与前景文案或 CTA 争夺注意力。

快速检查

  • 对象具有清晰可见的形态、边缘、高光和阴影。
  • 材质使用了 metalness、roughness 和可选的 emissive。
  • 同时设置了方向光和环境光。
  • 相机使用透视投影,而非误用正交投影。
  • 调整窗口大小时,对象不会变形。
  • 已清理几何体、材质、事件监听器、RAF 和渲染器。

评论

支持 Markdown