414 lines
32 KiB
Markdown
414 lines
32 KiB
Markdown
# 图形渲染模块
|
||
|
||
**脚本入口:** `eve.Graphics()`
|
||
|
||
清屏、2D 图元、纹理、Canvas、摄像机和 3D 渲染。Camera2D/Camera3D 提供屏幕与世界坐标换算,供 2D/3D 拾取使用;形状命中测试见 [Math](math.md),物理体查询见 [Physics](physics.md)。
|
||
|
||
## 基本用法
|
||
|
||
```squirrel
|
||
gfx.setBackgroundColor(0.08, 0.1, 0.16, 1.0);
|
||
gfx.clear();
|
||
gfx.drawSolidRect(40, 40, 160, 80, 0.2, 0.7, 1.0, 1.0);
|
||
```
|
||
|
||
## 对象关系与调用时机
|
||
|
||
`Graphics` 管理设备、swapchain、camera、light 和提交;Texture/Shader/Mesh/Renderable 是资源或场景对象。CPU 资源创建在 init,帧内只更新参数并 draw。
|
||
|
||
## 目标导向指南
|
||
|
||
### 绘制 2D 游戏帧
|
||
|
||
在 `eve_render()` 开始调用 `clear()`,随后按背景、地图、角色、粒子、UI 的顺序提交。纯色占位使用 `drawSolidRect()`;已有 Texture 使用 `drawTexturedRect()`。需要围绕矩形中心旋转的精灵可调用 `drawTexturedRectRotated(texture, centerX, centerY, width, height, degrees, r, g, b, a)`;屏幕坐标 Y 轴向下,因此正角度表现为顺时针旋转。正常主循环由引擎负责 present。
|
||
|
||
`drawText(font, text, x, y, r, g, b, a, scale)` 在当前屏幕或 Canvas 坐标的任意
|
||
位置绘制 UTF-8 文本,`(x, y)` 是该行左上角。`font` 必须是 `newFont()` 返回的字体;
|
||
绘制调用不读取或修改当前字体状态。原有的 `print(text, x, y, r, g, b, a, scale)`
|
||
继续保留,并使用 `setFont()` 选择的当前字体。
|
||
|
||
需要 UV 动画、旋转、独立混合模式或程序化变换时,使用脚本精灵对象:
|
||
|
||
```squirrel
|
||
local sprite = gfx.newSprite2D();
|
||
sprite.setTexture(sheet.getTexture());
|
||
sprite.setQuad(quad);
|
||
sprite.setPosition(400, 270);
|
||
sprite.setScale(2.0, 2.0);
|
||
sprite.setRotation(30.0); // degree, rotate around rect center
|
||
sprite.setAnchor(0.25, 0.75); // normalized rotation pivot
|
||
sprite.setFlip(true, false); // mirror UV without negative scale
|
||
sprite.setBlend("alpha"); // alpha | premultiplied | additive | multiply
|
||
|
||
// eve_render: clear/draw background first, then submit all live Sprite2D objects
|
||
gfx.renderSprites();
|
||
```
|
||
|
||
`Sprite2D` 还提供 size、color、layer、visible、receiveLight、castOcclusion 等属性。
|
||
裁边动画通常由 `SpriteAnim.bindSprite(sprite)` 自动调用 `setFrameLayout`,无需游戏代码逐帧修正偏移。
|
||
不再使用时调用 `destroy()`;`renderSprites()` 是聚合提交接口,不要再把同一精灵加入另一条 2D 队列,以免重复绘制。
|
||
|
||
### 渲染带光照的 3D 对象
|
||
|
||
初始化时创建 mesh、shader 和 renderable,设置 camera、ambient 和 directional light;每帧只更新 transform/material 参数,最后调用 `render3D()`。阴影开关、bias 和 strength 应逐场景调节。
|
||
|
||
`Camera3D` 默认使用透视投影。等距视图可调用 `setOrthographic(height)`,其中
|
||
`height` 是世界空间中的垂直可视范围;`setPerspective()` 恢复透视投影。
|
||
`setClipPlanes(near, far)` 配置两种投影共用的近、远裁剪面,并要求
|
||
`0 < near < far`。`getEyeX/Y/Z` 与 `getTargetX/Y/Z` 读取世界空间眼点和注视点;
|
||
`getFov()` 返回垂直视野(度),与 `setFov()` 对应。
|
||
|
||
### 跨帧绘制 3D 基础图形
|
||
|
||
调试可视化、编辑器辅助线和玩法范围提示应使用持久 `Primitive3D`,不需要保留帧内
|
||
Canvas。Graphics 是图形场景的唯一所有者;脚本对象只保存带 owner、index 和 generation
|
||
的句柄。对象被移除、Graphics 销毁或槽位复用后,旧代理会通过 `isStale()` 明确失效。
|
||
|
||
```squirrel
|
||
local created = gfx.newPrimitiveLine3D(
|
||
0.0, 0.0, 0.0, 4.0, 1.0, -2.0, 1.0, 0.7, 0.1, 1.0, 3.0);
|
||
if (created.ok) {
|
||
debugLine <- created.value;
|
||
debugLine.setDash(12.0, 8.0, 0.0, "screen"); // screen | world
|
||
debugLine.setDepthMode("test"); // test-write | test | ignore
|
||
}
|
||
|
||
local sphereResult = gfx.newPrimitiveSphere3D(0.0, 1.0, -4.0, 1.5, 0.2, 0.8, 1.0, 1.0, 2.0);
|
||
local boxResult = gfx.newPrimitiveAabb3D(-1.0, 0.0, -1.0, 1.0, 2.0, 1.0, 1.0, 1.0, 0.0, 1.0, 2.0);
|
||
```
|
||
|
||
`newPrimitiveLine3D(...)`、`newPrimitiveSphere3D(...)` 和
|
||
`newPrimitiveAabb3D(...)` 返回统一 Result 表;成功后的 `value` 是 owned `Primitive3D`。
|
||
代理提供 `setVisible(bool)`、`setColor(r,g,b,a)`、`setLineWidth(width)`、
|
||
`setDash(draw,gap,phase,space)`、`clearDash()`、`setDepthMode(mode)`、`remove()` 和
|
||
`setObjectId(id)`、`isStale()`;`ownership()` 固定返回 `"owned"`。所有修改和移除操作都返回 Result,必须检查 `ok` 或显式忽略;代理析构会
|
||
移除仍存活的图形。
|
||
|
||
持久图形还提供以下创建入口,参数末尾均为 `r,g,b,a,width`(浮点数),返回相同 Result/owned proxy:
|
||
|
||
| 方法 | 几何参数(在颜色和线宽之前) |
|
||
| --- | --- |
|
||
| `newPrimitiveDisk3D` | `x,y,z,nx,ny,nz,radius` |
|
||
| `newPrimitiveCylinder3D` | `ax,ay,az,bx,by,bz,radius` |
|
||
| `newPrimitiveCapsule3D` | `ax,ay,az,bx,by,bz,radius` |
|
||
| `newPrimitiveCone3D` | `x,y,z,dx,dy,dz,height,radius` |
|
||
| `newPrimitiveArrow3D` | `ax,ay,az,bx,by,bz,headLength,headRadius` |
|
||
| `newPrimitivePolyline3D` | `xyz,closed`;xyz 为至少两个点的扁平浮点数组,闭合至少三个点 |
|
||
| `newPrimitiveObb3D` | `xyz`;中心和三个 halfAxis 共 12 个浮点数 |
|
||
| `newPrimitiveFrustum3D` | `xyz`;8 个角点,共 24 个浮点数,顺序与 C++ Frustum 一致 |
|
||
| `newPrimitiveGrid3D` | `xyz,cellsU,cellsV`;原点、axisU、axisV 共 9 个浮点数,格数为 1–4096 的整数 |
|
||
| `newPrimitiveArc3D` | `xyz,radius,start,sweep`;中心、法线、起始方向共 9 个浮点数,角度为弧度 |
|
||
|
||
这些图形默认描边;`setPaintMode("fill"|"stroke"|"fill-stroke")` 切换填充方式。
|
||
代理支持 `setWidthSpace("screen"|"world")`、`setLineCap("butt"|"square"|"round")`、
|
||
`setLineJoin("miter"|"bevel"|"round")`、`setCullMode("none"|"back"|"front")`、
|
||
`setBlendMode("alpha"|"opaque"|"additive"|"premultiplied"|"multiply")`。
|
||
`setTransform(elements)` 接受 16 个浮点数的列主序矩阵,整体替换局部到世界变换。
|
||
`setLayer(layer)` 设置非负 uint32 排序层。
|
||
`gfx.setPrimitiveTransforms3D(proxies,matrices)` 批量替换同一 Graphics 所属图形的变换。
|
||
`proxies` 为 Primitive3D 数组;`matrices` 是每对象 16 个浮点数的扁平列主序矩阵数组。
|
||
成功返回 Result 表与 `count`;任一代理失效、类型错误、owner 不匹配或矩阵非法时整批不修改。
|
||
同一代理重复出现时最后一个矩阵生效;空批次成功且 count 为 0。最多接受 65536 个对象。
|
||
`setPolyline(xyz,closed)` 将代理几何替换为新的 owning 折线,保留画笔、变换、可见性和句柄。
|
||
所有设置返回 Result;非法模式、矩阵长度或失效代理不会修改原图形。
|
||
上述操作在 VM 所在线程同步执行,不保留调用方数组,不调用外部回调。
|
||
|
||
### 实时编辑与替换 Shader
|
||
|
||
开发工具可保留已有 `Shader` 对象及其材质引用,仅替换内部后端资源:
|
||
|
||
```squirrel
|
||
local result = gfx.replaceShaderFromGlsl(shader, vertexSource, fragmentSource);
|
||
// Vulkan SPIR-V:gfx.replaceShaderFromSpv(shader, vertexWords, fragmentWords)
|
||
// words 数组须为 uint32 范围整数;先用 spirv-val 验证字节码。返回 Result,失败保留旧管线。
|
||
// WebGPU 后端使用:gfx.replaceShaderFromWgsl(shader, vertexSource, fragmentSource)
|
||
if (!result.ok) {
|
||
print(result.diagnostics);
|
||
}
|
||
```
|
||
|
||
这些接口都返回统一的结构化 `Result`,调用方必须检查 `ok` 或显式忽略结果。编译或
|
||
管线创建失败时,原 `Shader` facade、已声明 uniform 以及上一份可用管线保持不变,
|
||
因此编辑器可继续显示最后一次成功的效果。空的 vertex source 表示沿用引擎默认顶点
|
||
阶段;调用必须发生在 Graphics 所属的渲染线程。GLSL 是 Vulkan 开发路径,WGSL 是
|
||
WebGPU 开发路径;当前后端不支持对应源码格式时会返回 `Unsupported` 和诊断信息。
|
||
|
||
### 纹理过滤(mipmap / 各向异性 / LOD)
|
||
|
||
默认 `newTexture` 仍为线性过滤、单级 mip(兼容旧行为)。需要三线性与各向异性时:
|
||
|
||
```squirrel
|
||
// generateMipmaps=true, maxAnisotropy=16, filter/mipmap="linear", lodBias=0
|
||
tex <- gfx.newTextureWithSampler(img, false, false, true, 16.0, "linear", "linear", 0.0);
|
||
print(tex.getMipmapCount());
|
||
gfx.setTextureSampler(tex, "nearest", "none", 1.0, 0.0); // 像素风
|
||
print(gfx.getMaxAnisotropy());
|
||
```
|
||
|
||
C++ 侧使用 `TextureCreateInfo::withMipmaps()` / `TextureSampler::anisotropic()`,并通过同名 `setTextureSampler` 重载热更新采样状态(字符串版参数与脚本一致)。`filter` 只接受 `nearest`/`linear`,`mipmap` 只接受 `none`/`nearest`/`linear`,传错会抛异常而不是静默回退。Cubemap(IBL)默认生成完整 mip 链,供 `textureLod` 按粗糙度采样。
|
||
|
||
几何 LOD:`Renderable3D.setMeshLod(index, mesh, switchDistance)`,`RenderSystem3D` 按相机距离选择网格。
|
||
|
||
需要把运行对象交给 Editor/MCP 等跨帧工具时,同时读取
|
||
`Renderable3D.getEntityId()` 与 `getEntityGeneration()`;两者共同构成临时 ECS handle,
|
||
generation 用于拒绝实体销毁后被复用的旧 id。不要只长期保存 entity id。
|
||
Agent 完成材质事务后,可用 `getTintR()`、`getTintG()`、`getTintB()` 与 `getRoughness()`
|
||
独立核对字段材质的运行时状态。
|
||
|
||
### 材质(Material)与模型部件
|
||
|
||
把着色方法(`pbr` / `unlit` / `hair` / `custom`)、贴图和 PBR 参数打成一个 `Material`,挂到整模或某个部件:
|
||
|
||
```squirrel
|
||
local mat = gfx.newMaterial();
|
||
mat.setShadingModel("pbr");
|
||
mat.setAlbedoTexture(albedo);
|
||
mat.setNormalTexture(nrm);
|
||
mat.setSurfaceMode("transparent"); // opaque | masked | transparent
|
||
mat.setBlendMode("alpha"); // alpha | premultiplied | additive | multiply
|
||
mat.setDepthWrite(false);
|
||
mat.setDoubleSided(true);
|
||
mat.setSortPriority(0); // 同优先级按相机深度从后向前排序
|
||
mat.setMetallic(0.2);
|
||
mat.setRoughness(0.5);
|
||
|
||
local r = Renderable3D.create();
|
||
r.setMaterial(mat); // 整模
|
||
// 或多部件:r.setPart(0, "body", bodyMesh, bodyMat);
|
||
r.setPartSortPriority(0, 100); // 每实例覆盖透明排序,不修改共享 Material
|
||
r.clearPartSortPriority(0); // 恢复使用 Material.getSortPriority()
|
||
local effectiveOrder = r.getPartSortPriority(0);
|
||
```
|
||
|
||
遮罩材质使用 `setSurfaceMode("masked")`、`setAlphaCutoff()` 和
|
||
`setAlphaTechnique("cutoff" | "dither" | "coverage")`。对应查询接口为
|
||
`getSurfaceMode()`、`getBlendMode()`、`getDepthWrite()`、`getDoubleSided()`、
|
||
`getSortPriority()`、`getAlphaCutoff()` 和 `getAlphaTechnique()`。纹理 Alpha 数据可用
|
||
`Texture.setAlphaConvention("straight" | "premultiplied")` 声明,并通过
|
||
`Texture.getAlphaConvention()` 查询。
|
||
|
||
虚拟纹理材质通过 `setVirtualTexture(albedoAtlas, normalAtlas, pageTable,
|
||
pageCountX, pageCountY, borderFraction)` 显式启用 atlas/page-table 路径;
|
||
`usesVirtualTexture()` 可查询当前模式,`clearVirtualTexture()` 恢复传统材质贴图路径。
|
||
三个纹理对象必须至少存活到材质停止使用它们为止;
|
||
参数校验失败时 `setVirtualTexture()` 会抛出包含诊断信息的脚本异常。
|
||
|
||
### 高级渲染与屏幕空间效果
|
||
|
||
GBuffer、毛发、体积光、AO、GI 与抗锯齿集中在 [高级渲染与屏幕空间效果](graphics/rendering-effects.md),避免各类渲染特性持续修改本概览。
|
||
|
||
### 2D 屏幕拾取
|
||
|
||
`Camera2D` 用 `setPosition` / `setZoom` 控制视口中心与缩放;`screenToWorldX/Y(screenX, screenY, viewW, viewH)` 把鼠标像素换成世界坐标(`viewW/H` 通常取 `gfx.getWidth/Height`),再交给 Math 的 `pointIn*` 或 Physics 的 `testPoint` / `queryAABB`。
|
||
|
||
### 3D 屏幕拾取
|
||
|
||
`Camera3D.screenToRay(screenX, screenY, viewW, viewH)` 写入眼点与单位方向,用 `getScreenRayOrigin*` / `getScreenRayDir*` 读取,再对包围球/盒调用 Math 的 `raycastSphere` / `raycastBox`。
|
||
|
||
### 曲面瀑布
|
||
|
||
`Waterfall.createCurvedSheet(width, height, segX, segY, curveDepth, lipOverhang)` 创建带横向弧度和顶部探出段的细分瀑布网格,适合贴合崖壁并形成自然的离壁水帘;原有 `createSheet(width, height)` 仍用于平面瀑布。
|
||
|
||
```squirrel
|
||
local fall = gfx.newWaterfall();
|
||
fall.createCurvedSheet(3.0, 7.0, 28, 48, 0.75, 0.85);
|
||
```
|
||
|
||
### 风格化水体配置
|
||
|
||
`Water.applyConfigJson(json)` 使用 `eve.graphics.stylized-water` 版本化 schema 一次性校验并应用深浅水色、波浪、泡沫、透明度、折射、反射和焦散参数;失败时保留原配置。`Water.configJson()` 返回当前配置的规范 JSON,可用于编辑器属性面板、预设保存和运行时复制。
|
||
|
||
```squirrel
|
||
local water = gfx.newWater();
|
||
water.createPlane(14.0, 14.0, 64, 64);
|
||
local current = water.configJson();
|
||
water.applyConfigJson(current);
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
- 忘记每帧 `clear()`,保留未定义的旧帧内容。
|
||
- 每帧编译 shader 或上传纹理。
|
||
- 2D/UI/3D 提交顺序错误导致覆盖。
|
||
- 拾取时 `viewW/H` 与实际渲染 drawable 不一致,射线会偏。
|
||
|
||
## Agent 运行时观察
|
||
|
||
`eve_renderable3d_get` 使用完整 ECS `entityId` + `generation` 读取 live Renderable3D 的 transform、
|
||
field-backed PBR 参数与资源占用标志;陈旧 identity 返回结构化 `stale`。
|
||
`eve_editor_execute_observe` 的默认 `renderable3d` observer 在写入前验证同一 identity,再通过
|
||
Editor 事务修改目标,并在一个响应中返回 live `before/after`、事务回执和 Editor snapshot。传入
|
||
`expect` JSON 子集和可选 `tolerance` 后,引擎还会返回 `converged`、`maxError` 与不匹配字段路径,
|
||
Agent 可直接决定是否继续纠正;无效期望会在写入前拒绝。同一协议还支持 `scene-node` observer,
|
||
因此 Agent 不必为场景和渲染对象维护两套调用编排。
|
||
|
||
## API 快查
|
||
|
||
`drawScene3DRGBA(x, y, w, h, r, g, b, a)` 把最近一次 `render3D()` 产生的正式
|
||
场景颜色复合到当前目标(交换链或 `Canvas`)。它适合自定义编辑器把与游戏相同的场景
|
||
呈现在任意 Viewport 中;与 `renderScene3DToCanvas` 的独立预览渲染不同,它复用完整
|
||
运行时场景管线。
|
||
|
||
`updateTextureFromImageData(texture, imageData)` 将尺寸相同的 RGBA8 `ImageData`
|
||
重新上传到由当前 Graphics 后端创建的已有纹理;纹理对象保持不变,可继续被材质引用。
|
||
该方法必须在渲染线程调用,后端所有权、格式或尺寸不匹配时抛出异常。
|
||
|
||
C++ 渲染适配器可调用 `updateTextureRegion(texture,x,y,width,height,rgba,bytesPerRow)`
|
||
更新单 mip RGBA8 纹理的子矩形。Vulkan 使用 staging buffer 和原图 `copyBufferToImage`,
|
||
WebGPU 使用带 origin 的 `WriteTexture`;两者都不重建 Texture、采样器或描述符。
|
||
多个离散矩形应使用 `updateTextureRegions(texture, regions)`:它在任何写入前验证整个
|
||
批次;Vulkan 将所有 region 紧密打包进一个 staging buffer 并只提交一次。
|
||
|
||
下列方法名来自当前 Squirrel 绑定;同一模块创建的辅助对象(例如 `World`、`Body`、`Source`)的方法也列在这里。
|
||
|
||
- `bakeMeshMorph()`、`newMeshFromArrays()`、`updateMeshVertices()`、`clear()`、`clearMorphWeights()`、`declareFloat()`、`declareMatrix()`、`declareVec2()`、`declareVec3()`、`declareVec4()`
|
||
- `drawSolidRect()`、`drawTexturedRect()`、`drawTexturedRectRotated()`、`drawOcclusionSolid()`、`drawOcclusionTexture()`、`getCastShadow()`、`getCastOcclusion()`、`getDirX()`、`getDirY()`、`getDirZ()`、`getEyeX()`、`getEyeY()`、`getEyeZ()`、`getFov()`、`getHeight()`、`getMorphCount()`
|
||
- `getMorphName()`、`getMorphWeight()`、`getName()`、`getRadius()`、`getScreenRayDirX()`、`getScreenRayDirY()`、`getScreenRayDirZ()`、`getScreenRayOriginX()`
|
||
- `getScreenRayOriginY()`、`getScreenRayOriginZ()`、`getShader()`、`getShadowBias()`、`getShadowStrength()`、`getType()`、`getUniformIndex()`、`getVertexCount()`、`getIndexCount()`
|
||
- `getTargetX()`、`getTargetY()`、`getTargetZ()`、`getVolumetric()`、`getVolumetricIntensity()`、`getWidth()`、`getX()`、`getY()`、`getYaw()`、`getZ()`、`getZoom()`、`hasMorph()`、`hasMorphData()`
|
||
- `hasUniform()`、`isEnabled()`、`isMorphDirty()`、`newHairShader()`、`newMeshCylinder()`、`newMeshShader()`、`newMeshShaderVF()`、`newMeshSphere()`、`newQuad()`、`newShader()`
|
||
- `newShaderFromSpvFile()`、`replaceShaderFromGlsl()`、`replaceShaderFromWgsl()`、`newTexture()`、`newTextureWithSampler()`、`updateTextureFromImageData()`、`setTextureSampler()`、`getMaxAnisotropy()`、`newVolumetric()`、`newAmbientOcclusion()`、`newGlobalIllumination()`、`newAntiAliasing()`、`setMsaaSamples()`、`getMsaaSamples()`、`present()`、`render3D()`、`reset()`、`screenToRay()`、`screenToWorldX()`、`screenToWorldY()`
|
||
- `sendFloat()`、`sendVec2()`、`sendVec3()`、`sendVec4()`、`setActive()`、`setAmbient()`、`setBackgroundColor()`、`setCamera()`
|
||
- `setCanvas()`、`setCastOcclusion()`、`setCastShadow()`、`setCloudShadows()`、`setColor()`、`setDirection()`、`setDirectionalLight()`、`setEnabled()`、`setEnvIntensity()`、`setEnvMap()`
|
||
- `setEye()`、`setFov()`、`setMesh()`、`getMesh()`、`setMeshLod()`、`clearMeshLod()`、`getMeshLodCount()`、`getMeshLodLevelAtDistance()`、`setMetallic()`、`setMorphWeight()`、`setNormalTexture()`、`setHeightTexture()`、`setPosition()`、`setRadius()`
|
||
- `setReceiveLight()`、`setReceiveShadow()`、`setRotation()`、`setRoughness()`、`setScale()`、`setShader()`、`setHair()`、`getHair()`、`setShadowBias()`、`setShadowStrength()`
|
||
- `setTarget()`、`setTexCellBomb()`、`getTexCellBombScale()`、`getTexCellBombStrength()`、`getTexCellBombRotation()`、`setParallax()`、`getParallaxScale()`、`getParallaxMinLayers()`、`getParallaxMaxLayers()`、`setTexture()`、`setTint()`、`setType()`、`setUp()`、`setViewport()`、`setVisible()`、`setVolumetric()`、`setVolumetricIntensity()`、`setYaw()`
|
||
- `setZoom()`、`worldToScreenX()`、`worldToScreenY()`、`Texture.getMipmapCount()`
|
||
- 字体:`newFont()`、`setFont()`、`getFont()`、`drawText()`、`print()`、`getAscent()`、`getBaseline()`、`hasGlyph()`
|
||
- `AlphaMask`:`newAlphaMask()`、`setThreshold()`、`getThreshold()`、`setSoftness()`、`getSoftness()`、`setInverted()`、`getInverted()`
|
||
- `MapFog`:`newMapFog()`、`update()`、`setTime()`、`getTime()`、`setCloudTexture()`、`getCloudTexture()`、`setMaskTexture()`、`getMaskTexture()`、`setCloudTiling()`、`setCloudSpeed()`、`setDistort()`、`setDistortFix()`、`setFogColor()`、`setFogAlpha()`、`setEdgeSoftness()`、`setShadowEnabled()`、`setShadow()`、`setSelectStrength()`、`setDissolveScale()`、`setCloudMix()`、`setCloudDensity()`、`getCloudTileA()`、`getCloudTileB()`、`getFogAlpha()`、`getShadowEnabled()`、`makeCloudTexture()`、`draw()`(大地图迷雾:双层云 + mask R/G/B)
|
||
- `Sprite2D`:`setPosition()`、`getX()`、`getY()`、`setRotation()`、`getRotation()`、`setScale()`、`getScaleX()`、`getScaleY()`、`setSize()`、`getWidth()`、`getHeight()`、`setTexture()`、`getTexture()`、`setQuad()`、`getQuad()`、`setColor()`、`setLayer()`、`getLayer()`、`setVisible()`、`getVisible()`、`setReceiveLight()`、`getReceiveLight()`、`setBlend()`、`getBlend()`、`setAnchor()`、`getAnchorX()`、`getAnchorY()`、`setFlip()`、`getFlipX()`、`getFlipY()`、`setFrameLayout()`、`setCastOcclusion()`、`getCastOcclusion()`、`destroy()`
|
||
- `Volumetric`:`setQuality`、`setMode`、`scatter`、`applyFromScene`、`rayMarch`、`applyFog`、`setFogHeight`、`setFogStart`、`setFogEnd`、`setCamera`、`setLightDirection`、`setDensity` 等
|
||
- `FogVolume`:`setShape/getShape`、`setPosition`、`setSize`、`setExtinction/getExtinction`、`setAlbedo`、`setEmissive`、`setAnisotropy/getAnisotropy`、`setEdgeFalloff/getEdgeFalloff`。`setNoise(amount, scale, seed)` 可加入确定性的世界空间密度变化,`getNoiseAmount/getNoiseScale/getNoiseSeed` 返回当前设置;调用 `Volumetric.setCamera()` 后,通过 `injectFroxelLocalVolume(volume)` 按当前视锥注入 froxel 网格。
|
||
- `Volumetric` froxel:`configureFroxelGrid`、`clearFroxelGrid`、
|
||
`injectFroxelHeightFog`、`integrateFroxel`、`uploadFroxel`、
|
||
`applyFroxel`、`applyFroxelTo`
|
||
- `AmbientOcclusion`:`setQuality`、`setMode`、`setCamera`、`setRadius`、`setBias`、`setIntensity`、`setPower`、`compute`、`blur`、`applyOverlay`、`applyFromDepth`、`resolutionFor` 等
|
||
- `GlobalIllumination`:`setQuality`、`setCamera`、`setRadius`、`setIntensity`、`setLightDirection`、`setLightColor`、`applyFromDepth`、`getSampleCount` 等
|
||
- `AntiAliasing`:`setQuality`、`setMode`、`apply`、`applyTo`、`applyCanvas`、`applyCanvasTo`、`suggestScale`、`resolutionFor`、`setFloat`、`getFloat` 等
|
||
|
||
## 使用要点
|
||
|
||
- 模块对象和它创建的资源对象应保存在全局或实体状态中,不要在每帧重复创建。
|
||
- 带 `update(dt)` 的系统应在 `eve_update` 调用;绘制方法应在 `eve_render` 调用。
|
||
- 参数约束、默认值和返回类型以对应模块头文件及 `addFunc` 绑定为准;本文 API 快查与当前源码同步生成。
|
||
|
||
**源码:** [`src/modules/graphics/`](../../../src/modules/graphics/)
|
||
**相关测试:** 在 [`test/`](../../../test/) 中搜索 `graphics`、`Camera2D`、`Camera3D`。
|
||
### HDR、景深与反射探针绑定
|
||
|
||
下列绑定用于 HDR 图像、反射探针采集/注册、天空环境、曝光、Bloom、Gaussian 景深(DOF)与反射链质量控制。
|
||
`Camera3D.setDepthOfField(focusDistance, maxBlurPx, focusRange)` 配置最终 HDR resolve 上的可分离 Gaussian 景深;
|
||
`maxBlurPx <= 0` 关闭效果。焦点平面用视空间距离表示;`getDofFocusDistance` / `getDofMaxBlur` /
|
||
`getDofFocusRange` 读取当前参数。HD-2D 缩微感可配合 `hd2d.Hd2dLook` 一键写入(见 [HD2D](hd2d.md))。
|
||
Cutout/masked 精灵需写入深度,DOF 才能正确对焦角色。
|
||
|
||
`applyConfiguredToCamera`、`applyToCamera`、`clearReflectionProbe`、`configureInfluence`、`filterAndPublish`、`getActiveCubemap`、`getAdaptiveFaceBudget`、`getAdaptiveFilterSamples`、`getBloomIntensity`、`getBloomThreshold`、`getCaptureClusteredLighting`、`getCaptureFarDistance`、`getCaptureLodDistanceScale`、`getCaptureMask`、`getCaptureTransparent`、`getCenterX`、`getCenterY`、`getCenterZ`、`getCount`、`getDofFocusDistance`、`getDofFocusRange`、`getDofMaxBlur`、`getEnvProbeCenterX`、`getEnvProbeCenterY`、`getEnvProbeCenterZ`、`getEnvProbeExtentX`、`getEnvProbeExtentY`、`getEnvProbeExtentZ`、`getEnvironmentLighting`、`getEnvironmentLightingIntensity`、`getExposure`、`getFaceCanvas`、`getGpuBudgetMs`、`getInfluenceBlendDistance`、`getInfluenceExtentX`、`getInfluenceExtentY`、`getInfluenceExtentZ`、`getInfluenceIntensity`、`getInfluencePriority`、`getLastCandidateCount`、`getLastCapturedFaceCount`、`getLastFilterSampleCount`、`getLastPublishedCount`、`getLastSelectedCount`、`getMaxRoughness`、`getPendingFaceCount`、`getPostProcessQuality`、`getPublishedRevision`、`getReflectionCaptureEnabled`、`getReflectionCaptureMask`、`getReflectionProbeCount`、`getReflectionQuality`、`getRefreshInterval`、`getResolution`、`getResolutionScale`、`getRevision`、`getSelectionHysteresis`、`getSkyB`、`getSkyFaceColor`、`getSkyFaceTexture`、`getSkyFaceTextureScale`、`getSkyG`、`getSkyIntensity`、`getSkyR`、`getSmoothedGpuDurationMs`、`getStagedRevision`、`getStagingCubemap`、`getThickness`、`getTotalCapturedFaceCount`、`getUpdateMode`、`hasEnvProbe`、`isAutoExposure`、`isCaptureComplete`、`isCapturePending`、`isRecaptureQueued`、`newHDRImageData`、`newReflectionProbeCapture`、`newReflectionProbeRegistry`、`queueCapture`、`queueCaptureAABB`、`remove`、`reportGpuDurationMs`、`requestCapture`、`setAutoExposure`、`setBloom`、`setCaptureClusteredLighting`、`setCaptureLodDistanceScale`、`setCaptureMask`、`setCaptureTransparent`、`setDepthOfField`、`setEnvironmentLighting`、`setExposure`、`setGpuBudgetMs`、`setMaxRoughness`、`setReflectionCaptureEnabled`、`setReflectionCaptureMask`、`setReflectionProbe`、`setReflectionQuality`、`setRefreshInterval`、`setResolutionScale`、`setSelectionHysteresis`、`setSkyColor`、`setSkyFaceColor`、`setSkyFaceTexture`、`setSkyFaceTextureScale`、`setUpdateMode`、`stageCapturedFaces`、`tick`、`tickAdaptive`、`updateCamera`。
|
||
|
||
### 预编译网格 Shader 与画布快照
|
||
|
||
`gfx.loadMeshShaderSpv(vertexPath, fragmentPath)` 从 VFS 加载 Vulkan SPIR-V,返回统一 Result。
|
||
空 vertexPath 使用默认 Mesh3D 顶点着色器;fragmentPath 必填。成功后 `value` 是 Graphics
|
||
所有的借用 Shader,只在 Graphics 生命周期内使用。调用者须预先编译匹配 Mesh3D 布局的程序。
|
||
加载失败返回诊断,不修改已有材质。仅在渲染/VM owner 线程同步调用。
|
||
|
||
`canvas.readPixels()` 返回独立 RGBA8 ImageData 的 Result;成功时脚本拥有 `value`,
|
||
之后修改或清空画布不影响快照。调用前先提交离屏绘制批次;此操作等待 GPU,适合验证、
|
||
导出,不适合每帧执行。失败返回诊断,不改变画布。HDR 使用既有独立接口。
|
||
|
||
Vulkan RGBA8 画布在分次提交之间保留像素,显式 clear 在下一次绘制提交时执行。
|
||
参考 `examples/ink-arena`,可用片元着色器和 alpha 混合在 GPU 上累积表面墨迹。
|
||
|
||
### 自定义网格资源和光栅状态
|
||
|
||
以下接口同步返回统一 Result,必须检查 `ok`。仅在 Graphics 所在线程、帧提交之外
|
||
调用,Shader 仍由 Graphics 所有;失败保留此前已提交的程序、资源和状态。
|
||
当前 Vulkan 提供实现,其他后端返回 Unsupported。
|
||
|
||
`gfx.replaceMeshShaderResourcesFromFiles(shader, vertexPath, fragmentPath, images, constantsPath)`
|
||
事务式替换普通网格 Shader 与独立 set 1 资源。vertexPath 为空使用默认顶点程序;
|
||
constantsPath 可为空。images 每项包含 `binding, path, format, dimension, width, height,
|
||
layers, mips, filter, wrapU, wrapV, anisotropy`。格式支持 `r8-unorm`、`rg8-unorm`、
|
||
`r16-unorm`、`rgba8-unorm`、`bgra8-unorm` 以及 RGBA/BGRA/BC1/BC3/BC7 对应
|
||
`-srgb` 格式;BC1/3/7 也支持 `-unorm`。dimension 为 `2d/array2d/cube`。
|
||
字节严格按 layer-major、mip-major 紧密排列,不经过图片解码、预乘或缩放。
|
||
filter 为 0 最近、1 双线性加最近 mip、2 三线性;wrapU/V 为 0 repeat、1 clamp。
|
||
Cube 必须是六个等宽高面;最多 16 个图像,binding 在 0..31,常量使用 binding 32,
|
||
16 字节对齐且最多 64 KiB。输入在调用内复制到 GPU 所有的状态,文件缓冲不跨帧借用。
|
||
普通 SPIR-V 重载会继续校验该资源布局。
|
||
|
||
`gfx.configureMeshShaderSurface(shader, blend, depthWrite, doubleSided)` 配置混合、
|
||
深度写入和剔除。blend 支持 `opaque/alpha/premultiplied/additive/multiply`。
|
||
`gfx.configureMeshShaderRaster(shader, compare, constantBias, slopeBias, colorMask)` 配置
|
||
深度比较、原生深度偏移和颜色写入。compare 支持 `less/lessEqual/always`;
|
||
colorMask 的位 0..3 对应 RGBA,7 表示仅 RGB。两个接口独立更新各自的状态,
|
||
状态在程序重载、资源替换和目标重建后保留。不适用于专用 hair/X-ray 管线。
|
||
偏移参数属于原生深度缓冲单位,跨后端不承诺相同数字产生逐像素相同结果。
|
||
|
||
|
||
### 自定义 Mesh Shader 实例资源
|
||
|
||
`replaceInstancedMeshShaderResourcesFromFiles(shader, vertex, fragment, images, constants, instances)`
|
||
与 `replaceMeshShaderResourcesFromFiles` 共享纹理/常量校验,并读取不可变实例矩阵文件。
|
||
`instances` 是小端 float32、列主序 mat4 记录(每条 64 字节,最多 64 MiB),必须有限、
|
||
仿射且可逆;上传复制到 Graphics 拥有的资源,调用者无需跨帧保留输入内存。
|
||
顶点程序可使用 set 1 / binding 33 的 `readonly buffer { mat4 transforms[]; }`,
|
||
其他可写或片段阶段存储缓冲不被接受。重载保留相同矩阵布局验证,失败不替换旧资源。
|
||
|
||
`drawMeshShaderInstances(mesh, shader, model, first, count)` 返回结构化 Result,必须检查 `ok`。
|
||
`model` 是 16 个列主序浮点值;`first`/`count` 选择已上传矩阵范围,顶点 Shader 的
|
||
`gl_InstanceIndex` 包含 first。零 count 为已校验的空操作;越界在绘制前拒绝。
|
||
仅在渲染线程的已开启 3D pass 内调用,不保留调用者引用、不调用脚本回调。
|
||
当前 Vulkan 实现该路径,其他后端明确返回 Unsupported;没有静默逐实例 CPU 绘制。
|
||
资源由 Graphics 统一释放,实例绘制本身不提供阴影、剔除或 ECS 注册,调用者按其
|
||
场景规则提交可见范围。小型 GPU 探针与地图装饰的集成证据分别记录,不能相互替代。
|
||
|
||
`Renderable3D.setInstanceRange(first, count, minimum, maximum, maximumHorizontalDistance)`
|
||
为场景对象配置值所有的实例范围并返回 Result;失败保留旧范围。
|
||
minimum/maximum 是实例矩阵输出空间中的 float3 包围盒,随后应用对象变换。
|
||
零距离禁用水平距离裁剪;正距离加上世界包围盒 X/Z 最大半径,与相机 X/Z
|
||
距离比较。主场景和离屏场景均执行 Vulkan 零到一深度的视锥裁剪。
|
||
`clearInstanceRange()` 恢复普通对象提交。范围元数据不持有资源指针,不是持久格式;
|
||
调用者在恢复场景时重新配置。更新必须在场景更新/渲染所属线程、绘制遍历以外执行。
|
||
|
||
当前仅支持单个静态自定义 Mesh Shader,使用 forward 绘制;调用者必须关闭
|
||
材质/对象投射阴影及对象遮挡投射,不支持 parts、蒙皮、LOD、hair 或 Xray。
|
||
设置时检查这些条件,后续改变对象能力时渲染阶段也检查,避免静默绘错。
|
||
实例缓冲实际容量仍由绘制接口校验。该路径读取既有 Renderable3D/Transform3D/
|
||
MeshRenderer 数据,不新增 ECS System 或在遍历中改变实体结构。
|
||
|
||
Vulkan 资源 Mesh Shader 的静态顶点输入允许 location 5 的 `vec4 tangent`:
|
||
xyz 是导入的对象空间切线,w 是相对于 `cross(normal, tangent)` 的副切线手性。
|
||
没有导入切线的网格提供零向量(w=0),消费者必须明确处理缺失数据;引擎不隐式
|
||
生成近似切线。带场景变换的导入会变换并归一化切线/副切线,保留镜像手性。
|
||
该输入目前属于 Vulkan 资源 Shader ABI;未实现此资源路径的后端仍返回 Unsupported。
|
||
|
||
`gfx.setSceneToneMapping("none" | "aces")` 返回 Result;默认 `aces` 保持既有输出。
|
||
`gfx.getSceneToneMapping()` 返回当前模式。`none` 仅在最终场景显示时将曝光后的
|
||
线性 RGB 裁剪到显示范围,然后执行需要的 sRGB 编码,不应用 ACES 曲线。
|
||
Bloom、曝光和 HDR 离屏纹理保持各自的职责;二维 UI 不经过此场景映射。
|
||
设置属于 Graphics 的显示状态,在 graphics/render 线程修改,不持有调用者引用
|
||
或触发回调。非法模式不改变旧值。Vulkan 支持两种模式;其他后端当前仅接受保留
|
||
默认模式的空操作,切换返回 Unsupported,不静默使用不同映射。
|
||
# Gaussian scatter bloom
|
||
|
||
`gfx.setBloomFilter("gaussianScatter", 0.68, 6, 65472.0)` returns a checked Result
|
||
and selects half-resolution prefiltering, separable Gaussian downsampling, and
|
||
linear interpolation between pyramid levels. `"karisTent"` restores the default
|
||
four-level filter. `gfx.getBloomFilter()` returns the selected name.
|
||
Camera bloom intensity and linear threshold remain controlled by `Camera3D.setBloom`.
|
||
Scatter must be finite in [0,1], maximum iterations an integer in [1,16], and
|
||
clamp finite in (0,65504]. Invalid settings leave the previous filter unchanged.
|
||
Settings are copied, render-thread only, with no callbacks or retained script
|
||
references. Graphics owns generated shaders/targets; targets allocate on the next
|
||
build. Both GLSL and WGSL implementations exist; backend runtime validation status
|
||
is recorded by the relevant test run, not implied by source availability.
|
||
|
||
### 资源纹理内部共享
|
||
|
||
`ShaderImageInput::contentOwner` 可携带不可变文件快照身份。相同快照、尺寸、格式、mip/层数及
|
||
完整采样器配置复用 GPU 图像;无身份则独立上传。调用者必须保证该身份的字节永不修改。
|
||
GPU 仅持有弱身份,shader 共同拥有图像;释放一个 shader 不影响其他持有者,新文件快照不复用旧内容。
|
||
新图像在内部按约 64 MiB 暂存批次提交(单个大图像可超过阈值)。同步 Result 失败保留原 shader,
|
||
脚本无需 begin/end 上传批次。
|