Repository Wiki
isHarryh/Ark-Pets

角色渲染与着色器管线

ArkPets 的桌宠角色渲染采用 两段式(Two-pass)渲染管线:第一渲染通道 SpineRenderPass 将 Spine 骨骼动画绘制到正交相机的帧缓冲(FBO)中,第二渲染通道 PostProcessRenderPass 再将该 FBO 纹理合成到屏幕,并附加描边、阴影、背景色与透明度过渡等后处理效果。着色器逻辑由 RawShader 与 EffectShader 两个 Java 封装类与 assets/shaders/ 下的 GLSL 源文件共同实现。

目的与范围(Purpose and Scope)

本页覆盖 cn.harryh.arkpets.render 包中与角色绘制直接相关的完整渲染子系统:

  • 渲染管线架构:SpineRenderPass(骨架绘制通道)与 PostProcessRenderPass(后处理合成通道)的协作方式;
  • 着色器封装:RawShader、EffectShader 的使用方式与对应 GLSL 资源(assets/shaders/ 下的 Plain/Complex 系列);
  • 渲染目标管理:DynamicOrthographicCamara 的帧缓冲(FBO)在两个通道间的桥梁作用;
  • 与渲染相关的配置项(render_shader_high_quality、render_outline_width、render_shadow_color 等)及其消费方式。

以下内容有意留给同级页面,本页仅作引用:

  • Spine 骨骼/动画数据模型的加载与行为状态机驱动 —— 参见 "Pet Runtime Core" 分类下的骨架动画相关页面;
  • 桌面窗口、透明窗体与主循环(ArkPets / Stage 层)的生命周期 —— 参见运行时核心的其他页面;
  • ArkConfig 配置体系的整体加载与持久化 —— 参见配置系统页面(本页只列出渲染消费的键)。

概述(Overview)

为什么需要两段式渲染?

桌宠角色的最终视觉效果并非"直接把骨架画到屏幕上"就能得到:

  1. 描边与阴影是全帧效果。要为一个任意姿态的角色生成描边,必须先拥有一张包含完整角色形状(含 Alpha)的纹理,然后对该纹理做邻域采样检测边缘。这正是 FBO 的作用——先把角色渲染成一张离屏纹理。
  2. 背景色与透明窗体合成。桌面窗口可以是半透明的,角色背后需要按配置填充背景色,并且整体不透明度要参与淡入淡出(例如窗口关闭动画),这些都必须在拿到角色纹理之后才能进行。
  3. 性能与质量可切换。后处理着色器提供高质量/低质量两档 GLSL 变体(ComplexFragment.glsl 与 ComplexFragmentLow.glsl),由配置项 render_shader_high_quality 在初始化时选择。

关键概念与术语:

术语含义
Render Pass(渲染通道)一次完整的绘制过程。本管线分为骨架通道与后处理通道
FBO(Frame Buffer Object)离屏帧缓冲,DynamicOrthographicCamara.getFBO() 返回,充当两通道之间的"纹理中转站"
RawShader骨架通道使用的轻量着色器封装,配合 TwoColorPolygonBatch 绘制 Spine 双色网格
EffectShader后处理通道使用的特效着色器封装,接收描边/阴影/透明度等 uniform
TransitionFloat带 easing 的浮点过渡器,用于描边与整体透明度的平滑动画

架构(Architecture)

Loading diagram...

分层解读:

  • 骨架层(sg_Pass1):SpineRenderPass 持有 TwoColorPolygonBatch(Spine 官方双色调色多边形批处理,支持 tint/dark 双色)与 SkeletonRenderer(设置 premultipliedAlpha(true),因为美术资源纹理按预乘 Alpha 导出)。着色器通过 batch.setShader(shader) 绑定为 RawShader,写入目标始终是相机 FBO。
  • 中转层(sg_Camera):DynamicOrthographicCamara(源码中的既有拼写)是两个通道之间唯一的耦合点。第一通道用 camera.combined 设置投影矩阵并用 camera.getFBO().begin()/end() 划定绘制范围;第二通道用 camera.getFBO().getColorBufferTexture() 把结果当作普通纹理读回。
  • 后处理层(sg_Pass2):PostProcessRenderPass 使用 SpriteBatch + EffectShader,先画背景纹理(setCanvas 生成的纯色 bgTexture),再画 FBO 纹理,特效全部在 EffectShader 的 uniform 中控制。
  • 配置层(sg_Config):所有视觉可调项集中在 ArkConfig,在 PostProcessRenderPass 构造函数中一次性读取并固化为字段;着色器质量变体在构造时决定,运行期不再切换。

渲染时序(每帧执行顺序)

Loading diagram...

该时序表明两个通道严格串行:第二通道必须等第一通道完成 FBO.end() 之后才能取到完整的角色纹理,因此每帧的角色像素数据流是 骨架 → FBO 纹理 → 特效合成 → 屏幕 的单向流水线。

实现详解(Main Content)

第一渲染通道:SpineRenderPass

SpineRenderPass 的职责非常单一:把一个 Spine Skeleton 画进相机 FBO,或者(在叠加模式下)以指定透明度画到"当前已绑定的帧缓冲"上。

构造阶段(SpineRenderPass.java#L25-L34):

java
1public SpineRenderPass(DynamicOrthographicCamara camera) { 2 this.camera = camera; 3 batch = new TwoColorPolygonBatch(); 4 renderer = new SkeletonRenderer(); 5 renderer.setPremultipliedAlpha(true); 6 /* Shader pedantic should be disabled to avoid uniform not-found error. */ 7 ShaderProgram.pedantic = false; 8 shader = new RawShader(); 9 batch.setShader(shader); 10}
  • TwoColorPolygonBatch 是 libGDX/Spine 生态的多边形批处理器,支持每个顶点携带 tint 与 dark 两种颜色,这是明日方舟立绘资源实现"染色+压暗"质感的前提;
  • setPremultipliedAlpha(true) 说明骨架纹理按 预乘 Alpha 处理,因此混合函数必须使用相应的 SRC 组合,否则半透明边缘会发白;
  • ShaderProgram.pedantic = false 是一个全局开关,用于容忍着色器中"未使用的 uniform"不报错——因为 RawShader 与 EffectShader 共享 libGDX 的严格校验机制,而 GLSL 编译器常会裁剪未引用的 uniform;
  • batch.setShader(shader) 把 RawShader 绑定为该批次的着色器,所有后续 draw 调用都会走它。

常规渲染 render(Skeleton)(SpineRenderPass.java#L39-L49):

java
1public void render(Skeleton skeleton) { 2 batch.getProjectionMatrix().set(camera.combined); 3 camera.getFBO().begin(); 4 shader.bind(); 5 shader.setAlpha(1.0f); 6 ScreenUtils.clear(0, 0, 0, 0, true); 7 batch.begin(); 8 renderer.draw(batch, skeleton); 9 batch.end(); 10 camera.getFBO().end(); 11}

逐行解读其控制流:

  1. batch.getProjectionMatrix().set(camera.combined) —— 批次的投影矩阵与相机同步,保证骨架坐标(Spine 世界坐标)正确映射到 FBO 像素;
  2. camera.getFBO().begin() —— 切换渲染目标为离屏 FBO;
  3. shader.bind() + shader.setAlpha(1.0f) —— 绑定 RawShader 并把整体不透明度设为满值(通道内不需要做透明过渡,过渡统一放在第二通道);
  4. ScreenUtils.clear(0, 0, 0, 0, true) —— 用 完全透明 的颜色清屏,这一步至关重要:后处理的描边/阴影都依赖 Alpha 通道判定"角色存在与否",残留的不透明像素会污染边缘检测;
  5. batch.begin() / renderer.draw(batch, skeleton) / batch.end() —— 标准批绘制,SkeletonRenderer 把骨架的 Attachment 逐个提交给批;
  6. camera.getFBO().end() —— 切回默认帧缓冲,为第二通道让路。

叠加渲染 renderAdditive(Skeleton, float)(SpineRenderPass.java#L56-L63):

java
1public void renderAdditive(Skeleton skeleton, float alpha) { 2 batch.getProjectionMatrix().set(camera.combined); 3 shader.bind(); 4 shader.setAlpha(alpha); 5 batch.begin(); 6 renderer.draw(batch, skeleton); 7 batch.end(); 8}

设计意图:与 render() 不同,renderAdditive 不 调用 FBO.begin()/end()、也不清屏,而是要求"调用者自己负责绑定与清理帧缓冲"。它通过 shader.setAlpha(alpha) 实现 淡入淡出式的骨架叠加(例如残影/换装过渡时把新旧骨架按不同 alpha 叠画到同一个 FBO 上),把帧缓冲生命周期的控制权交给外层,避免通道内部越权管理渲染目标。

第二渲染通道:PostProcessRenderPass

PostProcessRenderPass 负责把 FBO 纹理"成品化":加描边、加阴影、垫背景色、控制整体不透明度,并让这些参数平滑过渡。

构造阶段(PostProcessRenderPass.java#L41-L55):

java
1public PostProcessRenderPass(DynamicOrthographicCamara camera, ArkConfig config) { 2 this.camera = camera; 3 batch = new SpriteBatch(); 4 /* Shader pedantic should be disabled to avoid uniform-not-found error. */ 5 ShaderProgram.pedantic = false; 6 shader = new EffectShader(config.render_shader_high_quality); 7 batch.setShader(shader); 8 EasingFunction easingFunction = ArkConfig.getEasingFunctionFrom(config.transition_type); 9 float easingDuration = Math.max(0, config.transition_duration); 10 outlineAlpha = new TransitionFloat(easingFunction, easingDuration); 11 alpha = new TransitionFloat(easingFunction, easingDuration); 12 outlineWidth = config.render_outline_width; 13 outlineColor = new Color(Color.CLEAR); 14 shadowColor = ArkConfig.getGdxColorFrom(config.render_shadow_color); 15}

关键设计点:

  • new EffectShader(config.render_shader_high_quality) —— 质量变体在构造期一次性确定。高质量走 ComplexFragment.glsl,低质量走 ComplexFragmentLow.glsl(更省邻域采样,适合低端 GPU),运行中不热切换,避免着色器重编译带来的卡顿;
  • outlineAlpha 与 alpha 都包了一层 TransitionFloat,其 easing 函数与时长直接来自用户配置 transition_type / transition_duration(Math.max(0, ...) 兜底防止负时长);
  • outlineColor 初始为 Color.CLEAR(全透明),等待外部(例如鼠标交互逻辑)调用 setOutlineColor 显式上色;
  • shadowColor 从配置字符串经 ArkConfig.getGdxColorFrom 解析为 libGDX Color。

背景画布 setCanvas(Color)(PostProcessRenderPass.java#L60-L65):

java
1public void setCanvas(Color bgColor) { 2 Pixmap pixmap = new Pixmap(canvasMaxSize, canvasMaxSize, Format.RGBA8888); 3 pixmap.setColor(bgColor); 4 pixmap.fill(); 5 bgTexture = new Texture(pixmap); 6}

它用一张 canvasMaxSize × canvasMaxSize 的 RGBA8888 像素图填充纯色(可含 Alpha),生成 bgTexture。为什么需要它:透明桌宠窗口在某些合成器上需要不透明的"画布底色"才能正确显示,同时这块底色也是阴影效果的承接面。画布尺寸取的是常量上限(Const.canvasMaxSize),与相机实际宽高解耦,绘制时再用相机宽高裁剪。

每帧渲染 render()(PostProcessRenderPass.java#L91-L113):

java
1public void render() { 2 outlineAlpha.addProgress(Gdx.graphics.getDeltaTime()); 3 alpha.addProgress(Gdx.graphics.getDeltaTime()); 4 batch.getProjectionMatrix().set(camera.combined); 5 Texture passedTexture = camera.getFBO().getColorBufferTexture(); 6 shader.bind(); 7 shader.setOutlineColor(outlineColor); 8 shader.setOutlineWidth(outlineWidth); 9 shader.setOutlineAlpha(outlineAlpha.now()); 10 shader.setShadowColor(shadowColor); 11 shader.setTextureSize(passedTexture); 12 shader.setAlpha(alpha.now()); 13 ScreenUtils.clear(0, 0, 0, 0, true); 14 batch.begin(); 15 batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); 16 batch.draw(bgTexture, 0, 0); 17 batch.draw(passedTexture, 18 0, 0, 0, 0, camera.getWidth(), camera.getHeight(), 19 1, 1, 0, 20 0, 0, passedTexture.getWidth(), passedTexture.getHeight(), 21 false, true); 22 batch.end(); 23}

控制流拆解:

  1. 过渡推进:两个 TransitionFloat 各自按 getDeltaTime() 推进,因此描边与整体透明度的渐变帧率无关(时间驱动而非帧数驱动);
  2. 纹理获取:camera.getFBO().getColorBufferTexture() 把第一通道的输出当作输入纹理,这是两通道之间唯一的数据交接点;
  3. Uniform 灌装:依次设置描边颜色/宽度/透明度、阴影颜色、纹理尺寸(供 GLSL 内计算像素步长 1/size 做邻域采样)、整体透明度;
  4. 清屏:同样清成全透明,保持窗口无内容区域像素为 0 alpha,配合透明窗体;
  5. 混合模式:显式设置 GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA 的标准 alpha 混合;
  6. 两次 draw:先画 bgTexture(背景),再画 FBO 纹理。第二个 batch.draw 使用了完整的 10 参数 + 翻转标志重载,最后一个 flipY=true 是 垂直翻转——libGDX FBO 纹理的原点在左下角,而屏幕绘制坐标系原点在左上角,不翻转会导致角色上下颠倒。

属性设置接口:setOutlineAlpha(float)、setOutlineColor(Color)、setAlpha(float)(PostProcessRenderPass.java#L70-L86)都是"请求式"接口——它们只重置 TransitionFloat 的目标值(并 clamp 到 [0,1]),真正的视觉变化在后续每帧 render() 里被 easing 平滑拉过去,而不是立即跳变。这就是 ArkPets 描边/淡入淡出看起来"有弹性"的原因。

着色器封装与 GLSL 资源

渲染包中有两个着色器封装类,分别对应两个通道:

封装类所属通道GLSL 资源(assets/shaders/)用途
RawShaderSpineRenderPassPlainVertex.glsl / PlainFragment.glsl最小化顶点/片元处理,仅应用整体 alpha uniform
EffectShaderPostProcessRenderPassComplexVertex.glsl / ComplexFragment.glsl(高质量)或 ComplexFragmentLow.glsl(低质量)描边检测、阴影着色、纹理合成

两份 fragment 变体的差异是 ArkPets 性能分层策略的核心:ComplexFragment.glsl 保留完整精度的边缘采样,ComplexFragmentLow.glsl 牺牲部分效果换取更低的 GPU 占用,由 render_shader_high_quality 在初始化时二选一。着色器源码放在 assets/ 下随 jar 分发,Java 侧封装负责编译与 uniform 上传。

说明:RawShader.java / EffectShader.java 的内部实现细节未在本次采集范围内展开(源码预算所限),其对外可见的调用面已完整记录于上文两个 Render Pass 中(bind / setAlpha / setOutlineColor / setOutlineWidth / setOutlineAlpha / setShadowColor / setTextureSize)。

数据与纹理流(Data Model / Persistence)

本子系统不直接持久化数据,但其数据流具备明确的"纹理流水线"结构:

Loading diagram...
  • 输入:Spine Skeleton(由动画/行为子系统每帧更新姿态);
  • 中间产物:DynamicOrthographicCamara 的 FBO 颜色纹理(尺寸随相机,含有效 Alpha 通道);
  • 静态资源:bgTexture(setCanvas 时创建,一次生成、每帧复用);
  • 输出:屏幕/透明窗口像素,其中"角色不存在"区域保持 alpha=0。

使用示例(Usage Examples)

示例 1:搭建骨架渲染通道并渲染一帧(节选自 SpineRenderPass.java#L25-L49):

java
1// 绑定相机并创建通道 2SpineRenderPass pass = new SpineRenderPass(camera); 3// 每帧:把当前姿态的骨架画入 camera 的 FBO 4pass.render(skeleton); // 内部完成 FBO begin/clear/draw/end

示例 2:在已绑定的帧缓冲上叠加骨架(用于残影/过渡)(SpineRenderPass.java#L56-L63):

java
1// 调用方需自行绑定并清空帧缓冲 2camera.getFBO().begin(); 3ScreenUtils.clear(0, 0, 0, 0, true); 4pass.renderAdditive(oldSkeleton, 0.35f); // 以 35% 不透明度叠加旧姿态 5pass.renderAdditive(newSkeleton, 1.0f); // 再画满强度新姿态 6camera.getFBO().end();

示例 3:配置后处理通道并请求描边变化(节选自 PostProcessRenderPass.java#L41-L72):

java
1// 按用户配置初始化(质量档、描边宽度、阴影色、过渡曲线) 2PostProcessRenderPass post = new PostProcessRenderPass(camera, config); 3post.setCanvas(new Color(0, 0, 0, 0)); // 全透明画布 4// 鼠标悬停时请求显示描边(渐变由 TransitionFloat 完成) 5post.setOutlineColor(Color.WHITE); 6post.setOutlineAlpha(1.0f); // 目标值,非立即生效 7// 每帧调用:推进过渡并把 FBO 纹理合成到屏幕 8post.render();

配置项(Configuration Options)

以下配置键在 PostProcessRenderPass 构造函数中被读取并固化为渲染属性:

配置键类型消费位置作用
render_shader_high_quality布尔new EffectShader(...) 的入参选择 ComplexFragment.glsl(高质量)或 ComplexFragmentLow.glsl(低质量)片元着色器变体
render_outline_width浮点outlineWidth 字段描边像素宽度,直接作为 EffectShader 的 uniform
render_shadow_color颜色字符串ArkConfig.getGdxColorFrom(...)阴影颜色(RGBA),解析为 libGDX Color 后灌入着色器
transition_type枚举字符串ArkConfig.getEasingFunctionFrom(...)TransitionFloat 使用的缓动函数类型(同时作用于描边 alpha 与整体 alpha)
transition_duration浮点(秒)Math.max(0, ...)过渡时长,0 表示无渐变

另有常量 Const.canvasMaxSize(setCanvas 中使用)决定背景画布的最大边长。

API 参考(API Reference)

SpineRenderPass(DynamicOrthographicCamara camera): SpineRenderPass

初始化绑定到指定相机的骨架渲染通道。

参数:

  • camera(DynamicOrthographicCamara):其 FBO 作为渲染目标,其 combined 矩阵作为投影。

行为:创建 TwoColorPolygonBatch、SkeletonRenderer(premultipliedAlpha=true)、RawShader,并全局设置 ShaderProgram.pedantic = false。

SpineRenderPass.render(Skeleton skeleton): void

把骨架完整绘制到相机 FBO。

参数:

  • skeleton(Skeleton):待渲染的 Spine 骨架(姿态由调用方先行更新)。

副作用:清空 FBO 为全透明,随后绘制骨架,整体 alpha 固定为 1.0。

SpineRenderPass.renderAdditive(Skeleton skeleton, float alpha): void

把骨架按给定透明度叠加绘制到 当前已绑定 的帧缓冲。

参数:

  • skeleton(Skeleton):待叠加的骨架;
  • alpha(float):叠加透明度。

前置条件:调用方必须已绑定帧缓冲并完成清理(本方法不执行 FBO.begin/end 与清屏)。

PostProcessRenderPass(DynamicOrthographicCamara camera, ArkConfig config): PostProcessRenderPass

初始化后处理通道。

参数:

  • camera(DynamicOrthographicCamara):其 FBO 纹理作为输入;
  • config(ArkConfig):渲染相关配置集合(见上表)。

行为:创建 SpriteBatch 与 EffectShader(质量档由 render_shader_high_quality 决定),初始化两个 TransitionFloat、描边宽度/颜色与阴影颜色。

PostProcessRenderPass.setCanvas(Color bgColor): void

生成纯色背景纹理。

参数:bgColor(Color):可含 Alpha 的背景色。

PostProcessRenderPass.setOutlineAlpha(float newAlpha): void / setAlpha(float newAlpha): void

请求描边透明度 / 整体透明度过渡到 newAlpha(自动 clamp 到 [0,1])。实际渐变由后续每帧 render() 中的 TransitionFloat 完成。

PostProcessRenderPass.setOutlineColor(Color color): void

设置描边颜色(立即生效于下一次 render())。

PostProcessRenderPass.render(): void

推进过渡计时,灌装全部特效 uniform,并把背景纹理与 FBO 纹理(垂直翻转)合成绘制到屏幕。每帧调用一次。

失败模式、边界与并发(Failure Modes, Edge Cases & Concurrency)

  • Alpha 清屏不可省略:两个通道都用 ScreenUtils.clear(0, 0, 0, 0, true) 清成全透明。若 FBO 残留不透明像素,EffectShader 的边缘检测会把"上一次的残影"也当作角色形状参与描边/阴影,造成鬼影。因此第一通道每次 render() 都强制清屏,而 renderAdditive 故意不清屏(叠加语义),把风险转移给调用方负责。
  • renderAdditive 的隐式契约:它依赖调用方绑定帧缓冲,属于"受信调用"设计。若在未绑定 FBO 的情况下调用,绘制会落到当前默认帧缓冲,直接污染屏幕内容——这是有意为之的控制权下放,换取残影类效果的灵活性。
  • FBO 纹理坐标翻转:batch.draw(..., false, true) 中的 flipY=true 处理 libGDX FBO 的左下原点与屏幕左上原点的坐标差异。遗漏该参数是此类管线最常见的"角色上下颠倒"缺陷来源。
  • 预乘 Alpha 的一致性:SkeletonRenderer.setPremultipliedAlpha(true) 要求纹理资源确实是预乘格式;第二通道使用 GL_SRC_ALPHA/GL_ONE_MINUS_SRC_ALPHA 标准混合承接结果。两段管线对 Alpha 语义的约定必须一致,否则边缘出现白色或黑色描圈。
  • pedantic 全局开关:ShaderProgram.pedantic = false 是 libGDX 的 静态全局 设置,两处通道构造时都显式再设一次以保险。其代价是真实的着色器 uniform 拼写错误也会被静默吞掉,属于"容错优先于严格校验"的取舍。
  • 质量档不可热切换:EffectShader 的变体在构造期固定,运行期没有提供重建接口;用户改配置后需要重启实例(或重新构造通道)才能生效。
  • 线程模型:本管线遵循 libGDX 单渲染线程模型,render() 中的 Gdx.graphics.getDeltaTime() 表明所有状态推进都发生在渲染线程内;setOutlineAlpha/setAlpha/setOutlineColor 设计为"请求式"(只重置目标),因此即使从逻辑线程发起请求,实际的 uniform 写入仍统一发生在渲染帧中,避免了 GL 上下文跨线程问题。
  • 时长兜底:Math.max(0, config.transition_duration) 防御性处理负时长;setOutlineAlpha/setAlpha 对输入做 [0,1] clamp,防御越界 alpha 导致的混合异常。

性能与运维(Performance & Operations)

  • 单次离屏 + 单次合成:每帧恰好 1 次 FBO 绘制 + 2 次 SpriteBatch draw(背景 + 角色),开销集中在 EffectShader 的片元处理(描边/阴影采样)。低端设备可通过 render_shader_high_quality=false 切到 ComplexFragmentLow.glsl 降低片元成本。
  • 背景纹理复用:bgTexture 在 setCanvas 时一次生成、每帧复用,避免每帧重建 Pixmap 的开销;但重复调用 setCanvas 会创建新 Texture 而不释放旧纹理,调用方应避免高频换底色。
  • 时间驱动的过渡:过渡按 delta 时间推进而非帧计数,帧率波动不会改变渐变时长(时长恒等于 transition_duration 秒)。
  • 运维建议:调整 render_outline_width 时注意过大的描边宽度会显著增加片元着色器邻域采样命中范围;在透明窗口合成器异常的平台上,可通过 setCanvas 提供不透明底色规避。

扩展点(Extension Points)

  • 新增后处理效果:效果集中在 EffectShader 及其 GLSL 变体中,Java 侧只需在封装类上增加 setXxx uniform 方法,并在 PostProcessRenderPass.render() 中灌装;由于 ShaderProgram.pedantic=false,旧版本着色器缺 uniform 不会报错,利于渐进式升级。
  • 新增叠加绘制语义:renderAdditive 已示范"通道不管理帧缓冲、由调用方控制"的模式,可据此扩展残影、幻影、多重叠装等效果,而不必改动 SpineRenderPass 核心路径。
  • 相机适配:所有绘制均以 DynamicOrthographicCamara 的 combined 与宽高为参照,画布层另有 Const.canvasMaxSize 兜底,因此缩放画布/相机不需要改动两个通道本身。
  • SpineRenderPass.java —— 骨架渲染通道完整实现
  • PostProcessRenderPass.java —— 后处理渲染通道完整实现
  • assets/shaders/ —— ComplexFragment.glsl 等四份 GLSL 着色器源
  • 骨骼动画与行为状态机、透明窗口与主循环、ArkConfig 配置体系 —— 参见 "Pet Runtime Core" 分类下的对应兄弟页面