返回归档
GitHub
NotesApr 15, 202618 分钟阅读

Dappled Light(斑驳光影) —— 完整深度技术拆解

最近看到一个很舒服的网页特效,一开始以为是视频帧动画,点开源码后发现纯代码实现的,没有任何一张图片甚至图标,感觉非常神奇。于是认真去研究了一下这个相关的资料,在此对整理过程的学习和研究做一些沉淀,内容比较干,不感兴趣可以跳过内容到开源使用。

Dappled Light(斑驳光影) —— 完整深度技术拆解-1776181166272

最近看到一个很舒服的网页特效,一开始以为是视频帧动画,点开源码后发现纯代码实现的,没有任何一张图片甚至图标,感觉非常神奇。于是认真去研究了一下这个相关的资料,在此对整理过程的学习和研究做一些沉淀,内容比较干,不感兴趣可以跳过内容到开源使用。


你上面首页看到的效果,动效名称叫:Dappled Light(斑驳光影)——阳光穿过树叶、百叶窗等遮挡物,在墙面和桌面上形成的那种柔和的、斑驳的光影图案。

这个效果在自然界中极其常见,但要在网页上模拟出来,技术路径却非常多样。我梳理了从 2022 年至今所有公开的实现方案,发现它们大致可以按复杂度分成三个梯队:

  • CSS 层:用 mix-blend-modetext-shadowbackdrop-filter 等纯 CSS 属性模拟光影叠加,零 JS,性能一般
  • SVG / Canvas 层:用 SVG 滤镜链(feTurbulence + feDisplacementMap)做有机运动,或用 Canvas 2D 的 globalCompositeOperation 做多层离屏合成
  • WebGL / 3D 层:在真实的 3D 场景中做 Shadow Mapping,用自定义 Fragment Shader 计算距离感知的软阴影

每个梯队都有人做出了很好的作品。下面按实现方案逐一拆解,你可以根据自己的场景选择合适的方案。


一、纯 CSS 方案:用 Emoji + Blend Mode 造光斑

这是能追溯到的最早的 Web 端 Dappled Light 实现,2022 年初发表在 CSS-Tricks 上。作者的核心思路极其聪明:用 Emoji 字符当"光斑形状",用 CSS text-shadow 当"光源"

原理

<div class="backdrop">
  <p class="shapes">🍃</p>
  <p class="shapes">🍂</p>
  <p class="shapes">\</p>
</div>
.shapes {
  color: transparent;                    /* Emoji 本身不显示 */
  background-color: rgba(0, 0, 0, 0.3); /* 半透明黑底 = 阴影层 */
  text-shadow: 0 0 40px #fff;           /* 超大模糊白色阴影 = 光斑 */
  font: bolder 320pt/320pt monospace;   /* 巨大字号占满容器 */
  mix-blend-mode: multiply;             /* 关键:让亮色变透明,暗色保留 */
}

拆解每一步:

  1. Emoji 被设置 color: transparent,自身不可见
  2. text-shadow: 0 0 40px #fff 在 Emoji 轮廓周围产生一个巨大的白色模糊光晕,这就是"穿过树叶的光"
  3. background-color: rgba(0,0,0,0.3) 提供一层均匀的半透明暗色,这是"被遮挡的阴影区域"
  4. mix-blend-mode: multiply 做最后的合成:白色区域(光斑)与底图相乘后保持原样(白×任何色=原色),深色区域使底图变暗

动画也极简,用 @keyframes 移动 text-indent,让 Emoji 缓慢水平位移,模拟云层飘动导致的光线角度变化。

局限性

  • Emoji 渲染跨浏览器不一致,Chrome 后来的版本渲染效果有退化
  • 形状不够自然,能明显看出是文字轮廓
  • 无法精细控制光斑的分布和密度

但这个方案的价值在于:零 JS、零图片、不到 20 行 CSS,证明了 Dappled Light 在 Web 上是可行的。

在线预览新窗口打开

在线 Demo: 01-css-emoji-blend.html — 纯 CSS 实现,双击打开即可预览,四层 Emoji 以不同速度缓慢漂移。


二、SVG + CSS Animation 方案:从 Hack 到设计级品质

2024 年出现了一个叫 sunlit.place 的小网站,把 Dappled Light 做到了一个新的精致度。页面很简单,一面白墙,百叶窗的条纹光影,窗外树叶的斑驳投影。但每一帧都像一幅静物画。

和纯 CSS 方案不同,这里用了真实的 SVG 图形代替 Emoji。树叶剪影是一张精心绘制的 SVG/PNG,而非字符。百叶窗不是 DOM 元素循环,而是一个整体的 SVG 图案。配合 CSS animation 做缓慢的摇曳和透明度变化。

这个方案的核心突破是:用专业的视觉素材替代了 hack 式的 Emoji,让效果从"技术演示"变成了"设计作品"。

在线预览新窗口打开

在线 Demo: 02-svg-css-animation.html — 用 SVG path 绘制叶片剪影,配合百叶窗条纹和 CSS 缓动动画。


三、CSS + SVG Filter 工程方案:渐进模糊 + 风力动画 + 透视变换

还有一种方案来自开源项目 Sunlit(882 stars,在线 Demo),它受 Daylight Computer 和 sunlit.place 启发,做了一个完整的工程化实现。这是目前技术含量最高、文档最全的纯 CSS 方案。

架构分层

整个效果由 5 个独立的视觉层 叠加组成:

┌──────────────────────────────────────────┐
│ Layer 5: Leaf Billowing                  │  ← 树叶剪影 + SVG 风力动画
│   PNG 图片 + feTurbulence 滤镜            │
├──────────────────────────────────────────┤
│ Layer 4: Progressive Blur               │  ← 渐进式模糊(近清远糊)
│   4 层 backdrop-filter + mask-image      │
├──────────────────────────────────────────┤
│ Layer 3: Window Blinds                  │  ← 百叶窗条纹
│   多个 div.shutter + 透明度渐变           │
├──────────────────────────────────────────┤
│ Layer 2: Window Frame + 3D Transform    │  ← 窗框 + 透视变换
│   matrix3d() 制造倾斜投影角度             │
├──────────────────────────────────────────┤
│ Layer 1: Background / Page Content      │  ← 页面原有内容
└──────────────────────────────────────────┘

这个方案有三个值得深挖的技术细节:渐进式模糊如何模拟物理距离、SVG 滤镜如何让树叶"吹动"、以及如何用 matrix3d 制造投影角度。逐一来看。

Progressive Blur(渐进式模糊)

现实中,靠近墙壁的阴影边缘清晰,远离墙壁的阴影边缘模糊。这个方案用 4 层 backdrop-filter: blur() 叠加实现了这个渐变:

#progressive-blur > div {
  position: absolute;
  inset: 0;
  backdrop-filter: blur(var(--blur-amount));
  mask-image: linear-gradient(
    252deg,
    transparent,
    transparent var(--stop1),
    black var(--stop2),
    black
  );
}

/* 从左到右:模糊量递增 */
#progressive-blur > div:nth-child(1) { --blur-amount: 6px;  --stop1: 0%;  --stop2: 0%;  }
#progressive-blur > div:nth-child(2) { --blur-amount: 24px; --stop1: 30%; --stop2: 40%; }
#progressive-blur > div:nth-child(3) { --blur-amount: 48px; --stop1: 50%; --stop2: 60%; }
#progressive-blur > div:nth-child(4) { --blur-amount: 96px; --stop1: 70%; --stop2: 80%; }

每一层的 mask-imagelinear-gradient 控制该层在哪个空间范围内生效。最终合成后:画面左侧(靠近窗户 = 靠近投影物)模糊 6px,右侧(远离窗户 = 远离投影物)模糊 96px。这比单一 blur() 值的效果自然得多。

SVG 风力动画(feTurbulence + feDisplacementMap)

让树叶产生风吹效果,全程零 JS:

<svg style="width: 0; height: 0; position: absolute;">
  <defs>
    <filter id="wind" x="-20%" y="-20%" width="140%" height="140%">
      <feTurbulence type="fractalNoise" numOctaves="2" seed="1">
        <animate
          attributeName="baseFrequency"
          dur="16s"
          keyTimes="0;0.33;0.66;1"
          values="0.005 0.003;0.01 0.009;0.008 0.004;0.005 0.003"
          repeatCount="indefinite"
        />
      </feTurbulence>
      <feDisplacementMap
        in="SourceGraphic"
        scale="10"
        xChannelSelector="R"
        yChannelSelector="G"
      />
    </filter>
  </defs>
</svg>

原理拆解:

  1. feTurbulence 生成一个 Perlin 分形噪声场(fractalNoise)。numOctaves="2" 表示两层叠加的噪声,频率不同
  2. baseFrequency 通过 <animate> 在 16 秒内缓慢变化,噪声场本身在"呼吸"
  3. feDisplacementMap 拿这个噪声场当偏移映射:R 通道的值控制 X 方向偏移,G 通道控制 Y 方向偏移,scale="10" 控制最大偏移像素数
  4. 最终效果:树叶图片的每个像素都被噪声场轻微偏移,产生有机的风吹波动

配合 CSS @keyframes billow 做整体的缓慢旋转和缩放:

@keyframes billow {
  0%   { transform: perspective(400px) rotateX(0deg) rotateY(0deg) scale(1); }
  33%  { transform: perspective(400px) rotateX(-0.5deg) rotateY(1.2deg) scale(1.02); }
  66%  { transform: perspective(400px) rotateX(0.3deg) rotateY(-0.8deg) scale(0.99); }
  100% { transform: perspective(400px) rotateX(0deg) rotateY(0deg) scale(1); }
}

宏观上整棵树在轻微摇晃(CSS keyframes),微观上每片叶子在独立颤动(SVG displacement)。两层动画叠加,效果非常真实。

matrix3d 透视变换

百叶窗和窗框不能是正面朝前的,那样看起来像贴纸。需要有一个"光线从斜上方打下来"的角度感。这里用 Python 脚本计算了一个 matrix3d() 变换矩阵:

import numpy as np

def compute_homography(points_src, points_dst):
    # 从4个源点到4个目标点的透视变换矩阵
    # 将正方形窗口映射到梯形投影
    ...

src = np.array([[-1, 0], [0, 0], [0, -1], [-1, -1]]) * 500
dst = np.array([...])  # 目标梯形的四个角点

输出一个 16 个值的 CSS matrix3d(...) 变换,直接应用到窗框容器上,制造出"阳光从左上方 45 度角照射,投影在斜对面墙上"的视觉效果。

在线预览新窗口打开

在线 Demo: 03-css-svg-filter.html — 完整实现 5 层架构:渐进模糊、feTurbulence 风力动画、matrix3d 透视变换,零 JS。


四、L-system + Three.js 方案:程序化生成树影

还有一种非常独特的实现思路(在线 Demo),它不用任何图片素材,树叶剪影完全由算法生成

Lindenmayer System(L-system)是什么

L-system 是 1968 年由匈牙利植物学家 Aristid Lindenmayer 提出的一种字符串重写系统。它的核心思想极简:给定一个初始字符串(公理)和一组替换规则,反复应用规则,字符串就会像植物一样"生长"。

一个经典的分形植物 L-system:

公理:   X
规则:   X → F+[[X]-X]-F[-FX]+X
        F → FF
角度:   25°

符号含义(Turtle Graphics 解释):

  • F = 向前画一段线
  • + = 左转 25°
  • - = 右转 25°
  • [ = 保存当前位置和角度(入栈)
  • ] = 恢复到上次保存的位置和角度(出栈)
  • X = 不画线,纯粹作为递归占位符

迭代 6 次后,一个简单的 X 字符会膨胀成一棵具有复杂分支结构的植物:

第0代: X
第1代: F+[[X]-X]-F[-FX]+X
第2代: FF+[[F+[[X]-X]-F[-FX]+X]-F+[[X]-X]-F[-FX]+X]-FF[-FFF+[[X]-X]-F[-FX]+X]+F+[[X]-X]-F[-FX]+X
...
第6代: (数千个字符的完整分形树)

【图片占位】 prompt: L-system fractal tree generation process, showing 6 iterations from a single line to a full branching tree, white background, minimalist diagram style, green branches with increasing complexity, labeled "iter 0" through "iter 6", technical illustration

在 Dappled Light 中的应用

这个方案用 L-system 在运行时生成树木的几何形状,然后将这些几何体放入 @react-three/fiber 的 3D 场景中,使用 @react-three/drei 的两个关键组件来做阴影:

<AccumulativeShadows temporal frames={100} scale={10}>
  <RandomizedLight
    amount={8}
    frames={100}
    position={[5, 5, -10]}
    castShadow
  />
</AccumulativeShadows>

AccumulativeShadows — 一个水平放置的阴影接收平面。它不做实时阴影计算,而是跨多帧累积光照信息:每帧渲染一次场景的阴影,结果混合到一张 progressive light map(渐进光照贴图)上。100 帧后,光照贴图收敛成一张柔和的软阴影纹理。之后就零性能消耗了,因为只是显示一张静态纹理。

RandomizedLight — 不是一盏灯,而是 8 盏灯在随机位置抖动radius 参数控制抖动范围,amount=8 是灯的数量。每一帧,8 盏灯各自偏移到稍微不同的位置,从略微不同的角度照射场景。100 帧累积后,就像做了 800 次(8灯×100帧)蒙特卡洛光线追踪采样,阴影边缘自然柔和,完全不需要手写模糊 shader。

页面上的 Wind 滑块(XS/M/L)控制的是 L-system 树木的摆动幅度。树枝在风中摇动 → 每帧阴影形状微变 → AccumulativeShadows 持续累积新帧 → 阴影跟着"呼吸"。

与 CSS 方案的本质区别

维度CSS 方案L-system 方案
树叶来源静态 PNG/SVG 素材算法实时生成
阴影计算CSS blend mode 近似真实 3D Shadow Mapping
软化方式backdrop-filter blur蒙特卡洛累积采样
变化性固定形状 + 偏移动画每次刷新树的形状都不同
性能成本极低初始化高,收敛后低
在线预览新窗口打开

在线 Demo: 04-lsystem-threejs.html — L-system 算法生成分形树,Three.js PCFSoftShadowMap 投影,每次刷新树形不同。需要本地静态服务器运行(npx serve)。


五、OGL + WebGL 方案:3D 场景中的实时软阴影

Daylight Computer 官网 的光影效果走了一条完全不同的路。它不是在页面上叠加一个光影层,而是构建了一个真实的 3D 场景。产品图片是"悬浮"在墙壁前方的 Plane Mesh,阴影是通过 Shadow Map + 自定义 Shader 实时计算的。

技术栈

  • OGL(oframe/ogl):~20KB 的极简 WebGL 库,替代 Three.js
  • react-ogl(pmndrs/react-ogl):OGL 的 React 绑定
  • GSAP ScrollTrigger:滚动驱动光源位置变化

软阴影 Shader 的核心算法

OGL 不自带软阴影,开发团队自己在 Fragment Shader 中实现了基于 Vogel Disk 采样的距离感知软阴影

每帧执行两个渲染 Pass:

Pass 1:从光源视角渲染深度图。每个像素的 R 通道存储物体到墙壁的距离(0~1),G 通道固定为 1.0 作为"存在遮挡物"的标记。

Pass 2:渲染最终画面。墙壁 Plane 的 Fragment Shader 对每个像素执行以下计算:

const float goldenAngle = pi * (3.0 - sqrt(5.0)); // ≈2.3998 rad
const float diskSize = 80.0;     // 采样半径
const int diskSamples = 100;     // 采样点数

for (int i = 1; i <= diskSamples; i++) {
    // Vogel Disk: 黄金角螺旋分布采样点
    float r = diskSize * sqrt(float(i) / float(diskSamples));
    float theta = float(i) * goldenAngle;
    vec2 offset = vec2(r * cos(theta), r * sin(theta));

    // 加噪声随机旋转,消除规律性伪影
    vec2 rotated = rotate(offset, noise(uv) * PI);

    // 采样深度图
    vec4 depth = texture2D(depthMap, uv + rotated / screenSize);

    if (hasOccluder(depth)) {
        // 从深度值计算阴影半径:近墙=小半径,远墙=大半径
        float shadowRadius = mix(20.0, 300.0, depth.r);

        // 只有当采样点在阴影覆盖范围内才算
        if (shadowRadius / 2.0 >= length(offset)) {
            // 近处阴影浓(权重8),远处阴影淡(权重0.5)
            influence += mix(8.0, 0.5, shadowRadius / 300.0);
        }
    }
}

// clamp 到 0.8,阴影永远不会全黑
float shadow = clamp(influence / float(diskSamples), 0.0, 0.8);

【图片占位】 prompt: Technical diagram showing Vogel Disk sampling pattern - 100 dots arranged in a golden angle spiral within a circle, colored dots showing which samples detect shadow occlusion (green) vs empty space (gray), with a center pixel marked in red, dark background, clean vector style

Vogel Disk 比方形网格好在哪? 方形网格采样会留下明显的方块伪影。Vogel Disk 用黄金角将点均匀分布在圆盘内,没有对齐的行/列,所以采样误差分布均匀,视觉上更平滑。这个算法在 pmndrs/drei 库中也被使用。

滚动时的光源运动

GSAP ScrollTrigger 的 scrub: true 将滚动位置映射为动画进度。光源 position 在 3D 空间中移动 → Shadow Camera 跟随 → 深度图每帧刷新 → 阴影角度实时更新。

在线预览新窗口打开

在线 Demo: 05-ogl-webgl.html — 原始 WebGL2 实现两遍渲染:深度图 + Vogel Disk 64 次采样软阴影。拖拽鼠标改变光源方向,观察阴影软硬变化。


六、Canvas 2D 方案:百叶窗光线投射

还有一个纯 Canvas 2D 的实现(源码 Gist),不用 CSS blend mode,不用 WebGL,不用 SVG 滤镜,一个 HTML 文件搞定。

核心技术:多层 OffscreenCanvas + globalCompositeOperation

整个效果在一个 position: fixed 的全屏 Canvas 上绘制,用 mix-blend-mode: multiply 叠加到页面内容上。内部用了 3 层 OffscreenCanvas 做离屏合成:

主 Canvas 流程(每帧 requestAnimationFrame):

1. 用阴影色填充整个画布(暖灰色,~rgb(228,214,196))
   → 这是"被遮挡区域"的基底色

2. 在离屏 Canvas 上绘制百叶窗光缝:
   - 18 条水平光缝,每条用 linearGradient 做柔边
   - 每条光缝有独立的微风偏移(sin 波)
   - 距离中心越远,光缝越暗(边缘衰减)

3. 用 destination-in 合成水平/垂直渐变蒙版
   → 光线在窗框边缘自然衰减

4. 用 destination-out 从光缝中"挖去"窗框横梁和拉绳的阴影

5. 将离屏 Canvas 以 destination-out 画到主 Canvas
   → 有光的地方变透明,露出页面内容
   → 没光的地方保持暖灰色阴影

6. 在另一层离屏 Canvas 上绘制暖色光晕(radialGradient)
   → 用 destination-in 裁剪到光缝形状内
   → 叠加到主 Canvas,给光线加暖色温

关键代码细节

Canvas transform 模拟投影角度:

const skewX = lerp(0.34, 0.26, n);  // 水平斜切
const skewY = lerp(0.13, 0.09, n);  // 垂直斜切
ctx.transform(1, skewY, skewX, 1, 0, 0);  // 仿射变换

不是 CSS skew(),是直接操作 Canvas 的变换矩阵。(1, skewY, skewX, 1, 0, 0) 是一个剪切变换,让矩形的百叶窗光线投影成平行四边形。

百叶窗缝隙动画:

const numSlats = 18;
const spacing = innerH / numSlats;
const slatThick = spacing * lerp(0.88, 0.12, open); // open 随时间呼吸
const gapH = spacing - slatThick;

for (let i = 0; i < numSlats; i++) {
    // 每条缝隙有独立的风力偏移
    const wb = Math.sin(now * 0.00008 + i * 0.53) * 1.1
             + Math.sin(now * 0.00019 + i * 0.79) * 0.6;
    const sy = baseY + wb;

    // 距离中心越远,模糊越大(模拟物理距离)
    const vertPos = i / numSlats;
    const slatSoft = baseSoft * (0.55 + vertPos * 1.0);
    ...
}

open 值通过 0.5 + sin(t*0.00006)*0.04 + sin(t*0.00015)*0.02 缓慢"呼吸",百叶窗的开合程度微微变化。每条缝隙的风力偏移用了两个不同频率的 sin 波叠加,避免所有缝隙同步运动。

开关动画的过渡:fadeVal + Hermite 缓动插值(fadeVal²(3-2fadeVal),即 smoothstep)实现 1.8 秒的淡入淡出,而非突然出现/消失。

在线预览新窗口打开

在线 Demo: 06-canvas-2d.html — 多层 OffscreenCanvas 合成,18 条百叶窗光缝带独立风力偏移,Canvas transform 制造斜切投影。右上角可开关效果。


七、Video Overlay 方案:最简单的实现

除了上述代码驱动的方案,还有一种更直接的思路:把光影效果预渲染成视频,用 <video> 循环播放,overlay 在页面内容上。配合 mix-blend-mode: multiply,视频中亮色区域变透明,暗色区域叠加为阴影,效果和代码方案视觉上差别不大。

这种方式的优势在于制作成本低,设计师可以在 After Effects 等工具中精确调整光影节奏,不需要理解 WebGL 或 Canvas。劣势也很明显:视频文件体积大,无法根据用户交互(如滚动、鼠标位置)做实时响应,循环播放也容易暴露接缝。

有些网站在此基础上扩展出了多种环境模式(rainy mode、moonlight mode)并加上了音效,把 Dappled Light 从单一的"日光模式"做成了完整的环境氛围系统。这类实现在技术上比较直接,不再展开,但在产品设计层面有参考价值。

在线预览新窗口打开

在线 Demo: 07-video-overlay.html — Canvas 生成光影图案模拟视频叠加,支持 ☀️ Sunny / 🌧️ Rainy / 🌙 Moonlight 三种模式切换。


八、各方案技术对比

方案技术路径树叶/遮挡物阴影软化方式动画驱动JS 依赖性能
纯 CSSCSS blendEmoji 字符text-shadow blurCSS @keyframes极低
SVG + CSS AnimationCSS + SVG绘制素材CSS animationCSS animation
CSS + SVG Filter 工程方案CSS + SVG filterPNG 叶片素材backdrop-filter 渐进模糊SVG animate + CSS极少
L-system + Three.jsThree.js + R3FL-system 生成AccumulativeShadows 蒙特卡洛Three.js useFrame较重初始化高,稳态低
OGL + WebGLOGL + WebGL3D Plane MeshVogel Disk ShaderGSAP ScrollTrigger中等中等
Canvas 2DCanvas 2D几何计算linearGradient 柔边requestAnimationFrame单文件,无框架
Video OverlayVideo预渲染视频N/Avideo 循环少量极低

九、如果你想自己实现

根据你的场景选择:

想要最快集成 → 直接 fork 开源项目 Sunlit,改改颜色和尺寸。纯 CSS + 一张叶片图,几乎零依赖。

想要独立单文件 → 用 Canvas 2D 方案,一个 HTML 文件搞定,copy 这个 Gist 即可,无任何框架依赖。

想要程序化生成、每次不同 → 用 L-system + @react-three/dreiAccumulativeShadows + RandomizedLight,自己用 L-system 算法生成树木几何。

想要最强的物理真实感 → 参考 Daylight Computer 的 Vogel Disk 软阴影方案,完全自定义光源、深度、模糊半径。


十、核心收获

研究完这一圈下来,最大的感受是:同一个视觉效果,从 20 行 CSS 到完整的 WebGL Shader,存在巨大的技术光谱。 选择哪个方案不取决于"哪个最高级",而是取决于你的场景:

  • 个人博客加点氛围?CSS blend mode 足矣
  • 产品官网需要品牌感?Canvas 2D 或 SVG 方案
  • 需要与 3D 场景深度集成?WebGL Shadow Map
  • 需要程序化生成自然形态?L-system + 累积阴影

Dappled Light 的魅力在于它触达了一个人类天然觉得舒服的视觉记忆——午后的阳光透过树叶洒在桌面上。技术只是手段,那份温暖才是目的。


参考资料

来源链接说明
CSS-TricksA Serene CSS Dappled Light Effect2022 年,最早的 Web 端 Dappled Light 实现
Sunlit.placesunlit.place2024 年,设计师 Chloe 的 SVG + CSS Animation 精致版
Sunlit 开源项目jackyzha0/sunlit2024 年,Jacky Zhao 的完整 CSS 工程方案(882 stars)
Farah Yanfarayan.me/dappled2024 年,L-system + @react-three/drei 方案
Daylight Computerdaylightcomputer.com2023 年,basement.studio 开发,OGL + WebGL 实时软阴影
basement.studio 技术博客Creating Daylight | The ShadowsDaylight 官网阴影算法的官方技术拆解
basement.studio 技术博客Creating Daylight | The DevexDaylight 官网开发体验、调试工具、性能优化
Mason WangGist: cozy-window-shade.html2026 年,斯坦福 Mason Wang 的纯 Canvas 2D 百叶窗方案
dany.worksdany.works曾有 Video Overlay + 多模式(rainy/moonlight)+ 音效的版本,网站现已改版
Ken HawkinsCreating warm, dappled light with CSS and SVGSunlit 方案的衍生实现分析,含 scroll-driven animation
pmndrs/dreiAccumulativeShadows 文档@react-three/drei 累积阴影组件的官方文档
WikipediaL-systemLindenmayer 系统的理论基础