2D 光照与阴影
Estella 用 Light2D 光源和 ShadowCaster2D 遮挡体照亮 2D 场景。一个光源就是一个带
Light2D 组件的实体;而一个渲染器只有在用 Lit-2D 材质绘制时才会被照亮。这让光照是可选的、
且开销低——大多数精灵保持不受光,你想照亮的那些才配一个受光材质。
给一个实体加 Light2D 组件并选一个类型:
import { defineSystem, Commands, Transform, Light2D, Light2DType } from 'esengine';
const addSun = defineSystem([Commands()], (cmds) => { cmds.spawn() .insert(Transform, { position: { x: 0, y: 0, z: 0 } }) .insert(Light2D, { type: Light2DType.Directional, color: { r: 1, g: 0.95, b: 0.8, a: 1 }, intensity: 1.2, direction: { x: -1, y: -1 }, });});Light2D 字段(含默认值):
| 字段 | 默认 | 说明 |
|---|---|---|
type |
Point |
Light2DType:Point、Directional、Ambient 或 Spot。 |
color |
{1,1,1,1} |
光颜色,RGBA 0..1。 |
intensity |
1 |
亮度倍数。 |
radius |
200 |
衰减半径,世界单位(Point / Spot)。 |
direction |
{0,0} |
朝向(Directional / Spot)。 |
innerAngle |
30 |
聚光锥内角,度(内部全亮)。 |
outerAngle |
45 |
聚光锥外角,度(到这里衰减为零)。 |
shadowSoftness |
0 |
阴影边缘柔和度(光源尺寸);0 = 硬边。 |
shadowDistance |
0 |
方向光阴影延伸距离;0 = 无方向光阴影。 |
enabled |
true |
不移除也能关掉光源。 |
| 类型 | 行为 |
|---|---|
| Directional(方向光) | 来自单一 direction 的均匀光,像太阳——位置无关,只看朝向。 |
| Point(点光) | 从实体位置向外辐射,到 radius 处衰减为零。 |
| Spot(聚光) | 从位置沿 direction 的一个锥,在 innerAngle 内全亮、到 outerAngle 衰减到边缘,直到 radius。 |
| Ambient(环境光) | 处处叠加的平坦底光,与位置无关——用较低的 intensity,让阴影区不至于纯黑。 |
// 一盏暖色点光.insert(Light2D, { type: Light2DType.Point, radius: 320, intensity: 1.5, color: { r: 1, g: 0.7, b: 0.3, a: 1 } });
// 一束手电锥.insert(Light2D, { type: Light2DType.Spot, direction: { x: 0, y: -1 }, innerAngle: 18, outerAngle: 34, radius: 500, intensity: 2 });
// 一点微弱环境光,让画面不至于全黑.insert(Light2D, { type: Light2DType.Ambient, intensity: 0.25 });让渲染器接受光照
Section titled “让渲染器接受光照”默认情况下,一个 Sprite 用不受光材质绘制,会忽略每一盏灯。让它受光最简单的方式是
lit 开关——不需要材质、不需要着色器:
app.world.spawn() .insert(Transform, { position: { x: 0, y: 0, z: 0 } }) .insert(Sprite, { texture: hero, lit: true });在编辑器里就是 Sprite 组件上的 Lit 勾选框。精灵以平法线受光——每种 2D 光源、 衰减和阴影都开箱即用。
当你需要的不止这个开关(色调参数、法线贴图、自定义表面逻辑)时,给它一个着色器声明了 Lit-2D 域的材质。引擎会注入光照 uniform 和 helper,而且 2D 着色器的顶点段是可选的—— 你只写片元:
#pragma shader "MyLit"#pragma version 300 es#pragma domain Lit2D
#pragma fragmentprecision mediump float;in vec4 v_color;in vec2 v_texCoord;in highp vec2 v_worldPos;uniform sampler2D u_textures[8];out vec4 fragColor;
// #pragma domain Lit2D 让引擎注入 LightConstants 块和 applyLighting2D() helper——// 你只需用你的基础色和一个法线调用它。void main() { vec4 base = texture(u_textures[0], v_texCoord) * v_color; vec3 N = vec3(0.0, 0.0, 1.0); // 平法线,朝向相机 vec3 lit = applyLighting2D(base.rgb, N, v_worldPos); fragColor = vec4(lit, base.a);}#pragma end在编辑器里,内容浏览器的 New Material (Lit) 创建的正是这样一个现成的受光材质, 自带色调参数和可选法线贴图。代码侧的编译与赋材质方式与 材质与着色器里完全一致:
import { Material, defineSystem, Query, Mut, Sprite } from 'esengine';
const litMat = Material.create({ shader: Material.compileShader(myLitSource) });
const applyLit = defineSystem([Query(Mut(Sprite))], (q) => { for (const [, sprite] of q) sprite.material = litMat;});你也能可视化地做一个受光材质——把 材质图 的域设为 Lit2D——见
材质图一节。
你传给 applyLighting2D 的 N 是表面法线。传一个常量 vec3(0, 0, 1) 得到平坦、均匀受光的
表面。要有逐像素的凹凸感,就在着色器里采样一张法线贴图并用它作 N——同一张基础色便会随每盏
灯的方向产生高光和阴影。把法线纹理作为一个 #pragma param … texture 加到你的受光材质上
(见材质与着色器)。
给一个实体加 ShadowCaster2D,让它遮挡光并投出阴影:
import { ShadowCaster2D } from 'esengine';
cmds.spawn() .insert(Transform, { position: { x: 100, y: 0, z: 0 } }) .insert(Sprite, { /* …一个受光材质… */ }) .insert(ShadowCaster2D, { size: { x: 64, y: 64 } });ShadowCaster2D 字段 |
默认 | 说明 |
|---|---|---|
size |
{32,32} |
遮挡体尺寸,世界单位。 |
enabled |
true |
开关阴影投射。 |
阴影质量在光源上控制,而非遮挡体:
shadowSoftness— 光源尺寸。0是锐利硬边;更大的值给出柔和半影(更大、更近的光投出更软的 阴影)。shadowDistance— 对 Directional 光,阴影延伸多远;0让方向光不产生阴影。
线性色彩空间
Section titled “线性色彩空间”引擎默认以伽马空间渲染——颜色按作者所写直接混合,这是 2D 管线的经典方式。项目可以切换到 线性光管线:纹理在采样时从 sRGB 解码到线性(硬件完成)、光照与染色在线性光下混合、最终画面在 末尾 blit 时编码回 sRGB。
物理正确的混合改变的是光的叠加方式:衰减不再压向死黑,多盏灯的重叠像光一样相加而不是像颜料。 不受光的美术资源经解码再编码是恒等回环——切换不会“重画”你的精灵;差异只出现在颜色相互混合的地方。
线性模式也是 HDR 的入口:只要浮点渲染目标可用(WebGPU 恒可用;WebGL2 有
EXT_color_buffer_float),后处理链就跑在半浮点上——intensity 大于 1 的灯把真正的超范围
能量推进 bloom 与 tonemap,亮光会发辉光。
在 项目设置 → 渲染 → 色彩空间 → 线性 启用。着色器按该模式编译,所以它在引擎启动时定死—— 编辑器会提示重新加载。设置随项目清单走:视口、运行预览和所有导出——web、桌面、微信、playable—— 启动同一条管线。纯代码项目在创建应用时传入:
const app = createWebApp(module, { colorSpace: 'linear' });
编辑器里预览的 Light2D——它的照射光池,以及柱子投出的阴影(开 Preview FX 可在编辑态点亮场景)。