From 089e0fc7b45783d37d82bef103b22e7a22f61536 Mon Sep 17 00:00:00 2001 From: suzmii Date: Wed, 5 Aug 2026 15:26:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8E=BB=E9=99=A4=E9=87=8D=E5=A4=8D=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E5=B9=B6=E5=8A=A0=E5=9B=BA=E5=9C=BA=E6=99=AF=20Prompt?= =?UTF-8?q?=20=E7=BB=84=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除四套预设画风正文中的重复视觉风格标题 受控比例与画幅方向先替换,用户文本最后一次性拼入 用户输入中的模板占位符保持原文不再二次展开 补充标题唯一性与占位符注入回归测试 --- ...方案】图片画布游戏场景生成链路-2026-08-04.md | 2 + .../api-server/src/editor_scene_prompt.rs | 65 +++++++++++++++---- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md b/docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md index b289c7084..b7a145d42 100644 --- a/docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md +++ b/docs/technical/【技术方案】图片画布游戏场景生成链路-2026-08-04.md @@ -98,6 +98,8 @@ canvasCompletion? - 删除文档章节编号和 `[图片]` 占位。 - 保留场景结构约束和四套完整预设风格正文。 - 自定义模式只使用用户自定义画风,不附加预设风格正文。 +- `视觉风格:` 标题只由场景结构模板输出,预设正文不重复携带标题。 +- 先替换受控的比例和画幅方向,再切分静态用户占位符并一次性拼入画面内容与画风正文;用户输入中的模板占位符按原文保留,不参与后续替换。 画幅方向按标准化比例映射: diff --git a/server-rs/crates/api-server/src/editor_scene_prompt.rs b/server-rs/crates/api-server/src/editor_scene_prompt.rs index f8fb146e2..a0e614ba6 100644 --- a/server-rs/crates/api-server/src/editor_scene_prompt.rs +++ b/server-rs/crates/api-server/src/editor_scene_prompt.rs @@ -5,6 +5,10 @@ const STYLE_WATERCOLOR: &str = "watercolor"; const STYLE_FLAT: &str = "flat"; const STYLE_STOP_MOTION: &str = "stop-motion"; const STYLE_CUSTOM: &str = "custom"; +const SCENE_CONTENT_PLACEHOLDER: &str = "{{SCENE_CONTENT}}"; +const STYLE_PROMPT_PLACEHOLDER: &str = "{{STYLE_PROMPT}}"; +const ASPECT_RATIO_PLACEHOLDER: &str = "{{ASPECT_RATIO}}"; +const FRAME_ORIENTATION_PLACEHOLDER: &str = "{{FRAME_ORIENTATION}}"; const EDITOR_SCENE_STRUCTURE_PROMPT: &str = r###"生成一张静态、单幅、单层合成完成的叙事场景背景,可直接用于视觉小说、对话驱动型游戏、故事事件、章节画面或环境展示。 画面内容: @@ -33,8 +37,7 @@ const EDITOR_SCENE_STRUCTURE_PROMPT: &str = r###"生成一张静态、单幅、 - 不把按钮、槽位、交互标记、任务标记、导航信息、碰撞体或其他玩法指示烘烤进图片。 - 输出一张边缘完整、完成度均匀、可直接用于游戏的精修环境背景图。"###; -const EDITOR_SCENE_ANIME_PROMPT: &str = r###"视觉风格: -将“日系动画背景美术”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 +const EDITOR_SCENE_ANIME_PROMPT: &str = r###"将“日系动画背景美术”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 风格方向: 采用成熟日系二维动画背景美术常见的精修数字绘景体系。最终图像应首先被识别为一张手绘完成的二维栅格环境绘景,其次才被识别为一个具有可信空间和材质的真实地点。 整体使用“选择性线稿+不透明数字上色+局部柔和绘制”的线面结合方式,不模拟实拍照片,也不把整幅环境处理成角色插画式的硬边赛璐璐画面。 @@ -55,8 +58,7 @@ const EDITOR_SCENE_ANIME_PROMPT: &str = r###"视觉风格: 避免照片写实、照片描摹和照片拼贴;避免真实PBR材质、光滑三维渲染和游戏引擎截图;避免全场景统一粗黑漫画轮廓、角色式硬边赛璐璐阴影和过度平面卡通化;避免油画厚涂、概念设计式狂乱笔触、透明水彩渗化、水粉纸面和明显传统纸张纹理;避免绝对平滑的矢量色块;避免橙青电影调色、HDR、压死黑位、过量泛光、镜头光晕、色差、散景和浅景深;避免用高密度照片纹理、随机噪点或过度锐化制造虚假的精细度。 最终效果应当是:空间可信、形体经过动画化归纳、细有色线与手绘色面自然融合、色彩明净但不廉价、光影具有设计感而不电影写实的精修日系动画环境背景美术。"###; -const EDITOR_SCENE_WATERCOLOR_PROMPT: &str = r###"视觉风格: -将“透明水彩环境绘景”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 +const EDITOR_SCENE_WATERCOLOR_PROMPT: &str = r###"将“透明水彩环境绘景”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 风格方向: 使用真实冷压棉浆水彩纸、清水与透明水彩颜料完成精修二维环境绘景。最终图像必须首先被识别为一张在实体水彩纸上逐层绘制的透明水彩作品,而不是普通数字插画叠加纸纹或水渍滤镜。 采用“透明罩染、纸白发光、色彩叠加、边缘随水分变化”的绘制体系。画面可以保留完整、丰富的固有色,但所有色彩都必须具有透明度,底层颜色和纸张本白能够透过上层颜料继续参与最终成像。 @@ -80,8 +82,7 @@ const EDITOR_SCENE_WATERCOLOR_PROMPT: &str = r###"视觉风格: 避免照片描摹、照片拼贴、真实PBR材质、三维渲染和游戏引擎截图;避免HDR、塑料高光、镜头光晕、散景、景深、过度锐化和电影调色;避免仅在普通数字插画上叠加纸纹、水渍、颗粒或边缘滤镜;避免随机颜料爆炸、失控泼洒和水迹破坏主要形体。 最终效果应当是:完整可信的环境形体由透明罩染、纸白发光、色彩叠加、湿碰湿扩散、干湿边缘、水渍回流和颜料颗粒共同构成的精修透明水彩环境绘景。"###; -const EDITOR_SCENE_FLAT_PROMPT: &str = r###"视觉风格: -将“平面几何场景美术”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 +const EDITOR_SCENE_FLAT_PROMPT: &str = r###"将“平面几何场景美术”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 风格方向: 使用高度图形化的二维平面几何语言重新构成完整环境。最终图像必须首先被识别为经过精密形状设计和色彩规划的平面场景插画,而不是照片描摹、手绘媒介作品、低多边形三维或带文字的信息海报。 采用“大型几何形状+有限纯色+离散明暗+形状重叠造空间”的视觉体系。对象依靠轮廓、面积、方向和色彩关系被识别,不依赖真实纹理、复杂反射或细密笔触。 @@ -102,8 +103,7 @@ const EDITOR_SCENE_FLAT_PROMPT: &str = r###"视觉风格: 避免自动描摹照片形成的碎裂轮廓;避免廉价图标、信息图、UI面板、海报排版和企业矢量图库感;避免仅通过减少细节得到空洞色块,而没有经过设计的大中小形状、色彩面积和视觉节奏。 最终效果应当是:完整环境被归纳为清楚几何形状、有限色彩、离散明暗和有秩序的形状重叠,形成高可读、低纹理、色彩关系明确的精修二维平面场景美术。"###; -const EDITOR_SCENE_STOP_MOTION_PROMPT: &str = r###"视觉风格: -将“手工模型定格场景”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 +const EDITOR_SCENE_STOP_MOTION_PROMPT: &str = r###"将“手工模型定格场景”作为锁定的视觉风格。无论画面内容如何变化,以下视觉规律都必须清楚可见。 风格方向: 使用黏土、橡皮泥、泡沫、木料、布料、涂装纸板、金属丝和混合手工材料搭建真实微缩环境,并以定格动画布景摄影方式呈现。最终图像必须首先被识别为一座实际存在的手工模型布景,而不是电脑三维、照片级真实地点或平面插画。 采用“可触摸实体体积+手工材料证据+轻微制作误差+真实棚拍光影”的视觉体系。所有对象都应像能够被制作者拿起、摆放和逐格拍摄。 @@ -161,14 +161,25 @@ pub(crate) fn build_editor_scene_prompt( return Err("sceneContent 不能为空"); } let style_prompt = resolve_editor_scene_style_prompt(style_preset, custom_style)?; - Ok(EDITOR_SCENE_STRUCTURE_PROMPT - .replace("{{SCENE_CONTENT}}", scene_content) - .replace("{{STYLE_PROMPT}}", style_prompt.as_ref()) - .replace("{{ASPECT_RATIO}}", normalized_aspect_ratio) + // 比例和画幅方向来自后端白名单标准化结果,可以在插入用户文本前安全替换。 + let controlled_structure = EDITOR_SCENE_STRUCTURE_PROMPT + .replace(ASPECT_RATIO_PLACEHOLDER, normalized_aspect_ratio) .replace( - "{{FRAME_ORIENTATION}}", + FRAME_ORIENTATION_PLACEHOLDER, editor_scene_frame_orientation(normalized_aspect_ratio), - )) + ); + let (before_scene_content, after_scene_content) = controlled_structure + .split_once(SCENE_CONTENT_PLACEHOLDER) + .ok_or("场景 Prompt 模板缺少 SCENE_CONTENT 占位符")?; + let (between_scene_and_style, after_style_prompt) = after_scene_content + .split_once(STYLE_PROMPT_PLACEHOLDER) + .ok_or("场景 Prompt 模板缺少 STYLE_PROMPT 占位符")?; + + // 用户画面内容和自定义画风必须最后一次性拼入;拼入后禁止再对整段 Prompt + // 执行模板替换,避免用户原文中的 {{...}} token 被当成内部占位符展开。 + Ok(format!( + "{before_scene_content}{scene_content}{between_scene_and_style}{style_prompt}{after_style_prompt}" + )) } #[cfg(test)] @@ -202,6 +213,7 @@ mod tests { prompt.contains(expected), "missing preset marker: {expected}" ); + assert_eq!(prompt.matches("视觉风格:").count(), 1); } } @@ -225,6 +237,19 @@ mod tests { assert!(build_editor_scene_prompt("场景", "unknown", None, "16:9").is_err()); } + #[test] + fn scene_prompt_does_not_expand_placeholder_tokens_from_user_inputs() { + let scene_content = "保留 {{STYLE_PROMPT}}、{{ASPECT_RATIO}} 与 {{FRAME_ORIENTATION}} 原文"; + let custom_style = "保留 {{SCENE_CONTENT}}、{{ASPECT_RATIO}} 与 {{FRAME_ORIENTATION}} 原文"; + let prompt = + build_editor_scene_prompt(scene_content, STYLE_CUSTOM, Some(custom_style), "16:9") + .expect("custom scene prompt"); + + assert!(prompt.contains(scene_content)); + assert!(prompt.contains(custom_style)); + assert!(prompt.contains("图片比例 16:9 一致(横幅)")); + } + #[test] fn scene_prompt_source_does_not_include_document_placeholders() { for prompt in [ @@ -238,5 +263,17 @@ mod tests { assert!(!prompt.contains("8.1")); assert!(!prompt.contains("8.2")); } + assert_eq!( + EDITOR_SCENE_STRUCTURE_PROMPT + .matches(SCENE_CONTENT_PLACEHOLDER) + .count(), + 1 + ); + assert_eq!( + EDITOR_SCENE_STRUCTURE_PROMPT + .matches(STYLE_PROMPT_PLACEHOLDER) + .count(), + 1 + ); } }