Files
EVEngine/docs/usr/modules/graphics.md
T
西风逍遥游 3c21fe396a Merge pull request #312 from EVEngine/codex/pixelworld-noita-tech-route
feat(pixelworld): add deterministic Noita-style simulation stack
2026-08-31 23:01:41 +08:00

233 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 图形渲染模块
**脚本入口:** `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`。
### 实时编辑与替换 Shader
开发工具可保留已有 `Shader` 对象及其材质引用,仅替换内部后端资源:
```squirrel
local result = gfx.replaceShaderFromGlsl(shader, vertexSource, fragmentSource);
// 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);
```
## 常见问题
- 忘记每帧 `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()`、`getHeight()`、`getMorphCount()`
- `getMorphName()`、`getMorphWeight()`、`getName()`、`getRadius()`、`getScreenRayDirX()`、`getScreenRayDirY()`、`getScreenRayDirZ()`、`getScreenRayOriginX()`
- `getScreenRayOriginY()`、`getScreenRayOriginZ()`、`getShader()`、`getShadowBias()`、`getShadowStrength()`、`getType()`、`getUniformIndex()`、`getVertexCount()`、`getIndexCount()`
- `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()`
- `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`;调用 `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 与反射链质量控制:
`applyConfiguredToCamera`、`applyToCamera`、`clearReflectionProbe`、`configureInfluence`、`filterAndPublish`、`getActiveCubemap`、`getAdaptiveFaceBudget`、`getAdaptiveFilterSamples`、`getBloomIntensity`、`getBloomThreshold`、`getCaptureClusteredLighting`、`getCaptureFarDistance`、`getCaptureLodDistanceScale`、`getCaptureMask`、`getCaptureTransparent`、`getCenterX`、`getCenterY`、`getCenterZ`、`getCount`、`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`、`setEnvironmentLighting`、`setExposure`、`setGpuBudgetMs`、`setMaxRoughness`、`setReflectionCaptureEnabled`、`setReflectionCaptureMask`、`setReflectionProbe`、`setReflectionQuality`、`setRefreshInterval`、`setResolutionScale`、`setSelectionHysteresis`、`setSkyColor`、`setSkyFaceColor`、`setSkyFaceTexture`、`setSkyFaceTextureScale`、`setUpdateMode`、`stageCapturedFaces`、`tick`、`tickAdaptive`、`updateCamera`。