反对司令

成熟小鸟
2022-03-16
191
83
思考时间
3 天 13 小时 53 分钟
33
地址
内容设置了阅读权限,浏览时请确保您登陆了樹账户。
之所以没发在社区,是因为社区无法设置浏览权限,不想尝试接受中文互联网的“偷源精神”洗礼。

1、关于希望达到的目的
行动先于思考的心血来潮的产物,无意义行为,主要是因为好玩,顺带寻找前 ai 时代的天花板在哪,后知后觉发现可以用在翠鸟加入方式的创新上。可仅作为加入前引导,不影响后续手册说明。

2、如何实现
打算设计成galgame的形式在网页端部署。选项类同问卷,玩家的每一次选择均会被系统收集和筛选,错误的选择所反映的错误价值观将视为攻略失败无法进入后续线路,也就相当于未通过翠鸟测验。
玩家理应只有1-2次机会,所以账号的注册应 qq 等账号相关联,以免玩家反复注册攻略。

3、如何把控故事内容的纯净
悖论:过多的描述和设定会使流程冗长且不纯净,但过少的文本又缺乏代入感和不符合 galgame 一贯的长文本叙事,因此很难定夺。
如果按照 44 分 27 秒(问卷平均答题时长)左右的设计(听语音按照 1 小时 1.5 - 2w 的文本里量计算),文本量应在 1.1 w 左右,和目前的文本量差不多(含支线)。
当然不听语音的话阅读速度翻倍,同理文本量翻倍。
设定也是,越详细越趋向于笔者的私设,而非翠鸟计划公设。
内容已经过取舍和精简,这也是为什么本篇选择了烂俗而非文艺的表达方式(也有一部分原因在于笔者没有其他图片素材做背景),并且未为指路鸟指定姓名(诸多不便)。
因此想听听社区各位的看法。
PS:现在回看,让社区高于服务器群组的举动确实很有先见之明,有助于社区长久发展和内容创作。反之的后果:世界观设定

4、制作的技术路径
人工——文本、背景、配乐、程序,ai——立绘、cg、配音。
程序打算使用 renpy。

5、在网页端部署的可行性
renpy 可以直接构建网页发行版,但存在致命缺陷
但其余的部署笔者并不清楚,交由鸭鸭考虑。这也是为什么没有一步到位。若按照5首bgm(MP3格式)+20张背景&cg(2k jpg)+18张人物立绘(含差分)计算素材大小,大概在 50 mb 左右,社区是否能/应该支持相关的带宽费用。

6、后续的想法
例如通过业力解锁后续剧情/发布工作流及素材自由续写。

7、其他
只是分享现状:连老牌 acg 歌曲资源站天使动漫论坛都开始搞曲包售卖了,传统社区个中艰难可见一斑(人心险恶,也不排除是恰烂钱)。
还写了很多和翠鸟相关的有的没的,想想还是删了。
希望目的保持纯粹,若对社区无用,当作二创即可。
 
最后编辑:

rainsist

成熟小鸟
2020-10-08
221
83
思考时间
3 天 4 小时 21 分钟
33
spotify,浏览器安装为应用,chrome搜索针对的广告屏蔽器。
 

反对司令

成熟小鸟
2022-03-16
191
83
思考时间
3 天 13 小时 53 分钟
33
能用,但免费版音质太渣。
 

反对司令

成熟小鸟
2022-03-16
191
83
思考时间
3 天 13 小时 53 分钟
33
试制一段时间后,遇到了很多难点,在此记录具体的技术路径。

总顺序:美术→剧本→配音→配乐,既然开启一体机模式,程序这一环就可以独立出来,时不时穿插验证一下素材质量。
美术优先的原因在于 ai 绘画的不可控性,先设定人设再生成人物会花费巨量时间(你甚至无法判断生成人物展现的性格是否与剧本相符),因此不得不让渡剧本的优先级。
配音和配乐的优先级可能会调整,目前还没进展到那一步,顺序将围绕便利性与融合性定位。

一、美术资源的准备
  • 顺序:立绘→cg→背景→gui、光标、画廊、CTC素材等杂七杂八的东西
动画是“动”的艺术,所有元素围绕动而设计,只要张力在就能过得去;
而 gal 是“静”的艺术,玩家多数时间看到的是固定的画面,因此对美术资源的精细程度要求比前者要高。
立绘和 cg 的顺序主要取决于 ai 绘画中人物的一致性,人物的一致性只有两个来源:lora 和 图像编辑模型。
如果先生成 cg,拿 cg 训练人物 lora,考虑到人物在 cg 中的占比太小(像素太低),将导致精细度不够的问题。

(一)立绘
  • 顺序:生成角色(文生图)→放大图片(潜空间放大)→修复图像(局部重绘/Photoshop)→表情差分(局部重绘/Photoshop)→动作差分(图像编辑模型)→换装(图像编辑模型)

1、固定人物总体特征并潜空间放大2倍。
工具:comfyui(建议使用 comfyui 而不是 Stable Diffusion,前者自定义程度更高)
工作流:图生图
模型:wai-illustrious-sdxl
尺寸:768 x 1536(竖图)(1536 是底模训练集的尺寸,建议不要超过,否则出鬼图)
正面提示词:人物提示词(1girl, solo)+ 动作提示词( looking_at_viewer,standing)+ 构图提示词(full_body)+ 背景提示词(simple_background),人物提示词可以不填也可以根据想绘出的人物特性来设计,不知道的 tag 就去标签超市上找。
负面提示词:censored, 3d, realistic, subtitles, worst quality, low quality, bad anatomy, watermark,nsfw,halo,bird(去 3d、写实风格,并去掉跑图后容易出现的无关元素)
K 采样器参数:根据底模作者发布页的建议填。
工具:comfyui
工作流:潜空间放大
模型:wai-illustrious-sdxl
缩放Latent(比例):2.00
正面提示词:人物提示词(1girl, solo)+ 人物提示词(你想强化或修改的特征,比如wings,注意不可追加太多 tag,否则影响生图质量)+ 背景提示词(simple_background)
负面提示词:censored,3d,realistic,subtitles,worst quality,low quality,bad anatomy, watermark,nsfw
降噪:0.7
最终尺寸:1536 x 3072(竖图)

2、修复有问题的部位(手、翅膀等)。
工具:comfyui
工作流:局部重绘
遮罩:图片问题区域。
正面提示词:无或对应部位提示词。
负面提示词:censored,3d,realistic,subtitles,worst quality,low quality,bad anatomy, watermark,nsfw
controlnet:canny 或 depth 模型均可,强度不宜太高,根据需要开启 。
后续节点自动实现:裁剪遮罩区域图像,放大并单独修复,将修复好的区域拼回原图像。​
工具:Photoshop
手动修复 ai 无法修复的内容(突兀色块、缺失线条、错乱特征等)、去掉分辨率过低的人物特征(此步骤是为了 lora 训练集做准备,ai 无法识别/生成分辨率过低的人物特征,如:呆毛)。​

3、锐化图片(可选)。
放大模型:RealESRGAN_4x 或其他。

4、立绘差分。
需求:
姿势 a + 1 个动作差分 + 80 个表情差分(进线前 35 个 + 进线后 35 个 +10 个特殊表情,进线前后区别在于是否有脸红等表达好感的特征)
姿势 b + 40个表情差分(主要用于姿态 a 的过渡,表情差分可参考姿态 a 的表情差分)
服装若干套。
gal 人物立绘只需要以上差分数量就能达成比较好的演出效果。
  • 前置步骤——无五官人物主体:
工具: Photoshop/comfyui - Qwen-Edit-2511

需要去掉的内容有:图片背景、眉、眼、口、脸红。
需要保留的内容有:鼻。

如果你希望将素材中的人物表情与人物主体分开,再在程序中合并(renpy:层叠式图像以减少磁盘空间消耗,就需要这一步骤。
注:抠除背景时最好新建一个纯黑图层打底,ai 绘画的扩散模型会在人物线稿外缘生成 1 -2 个像素的白色线条,如果抠不干净,在深色背景时会出现明显边缘。
  • 表情差分:
工具:comfyui
遮罩:人物面部。
正面提示词:对应的表情提示词。
负面提示词:censored,3d,realistic,subtitles,worst quality,low quality,bad anatomy, watermark,nsfw
降噪:0.7 - 0.9
controlnet:canny 或 depth 模型均可,强度不宜太高,根据需要开启 。
生成后尤其需要注意瞳孔样式和高光是否发生变化,如发生变化,需要手动修改。
工具:Photoshop
需要抠出的部分:眉、瞳孔、眼眶、口
调整以符合透视关系:五官位置
其他:将最初的瞳孔、眼眶作为后续表情差分的形状指定和色指定,必要时可直接使用该瞳孔替换其他表情的瞳孔。
重复以上步骤,最后批量导出表情。
  • 动作差分:
工具:comfyui
模型:Nano Banana2/Qwen-Edit-2511(实测前者效果更好)
提示词:严格保持图 1 人物一致性,将图 1 人物转为正面视角,正面面向镜头,修改光影使得左右对称,并给角色换一个自然的站立动作。(如果有想要设定的动作,就直接设定)
Nano Banana2 只支持原生 2k 尺寸,并且生成后有明显噪点,所以我们需要再过一遍底模:
工具:comfyui
工作流:图生图
模型:wai-illustrious-sdxl
降噪:0.4
正面提示词:人物提示词(1girl, solo)+ 人物提示词(你想强化或修改的特征,比如wings,注意不可加太多 tag,否则影响生图质量)+ 背景提示词(simple_background)
负面提示词:censored,3d,realistic,subtitles,worst quality,low quality,bad anatomy, watermark,nsfw

ControlNet(Canny):
强度:0.5
开始百分比:0.000
结束百分比:1.000
工具:Photoshop
修复有问题的地方,如:头饰。
还需用液化/自由变换工具修复人物比例失调的问题,否则将导致姿势转换时突兀。
  • 服装差分:
先给姿势 a 的角色换装:
工具:comfyui
模型:Nano Banana2/Qwen-Edit-2511
提示词:给角色穿上可爱的浅色小皮鞋和花边袜
再让姿势 b 的角色换上姿势 a 的衣服。
工具:comfyui
模型:Nano Banana2/Qwen-Edit-2511
提示词:给角色穿上可爱的浅色小皮鞋和花边袜
给图1的人物更换上图2的衣服、鞋子和袜子,严格保持图1的人物姿势、容貌等人物一致性,禁止修改图1视角。
同样需要再过一遍底模:
工具:comfyui
工作流:图生图
模型:wai-illustrious-sdxl
降噪:0.4
正面提示词:人物提示词(1girl, solo)+ 人物提示词(你想强化或修改的特征,比如wings,注意不可加太多 tag,否则影响生图质量)+ 背景提示词(simple_background)
负面提示词:censored,3d,realistic,subtitles,worst quality,low quality,bad anatomy, watermark,nsfw

ControlNet(Canny):
强度:0.5
开始百分比:0.000
结束百分比:1.000

有几个工作流可以实现差分,但各有各的缺点,错误的顺序将导致目标无法达成,主要难点在于人物姿势迁移和换装上。

首先,姿势迁移,需要把姿势 a ——侧身的角色转化为姿势 b ——正对镜头,如果角色只有一套衣服,可以通过 Nano Banana2 实现(Qwen-Edit-2511 会导致人体比例畸形,即梦会有色差);
若遇到角色有多套衣服的情况,有以下几种选择:

1、先统一换好装,再控制姿势和动作。
(1)Qwen-Edit-2511 + Qwen Multiangle Camera ——也许是因为图尺寸太大,别说实现统一的姿势了,连转变姿势都有困难;
(2)Nano Banana2 —— 转变后的姿势和动作不可控,导致不同服装的姿势 b 会有差异,极大增加表情差分工作量;
(3)高斯泼溅重绘——视角和人物一致性非常不可控,尤其是高斯泼溅的插件尚没有精确的摄像机坐标。
2、先生成不同姿势的人物果体,再换装(类似业界做法)。
(1)Qwen-Edit-2511 ——换装时控制效果非常差劲,它不能同时把衣服转变成正面视角并换装,Qwen-2511-AIO 也无改善;
(2)Nano Banana2 ——禁止 nsfw;
(3)其他日后可能出现的模型——理论上可行,但过多遍模型会导致图片质量下降的潜在问题。
3、先借助姿势 a 生成姿势 b (同套服装),再生成姿势 a 的服装2,最后让姿势 b 使用姿势 a 的服装 2(上文做法)。
(1)Qwen-Edit-2511 ——同上;
(2)Nano Banana2 ——连带动作成功实现,需要强指令控制,然后再过一遍底模。

(二)cg

(三)背景

(四)gui


(五)光标
renpy 中可以定制鼠标光标,一种是 32 x 32/64 x 64 的小尺寸光标,一种是可视组件光标,一般采用前者。
  • 鼠标的图标风格越简约越好。
  • ai 无法生成低分辨率的图片,所以我们需要先生成高分辨率图片,再在 PS 中手动降分辨率。
  • 降分辨率过程中建议选择 nearest-exact 方法,该方法不会产生模糊的边缘像素。
可以实现的功能:
1、替换光标样式(单个图片或序列图片)
Python:
define config.mouse = { }
define config.mouse['default'] = [ ( "gui/mouse/mouse.png", 0, 0) ]
2、点击时变成其他样式
Python:
define config.mouse['pressed_default'] = [ ( "gui/mouse/mousedown.png", 0, 0) ]
3、点击/拖拽时触发粒子效果。
Python:
init python:
    import random
    import pygame

    class MouseParticles(renpy.Displayable):
        def __init__(self):
            super(MouseParticles, self).__init__()
            self.particles = []
            self.pressing = False

        def event(self, ev, x, y, st):
            if ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1:
                self.pressing = True
            elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1:
                self.pressing = False
            return None

        def render(self, width, height, st, at):
            r = renpy.Render(width, height)

            if self.pressing:
                mx, my = renpy.get_mouse_pos()
                for _ in range(2):
                    self.particles.append([
                        mx + random.uniform(-4, 4),
                        my + random.uniform(-4, 4),
                        random.uniform(-0.7, 0.7),
                        random.uniform(-1.5, -0.5),
                        1.0
                    ])

            alive = []
            for p in self.particles:
                p[0] += p[2]
                p[1] += p[3]
                p[4] -= 0.025
                if p[4] > 0.1:
                    alive.append(p)
                    surf = pygame.Surface((8, 8), pygame.SRCALPHA)
                    alpha = 255
                    pygame.draw.circle(surf, (55, 255, 255, 255), (4, 4), 3)
                    r.blit(surf, (int(p[0]) - 4, int(p[1]) - 4))

            self.particles = alive
            renpy.redraw(self, 0)
            return r

    particle = MouseParticles()
(六)CTC
  • CTC——Click To Continue,就是对话框文字显示完毕后的在右下角闪烁的组件,用来提示玩家点击鼠标前进。
1、图形样式:一般为对称的 64 x 64 图形,如雪花、齿轮、六边形等等,也有非常简约的线性图标,甚至是计算机字符。
2、显示效果:闪烁、透明度调整、旋转、缩放、左右摇摆等。
3、素材类别:可分为单张图片和多张图片组成的序列帧动画。

笔者这里也是尝试了后者,使用 AE 改了个小动画导出。
不过在 renpy 中的实现似乎不太容易,毕竟用 image 语句来写就有点太不优雅了。
Python:
image ctc_animation:
    "gui/ctc/CTC_00000.png"
    0.05
    "gui/ctc/CTC_00001.png"
    0.05
    "gui/ctc/CTC_00002.png"
    0.05
……
很简单,是吧,但是帧数一多就很丑很麻烦,而且会产生很多小文件。
知乎上看到一篇关于精灵表的文章,改了下代码成功实现,只需将序列帧以矩阵图的形式排列成大图即可。
Python:
init python:
    from renpy.display.layout import Crop

    class SpriteSheetAnimator(renpy.Displayable):
        def __init__(self, image_path, rows, cols, interval, loop=True, pingpong=False, **kwargs):
            super(SpriteSheetAnimator, self).__init__(**kwargs)
            self.image_path = image_path
            self.full_width, self.full_height = renpy.image_size(image_path)
            self.rows = rows
            self.cols = cols
            self.frame_w = self.full_width // cols
            self.frame_h = self.full_height // rows
            self.length = rows * cols
            self.frames = []
            for row in range(rows):
                for col in range(cols):
                    x = col * self.frame_w
                    y = row * self.frame_h
                    frame = Crop((x, y, self.frame_w, self.frame_h), renpy.displayable(image_path))
                    self.frames.append(frame)
            self.current_frame = 0
            self.last_time = 0
            self.interval = interval
            self.loop = loop
            self.pingpong = pingpong
            self.direction = 1  # 1 表示正向,-1 表示反向

        def render(self, width, height, st, at):
            ## st为0时,表示组件重新显示
            if (st == 0):
                self.last_time= 0
                self.current_frame = 0
            if st - self.last_time >= self.interval:
                self.last_time = st
                if self.pingpong:
                    self.current_frame += self.direction
                    # 边界检测并反转方向
                    if self.current_frame >= self.length - 1:
                        self.current_frame = self.length - 1
                        self.direction = -1
                    elif self.current_frame <= 0:
                        self.current_frame = 0
                        self.direction = 1
                else:
                    self.current_frame += 1
                    if self.current_frame >= self.length:
                        if self.loop:
                            self.current_frame = 0
                        else:
                            self.current_frame = self.length - 1
            frame_render = renpy.render(self.frames[self.current_frame], width, height, st, at)
            renpy.redraw(self, 0)
            return frame_render
不过要记得让组件每次出现从第一帧开始播放,不然后续会卡住。


(未完待续)
 
最后编辑: