|
| 1 | +# Celshade |
| 2 | + |
| 3 | +## 概述 |
| 4 | +Celshade 是 `Plugins/Renderer` 中 StudioModel 的风格化着色总管线,覆盖了基础 Celshade(分段明暗)、Outline、RimLight/RimDark、HairSpecular,以及与其强耦合的 HairShadow 与 Eyebrow-Passthrough(HairFaceColorMix)链路。 |
| 5 | + |
| 6 | +当前实现采用“Analysis + 多离屏几何 Pass + Normal Pass 屏幕空间采样”的组合: |
| 7 | +- Analysis 统计模型是否含 FACE/HAIR/透明网格; |
| 8 | +- HairShadow pass 把 FACE/HAIR 写入 FBO3 depth/stencil; |
| 9 | +- HairFaceColorMix pass 把 FACE 颜色+alpha 写入 FBO4; |
| 10 | +- Normal pass 在 FACE/Hair 分支分别采样 stencil 与 mixDiffuse/depth,完成阴影与透发效果。 |
| 11 | + |
| 12 | +## 职责 |
| 13 | +- 维护 Celshade 资产标记协议(`STUDIO_NF_CELSHADE` / `STUDIO_NF_CELSHADE_FACE` / `STUDIO_NF_CELSHADE_HAIR`)与外部配置入口(`[model]_external.txt`)。 |
| 14 | +- 在分析阶段统计 `r_draw_hasface / r_draw_hashair / r_draw_hasalpha / r_draw_hasadditive / r_draw_hasoutline`,驱动后续 pass 调度。 |
| 15 | +- 通过 `renderfx` 切换 HairShadow 与 HairFaceColorMix 几何 pass,并管理 FBO3/FBO4 的绑定与清理。 |
| 16 | +- 在 DrawPass 中构建 `StudioProgramState`,绑定 stencil/mixDiffuse/depth 等纹理输入并切换 shader 宏变体。 |
| 17 | +- 在 shader 侧实现 face/body celshade、rim light/dark、hair Kajiya specular、face stencil 阴影压暗、hair 屏幕空间颜色混合。 |
| 18 | +- 提供 cvar + 模型级覆盖(`studio_celshade_control`)双层参数体系,并支持 `r_studio_celshade_debug` 调试分支。 |
| 19 | + |
| 20 | +## 涉及文件 (不要带行号) |
| 21 | +- docs/Renderer.md |
| 22 | +- Plugins/Renderer/enginedef.h |
| 23 | +- Plugins/Renderer/gl_common.h |
| 24 | +- Plugins/Renderer/gl_local.h |
| 25 | +- Plugins/Renderer/gl_rmain.cpp |
| 26 | +- Plugins/Renderer/gl_studio.cpp |
| 27 | +- Build/svencoop/renderer/shader/common.h |
| 28 | +- Build/svencoop/renderer/shader/studio_shader.vert.glsl |
| 29 | +- Build/svencoop/renderer/shader/studio_shader.frag.glsl |
| 30 | + |
| 31 | +## 架构 |
| 32 | +整体流程(StudioModel Celshade): |
| 33 | + |
| 34 | +```mermaid |
| 35 | +flowchart TD |
| 36 | + A["model_external.txt"] --> B["studio_texture / studio_celshade_control"] |
| 37 | + B --> C["Analysis Pass"] |
| 38 | + C --> D{"has FACE + HAIR"} |
| 39 | + D -- "否" --> E["Normal Pass + Outline"] |
| 40 | + D -- "是" --> F{"r_studio_hair_shadow > 0"} |
| 41 | + F -- "是" --> G["HairShadow Pass 写 FBO3 depth stencil"] |
| 42 | + F -- "否" --> H["跳过 HairShadow"] |
| 43 | + G --> I["HairFaceColorMix Pass 写 FBO4 color depth"] |
| 44 | + H --> I |
| 45 | + I --> E |
| 46 | + E --> J["FACE 读取 FBO3 stencil 并压暗阴影"] |
| 47 | + E --> K["HAIR 读取 FBO4 mixDiffuse depth 并执行透发混色"] |
| 48 | +``` |
| 49 | + |
| 50 | +关键实现点: |
| 51 | +- 启用判定: |
| 52 | + - `R_StudioHasHairShadow()`:`r_draw_hashair && r_draw_hasface && r_studio_hair_shadow>0 && !R_IsRenderingShadowView()`。 |
| 53 | + - `R_StudioHasHairFaceColorMix()`:`r_draw_hashair && r_draw_hasface && !R_IsRenderingShadowView()`。 |
| 54 | +- 状态位到 shader 宏:`R_UseStudioProgram()` 将 `StudioProgramState` 映射为 `#define`(如 `HAIR_SHADOW_ENABLED`、`HAIR_FACE_COLOR_MIX_ENABLED`、`STENCIL_TEXTURE_ENABLED`、`MIX_DIFFUSE_TEXTURE_ENABLED`、`DEPTH_TEXTURE_ENABLED`)。 |
| 55 | +- 纠错逻辑:若出现 `STUDIO_NF_CELSHADE_FACE/HAIR` 但缺少 `STUDIO_NF_CELSHADE`,`R_UseStudioProgram()` 会自动补上 `STUDIO_NF_CELSHADE`。 |
| 56 | +- 全局开关:`R_StudioDrawMesh()` 在 mesh 级别执行 `if (!r_studio_celshade->value) flags &= ~STUDIO_NF_CELSHADE_ALLBITS;`,统一关断 Celshade 扩展位。 |
| 57 | + |
| 58 | +### Shader 核心 |
| 59 | +- `R_StudioCelShade()`(frag): |
| 60 | + - 使用 `smoothstep(r_celshade_midpoint ± r_celshade_softness)` 生成明暗分段。 |
| 61 | + - FACE 分支按头部朝向(`v_headfwd`)修正光向,减少极角下脸部跳变。 |
| 62 | + - FACE + stencil 分支:命中 `STENCIL_MASK_HAS_SHADOW` 时强制 `litOrShadowArea = 0.0`。 |
| 63 | + - 非 FACE 分支叠加 rim light / rim dark;HAIR 分支叠加 Kajiya strand specular。 |
| 64 | +- `R_GenerateAdjustedNormal()`:FACE 允许在原法线与球化法线之间插值(`flNormalMask`);与文档“蓝通道控制 face 球化法线比例”一致。 |
| 65 | +- HairFaceColorMix pass(frag `HAIR_FACE_COLOR_MIX_ENABLED`):输出 face `diffuseColor`;若有 specular 贴图则 `diffuseColor.a *= rawSpecularColor.a`。 |
| 66 | +- Hair normal pass 混色(frag `STUDIO_NF_CELSHADE_HAIR && MIX_DIFFUSE_TEXTURE_ENABLED`): |
| 67 | + - 采样 `depthTex` 重建 `sceneWorldPos`; |
| 68 | + - 仅在 `distance(sceneWorldPos, vWorldPos) < 4.0` 时,使用 `mixDiffuseColor.a` 混合 face/hair 颜色,降低跨层误混。 |
| 69 | +- HairShadow 顶点偏移(vert `HAIR_SHADOW_ENABLED && STUDIO_NF_CELSHADE_HAIR`):沿调整后的光向 + Z 偏移应用 `r_hair_shadow_offset`。 |
| 70 | + |
| 71 | +## 依赖 |
| 72 | +- 标记位与 renderfx 协议: |
| 73 | + - `STUDIO_NF_CELSHADE / FACE / HAIR`、`STUDIO_NF_CELSHADE_ALLBITS`。 |
| 74 | + - `kRenderFxDrawHairShadowGeometry`、`kRenderFxDrawHairFaceColorMixGeometry`、`kRenderFxDrawOutline`。 |
| 75 | +- ProgramState 位: |
| 76 | + - `STUDIO_HAIR_SHADOW_ENABLED`、`STUDIO_HAIR_FACE_COLOR_MIX_ENABLED`、`STUDIO_STENCIL_TEXTURE_ENABLED`、`STUDIO_MIX_DIFFUSE_TEXTURE_ENABLED`、`STUDIO_DEPTH_TEXTURE_ENABLED`。 |
| 77 | +- 离屏资源: |
| 78 | + - `s_BackBufferFBO3`(stencil/depth 采样来源,含 stencil view)。 |
| 79 | + - `s_BackBufferFBO4`(face mix diffuse + depth 采样来源)。 |
| 80 | +- 纹理槽位: |
| 81 | + - `STUDIO_BIND_TEXTURE_STENCIL=6`、`STUDIO_BIND_TEXTURE_MIX_DIFFUSE=7`、`STUDIO_BIND_TEXTURE_DEPTH=8`。 |
| 82 | +- stencil 位语义: |
| 83 | + - `STENCIL_MASK_HAS_SHADOW=0x1`、`STENCIL_MASK_HAS_FACE=0x2`。 |
| 84 | +- 参数来源: |
| 85 | + - 全局 cvar:`r_studio_celshade*`、`r_studio_hair_*`、`r_studio_outline*`、`r_studio_rim*`、`r_studio_celshade_debug`。 |
| 86 | + - 模型级覆盖:`studio_celshade_control`(`R_StudioLoadExternalFile_Celshade`)。 |
| 87 | +- 资产依赖: |
| 88 | + - `studio_texture.flags`,以及可选 `replacetexture/speculartexture`(用于 eyebrow alpha 与 HDR 贴图工作流)。 |
| 89 | + |
| 90 | +## 注意事项 |
| 91 | +- `r_studio_celshade=0` 会在 mesh 入口清空 `STUDIO_NF_CELSHADE_ALLBITS`,所有 Celshade 扩展链路(含 HairShadow/HairFaceColorMix)都会失效。 |
| 92 | +- HairFaceColorMix 启用条件不依赖 `r_studio_hair_shadow`:只要存在 FACE+HAIR 且非 ShadowView 就会执行该 pass。 |
| 93 | +- Hair 混色阈值是固定世界空间距离 `4.0`,极端比例模型可能需要额外调参。 |
| 94 | +- HairShadow pass 文档将 `r_studio_hair_shadow_offset` 描述为“screen space offset”,但实现是顶点几何偏移后再投影。 |
| 95 | +- HairFaceColorMix pass 明确“不写 stencil”;face 阴影判定依赖前序 HairShadow pass 写入的 FBO3 stencil。 |
| 96 | +- 若 `s_BackBufferFBO3.s_hBackBufferStencilView` 或 `s_BackBufferFBO4` 深度视图不可用,会导致阴影判定/透发混色退化。 |
| 97 | +- GlowShell 分支会移除 Celshade 位并改为 `STUDIO_NF_FLATSHADE`,因此 Celshade 视觉不会直接叠到 GlowShell pass。 |
| 98 | + |
| 99 | +## 调用方(可选) |
| 100 | +- `StudioRenderModel_Template`:驱动 Analysis、HairShadow、HairFaceColorMix、Normal、Outline 等 pass。 |
| 101 | +- `R_StudioDrawMesh`:统一处理 mesh flags(含 Celshade 开关),并分流到 AnalysisPass/DrawPass。 |
| 102 | +- `R_StudioDrawMesh_AnalysisPass`:收集 `hasface/hashair` 等统计位。 |
| 103 | +- `R_StudioDrawMesh_DrawPass`:根据 `renderfx + flags` 组装 `StudioProgramState`,完成纹理绑定、状态设置与 `glDrawElements`。 |
| 104 | +- `R_UseStudioProgram`:状态位 -> shader 宏变体编译/缓存。 |
| 105 | +- `R_CreateStudioRenderData` + `R_StudioLoadExternalFile`:初始化 CelshadeControl,并从 `studio_texture/studio_celshade_control` 加载覆盖参数。 |
| 106 | + |
| 107 | +## Pass渲染状态设置(按 Pass / Geometry 细分,重点 Stencil) |
| 108 | + |
| 109 | +### 1) Analysis Pass |
| 110 | +- 入口:`r_draw_analyzingstudio = true`。 |
| 111 | +- 行为:仅统计 `r_draw_hasface / r_draw_hashair / r_draw_hasalpha / r_draw_hasadditive`。 |
| 112 | +- stencil:无写入。 |
| 113 | + |
| 114 | +### 2) HairShadow Geometry Pass(`renderfx = kRenderFxDrawHairShadowGeometry`) |
| 115 | +- 调度:绑定 `s_BackBufferFBO3`,清 `depth/stencil`,`glDrawBuffer(GL_NONE)`。 |
| 116 | +- Geometry 过滤:仅 `STUDIO_NF_CELSHADE_FACE` 或 `STUDIO_NF_CELSHADE_HAIR`。 |
| 117 | +- stencil 写入: |
| 118 | + - FACE:`GL_BeginStencilWrite(STENCIL_MASK_HAS_FACE, STENCIL_MASK_HAS_FACE | STENCIL_MASK_HAS_SHADOW)`。 |
| 119 | + - HAIR:`GL_BeginStencilWrite(STENCIL_MASK_HAS_SHADOW, STENCIL_MASK_HAS_SHADOW)`。 |
| 120 | +- 状态:`glDisable(GL_BLEND)` + `glDepthMask(GL_TRUE)`;默认 `glCullFace(GL_FRONT)`,`DOUBLE_FACE` 则禁用剔除。 |
| 121 | + |
| 122 | +### 3) HairFaceColorMix Geometry Pass(`renderfx = kRenderFxDrawHairFaceColorMixGeometry`) |
| 123 | +- 调度:绑定 `s_BackBufferFBO4`,清 color/depth/stencil。 |
| 124 | +- Geometry 过滤:仅 `STUDIO_NF_CELSHADE_FACE`。 |
| 125 | +- 输出:face `diffuseColor`(可与 `specular.a` 相乘后写入 alpha)。 |
| 126 | +- stencil:不写(代码注释 `No need to write stencil here`)。 |
| 127 | +- 状态:`glDisable(GL_BLEND)` + `glDepthMask(GL_TRUE)`。 |
| 128 | + |
| 129 | +### 4) Normal Pass(FACE) |
| 130 | +- 绑定:非 HairShadow/HairFaceColorMix 情况下,为 FACE 尝试绑定 `s_BackBufferFBO3.s_hBackBufferStencilView` 到 slot 6。 |
| 131 | +- shader:`STUDIO_NF_CELSHADE_FACE && STENCIL_TEXTURE_ENABLED` 时读取 stencil,命中 `HAS_SHADOW` 强制阴影。 |
| 132 | + |
| 133 | +### 5) Normal Pass(HAIR) |
| 134 | +- 绑定:若启用 HairFaceColorMix,为 HAIR 绑定 `mixDiffuse`(slot 7)与 `depth`(slot 8)。 |
| 135 | +- shader:重建场景世界坐标 + 距离阈值判定后,按 `mixDiffuse.a` 融合 face/hair 颜色(眉毛透发核心)。 |
| 136 | + |
| 137 | +### 6) DrawPass 收尾(每个 mesh) |
| 138 | +- 恢复:`glDepthMask(GL_TRUE)`、`glDisable(GL_BLEND)`、`glEnable(GL_CULL_FACE)`、`glEnable(GL_DEPTH_TEST)`、`glDepthFunc(GL_LEQUAL)`。 |
| 139 | +- 结束 stencil:`GL_EndStencil()`。 |
| 140 | +- 解绑:按状态位解绑 depth/mixDiffuse/stencil/normal/parallax/specular/animated 纹理。 |
0 commit comments