美文网首页
【Unite Tokyo 2018】Achieving high

【Unite Tokyo 2018】Achieving high

作者: 离原春草 | 来源:发表于2023-07-28 19:18 被阅读0次

    这里来介绍一下米哈游崩3的相关技术,技术分享于2018年,虽然过去5年了,但其中依然有些内容值得借鉴学习。

    崩3、原神使用的都是Unity引擎,全文目录给出如下:

    下面介绍了崩3在移动端上用到的一些特性,包括但不限于Bloom、动态粒子、Distortion(一种后处理)以及平面反射:

    介绍一下反射实现逻辑:

    • 以1/3的分辨率进行渲染(不是PPR或SSR做的后处理),从下面的截图来看,这里是对反射渲染的viewport做了缩减处理的
    • 根据需要进行模糊处理
    • 渲染的时候考虑了菲涅尔效应
    • 通过材质处理,在金属反射面上增加了一些扭曲与sketch(笔触)效果

    屏幕空间的扭曲自然是通过后处理完成:

    • 会基于深度来控制扭曲强度

    Bloom逻辑没什么新意,还是多层下采样叠加后得到Bloom效果,并将之叠加到原始场景上去。

    最后混合就得到了相关效果,中规中矩。

    崩3有日夜变化与动态天气。

    云层是通过贴片来实现的,不过提供了较为丰富的配置以应对不同的场景:

    • 时间(早中晚)
    • 颜色(Bright/Dark/Rim/Second Dark Color)
    • 将贴图分成多个Layer,用不同的通道表示,在实际使用的时候根据配置对Layer进行叠色混合得到最终效果

    为云层提供了专门编辑器:

    • 通过billboard粒子实现
    • 可以自定义自己想要的云朵类型
    • 通过keyframe实现云层的动态变化
    • 添加了TOD时间控制逻辑

    上面展示了可以实现的效果,看起来还挺丰富的,也挺好看,就是不知道运动起来是否连贯,而且贴图消耗低(内存稍高),性能比较好,如果没有角色穿云的玩法,这种方案还是挺不错的。

    天气系统做了如下工作:

    • 对大气雾效的控制逻辑做了优化
    • 添加了对skybox颜色配置的逻辑
    • 为角色添加Lighting Volume

    又绕回后处理了,增加了多层DOF效果:

    • 采用六边形作为DOF高光形状
    • 基于模糊半径(通过曲线控制,可以实现一些镜头效果或者随着时间动态变化的效果)对分辨率进行调控(半径越大,分辨率越低)
      支持对Bokeh的强度(会根据场景的亮度动态调整,以确保效果)与旋转参数进行调整以实现动画效果,下面给了一个图片,文末的参考链接中有对应的视频可以看下:

    雨效逻辑:

    • 会根据时间的流速(如慢动作)调整表现
    • 一共设计了四种流速,并关联了四种不同的资产

    下面看下卡渲逻辑。

    角色着色要点:

    • 基于多层Ramp图+Brush实现
    • 头发做了各向异性,还做了折射和模糊?
    • 主要有三种光:方向主光、环境光IBL以及边缘光
    • 阴影用了PCSS(根据caster到receiver的距离调整阴影的软硬程度)
    • 描边

    崩3对Ramp图的使用可以说是一大亮点,这里来介绍一下具体的实现逻辑。

    早前的卡渲采用的Shading逻辑都是二值化处理,即基于NdotL的数值来判断当前像素是处于亮部还是暗部,这样导致的效果就是明暗交界过渡比较生硬,为了解决这个问题,就开始围绕NdotL的取值做文章,因为这个数值本身是个浮点数,因此我们可以将二值化处理变成分段的多值化处理,还可以更进一步,基于这个数值来对一张颜色贴图(称之为ramp图)进行采样,得到更为柔和的效果,就如上图所示。

    关于ramp图,其实有很多不同的用法,下面做一下简单的整理:

    1. 一维Ramp图

    比如有人基于这个数值来对shading的暗部判定逻辑进行调整从而得到更符合需要的明暗变化:

    float NdotL = dot(L, N)*0.5 + 0.5 // half-lambert
    float ShadowLerp = SAMPLE_TEXTURE2D(RampTexture, 1 - NdotL)
    float3 Diffuse = lerp(BaseColor, ShadowColor, ShadowLerp)
    

    又比如基于采样ramp图来对Shading Color进行调节:

    float NdotL = dot(L, N)*0.5 + 0.5
    float ShadowLerp = SAMPLE_TEXTURE2D(RampTexture, 1 - NdotL)
    float3 Diffuse = BaseColor * ShadowLerp
    
    1. 二维Ramp图
      根据二维Ramp图采样的UV坐标含义不同,我们有不同的用法

    2.1 TOD+MaterialID

    上面这张Ramp图可以分成上下两半,上面的暖色调(三行颜色)对应的是白天的参数,下面的冷色调(三行颜色)对应的是晚上的参数。而水平坐标对应的实际上材质ID,基于不同的材质ID,我们可以得到不同的颜色,从而可以为不同的物件指定不同的颜色叠加效果。

    2.2 LightingAngle+Distance

    也有人将横坐标解释成NdotL,纵坐标解释成到相机的距离的,从而可以实现颜色随着远近而变化的效果

    2.3 LightingAngle+MaterialID
    这个方案就是崩坏所使用的方案,横坐标跟上面一样,为NoL,纵坐标则为材质ID,从而使得不同的材质可以有不同的颜色变化效果。

    不过这里其实并不是直接简单采样就拿来使用,还做了比较多细节处理:

    • 一张Ramp图会分成多个通道,每个通道用于控制一个layer的叠色
    • 而每个通道的数值其实可以理解成每个layer颜色的权重,我们可以指定每个通道对应的颜色,以及对应权重的偏移值:

    整体的计算公式给出如下:

    float3 Tint = SAMPLE_TEXTURE2D(RampTexture, float2(NdotL, MaterialID))
    
    float3 TintHigh = Tint.r * TintHighColor
    float3 TintMed = (Tint.g + TintMedOffset) * TintMedColor
    float3 TintLow = (Tint.b + TintLowOffset) * TintLowColor
    
    float3 TintColor = saturate(TintBaseColor + TintHigh + TintMed + TintLow)
    
    

    这里给出了不同叠加层数下的不同效果。

    通过对ramp图的调整,我们可以实现软硬切换的效果,且必要的时候,还可以为同一个角色的不同部位赋予不同的软硬程度。

    明暗的渐变效果看起来不错,但也不是所有部位都需要,比如脸部就不要这个效果,因此这里还增加了一张mask图用于控制需要进行渐变的区域

    这里的阴影看起来不是基于光源视角的,而是基于相机视角(或者说基于相机视角做一个调整)产生阴影贴图,之后根据偏移+阴影贴图采样来实现的效果,且透明物体也会参与到阴影贴图的绘制中去。

    眼睛增加了折射效果,折射的原理就是将打在球面(眼球)上的射线按照折射原理做一个偏转,并基于偏转后的方向完成贴图采样。

    因为表面并不是平整的,按照斯涅耳(snell)定律来求取折射向量会比较复杂,《RTR》中给出了一个拟合公式:
    RefractVector = (w - k)*Normal-n*View \\ n = \frac{n1}{n2} \\ w = n * dot(View, Normal) \\ k = \sqrt{1+w^2-n^2}

    具体的推导过程可以参考Faster Refraction Formula, and Transmission Color Filtering

    有了折射向量,就能够算出偏移值:

    如上图所示,入射点处的eye forward direction我们标注为Up,Up跟折射向量Refract所夹的锐角我们记为theta,Up与不做折射时的向量Direct所夹的锐角,记为alpha,那么我们就有:
    cos\theta = abs(dot(\vec{Refract}, \vec{Up})) \\ cos\alpha = abs(dot(\vec{View}, \vec{Up})) \\ \vec{HorizontalOffset} = normalize( \vec{Up}*cos\theta + \vec{Refract}) * Height*(tan\alpha -tan\theta)

    这里的height是入射点到水平面的高度,得到上述偏移向量后,将之与(眼球)贴图的U方向的向量(世界空间)以及V方向的向量(世界空间)点乘就得到了UV偏移值(上图公式给的应该是简化版本)。

    眼睛绘制的另一项重要特征是焦散(Caustic),人眼结构如下图所示:

    由于前房区域可以看成是一个透镜,而透镜会有聚焦光线的作用,因而导致虹膜部分会形成焦散的亮斑,如下图所示:

    回到崩3这边的实现,由于卡渲并不追求物理真实,只要好看就可以了,因此完全可以把焦散绘制到贴图上,崩3采用的也是类似做法:

    • 添加一张Caustic Mask图,用于控制哪些区域会存在焦散亮斑
    • 由于卡渲的特殊,米哈游希望焦散效果出现在入射光线的另一侧,所以这里通过对diffuse计算逻辑进行翻转(将入射光线沿着法线做对称翻转)来模拟光照的变化,之后辅助菲涅尔公式(光在进入不同介质时会存在反射与折射,这个公式揭露了反射光强与折射光强的比例)来调节焦散强度的变化

    眼睛的最终渲染效果如图所示,下面来看下头发渲染。

    这里先列几个期望达成的渲染要点:

    1. 要能做到所见即所得的效果调节,即对参数或者贴图的调整,要能立马看到效果
    2. 要支持基于切线的各项异性
    3. 支持多层高光效果
    4. 支持随着粗糙度而变化的光照表现,且能够支持随着视角或光照的变化而跟随流动的效果与头发之间的AO遮挡

    头发渲染的一个重要特征是高光,最常用的模型是Kajiya-Kay(简称kk),这个模型是目前渲染头发比较常用的,《神海》以及这里的崩坏都用的这个模型。

    相对于普通物件的高光使用的NoH,这里使用的是ToH,但实际推导起来,两者其实是对等的:
    sin(T, H) = sin(90^\circ- \theta) = cos(\theta) = cos(H,N)

    kk高光项计算规则给出如下:

    可以看到,这个公式跟上面崩坏使用的代码是吻合的

    前面说到,崩坏这里用了两层高光,分别是低频跟高频,不确定是否每一层都用的kk(相当于采用的是Marschner模型了),为了模拟动漫中类似于电磁波抖动的效果,这里对高光做了抖动:

    shift = Sample(NoiseTex, shiftUV)
    shiftedT = T + shift * N
    newTangent = normalize(shiftedT)
    

    首先对采样坐标进行处理,得到噪声贴图的采样坐标,基于这个坐标得到抖动的幅度,基于抖动幅度利用法线对切线进行调整,从而实现高光在发束上下移动的效果。

    这里给出两层高光的效果。

    这里给出另一种高光实现方案,这里的高光想要实现顺着发丝移动的效果,且形状能够随着位置的不同而变化。

    要想实现这种效果:

    • 需要确保每一缕头发的uv是顺着发丝而平滑变化的(方便基于uv计算出切线,否则就需要用flowmap来获取切线)
    • 对于每一缕发束,需要通过某个贴图标识出其左边界与右边界,从而可以实现从左到右高光形状的变化,并用多个曲线来定义高光模板实现不同的高光效果
    • 之后,同样使用一张噪声贴图来控制抖动(高光粗细)
    • 在材质上,添加若干参数用于控制高光的形状、偏移等,从而实现各种不同的高光形状

    另一个需要各向异性高光的材质是丝绸,崩3这边的做法是利用副切线来进行计算,并使用了三层高光叠加的方式来实现,每种高光都可以分配不同的颜色。

    这里没有具体介绍高光的计算方法,也没有给出三层高光的计算区别,不过移动端角色渲染(丝绸篇)对PBR下的写实高光计算逻辑进行了梳理,大概思路给出如下:

    1. 获取某个点的各项异性数值anisotropy,并基于这个数值计算出在切线T与副切线B方向上的粗糙度:
    anisoAspect = sqrt(1 - 0.9 * anisotropy)
    roughnessT = roughness / anisoAspect 
    roughnessB = roughness * anisoAspect 
    
    1. 基于T、B以及对应的roughness计算G项跟D项,F项不受各项异性影响
    2. 基于计算得到的GDF,计算对应的高光
    3. 为了得到更逼真的高光效果,通常需要对法线做偏移调整

    说回到崩3的3层高光,推测可以在两个层面做文章:

    1. 为不同层指定不同的anisotropy或roughness
    2. 为不同层指定不同的法线偏移参数

    这里是演示效果,看起来还挺赞的

    这里介绍一些特殊的装饰材质,如水晶等需要折射效果的透明材质,以及纱巾等需要模糊的半透材质,具体实现逻辑没什么好介绍的,其中一个可以说一下的优化点是这里的折射与模糊不是针对全屏的,而是针对角色(绘制一个proxy mesh,这个mehs通常是对应的角色,或者更精简一点,只绘制半透物件对应区域)覆盖的区域的

    下面来看看描边算法。描边使用的是基于法线外扩的方案,只针对角色以及动态物体使用,并且通过顶点属性来控制描边的粗细,从而可以实现发梢描边逐渐变细最终缩小为0的效果,且描边粗细还会随着到镜头的距离做动态调整。

    此外,不同的材质,其描边颜色也是可以控制的(这里没有介绍是基于顶点属性还是贴图来实现)

    对于几何边缘而言,法线外扩方案就够了,但是对于一些非几何边缘,如一些色彩边缘来说,这个方案就无能为力了,崩3的做法是通过一个预处理(没有介绍预处理是在什么时候完成,是一次性的,还是每帧都需要,从理论上推测,可以做成一次性的,在开发阶段就制作完成,比如将之保存成一个额外的mesh,之后通过geometry shader完成对应的线条勾勒)来搜集这些边缘的数据,并且基于与法线外扩方案同一套控制参数(顶点属性控制粗细等逻辑,以及描边颜色控制逻辑等)来对效果进行调整,确保全屏效果一致。

    场景的描边,通常是基于normal与depth的边缘检测后处理实现的,但是这种方案的问题在于描边的宽度不好控制,虽然能够基于深度来控制,但是其调整范围是有限的,只能实现少数几个像素尺寸的调整(为什么?),所以不适合角色等距离相机较近的人物(较近的话,就只能考虑基于法线外扩来实现了)。

    除了普通的描边之外,这里还介绍了一种比较复杂的笔刷描边方案,其实现步骤给出如下:

    1. 轮廓线提取:从Mesh上提取轮廓边,可以分为Sharp Edge和Smooth Edge两种
    2. 连接轮廓线:根据模型的拓补关系,将相邻的轮廓边连接成尽可能长的轮廓线(是不是离线计算会比较合适?)
    3. 轮廓线分段:在步骤2的基础上,根据轮廓线上曲率和可见性的变化,将轮廓线在曲率或可见性的突变处分开
    4. 笔触映射:将想要添加的笔触(某种人工绘画时会呈现的一种特定的描边效果)制作成纹理贴图,根据对应的纹理坐标映射到步骤3的轮廓线上

    这种方案实现的描边效果更为风格化,但是性能消耗较高,不太适合用在实时,离线的CG制作倒是可以。

    下面来看下崩3中的特效或后处理效果。

    这里展示了体积光+bloom的效果,可以说是非常的抓人眼球了。

    来看下体积光是如何实现的:

    1. 看起来是用的Unity自带的功能,通过曲线来控制体积光的形状
    2. 可以通过曲线在运行时调整体积光的形状与强度(颜色)
    3. 增加了一张3D噪声贴图模拟雾气动态变化的效果
    4. 增加了Cookie Map用于控制体积光投影的形状(这个没有细说,不太理解,不过不重要)
    5. 通过蓝噪声抖动+TAA来降低体积光采样频率过低导致的锯齿问题

    开了GI,支持基于视频的动态自发光与体积聚光

    为角色添加了动态AO,基于魔改后的HBAO实现

    这里是用到的一些屏幕空间后处理效果,部分如Lens Flare需要为卡渲风格做特定调整(这里使用与bloom类似的方式提取的高光区域作为输入,然后进行多次不同方向上的卷积并应用色彩调制来获得最终结果)

    这里展示的是CG画质的渲染效果,看起来还是挺不错的

    为了得到高画质的场景效果,崩坏3采用的策略是在PBR的基础上对其做风格化调整,使之表现接近动画风格:

    1. 对于色彩的做了卡通化调整
    2. 对于物体材质细节根据需要进行强调或省略
    3. 结合使用图像空间的勾线来强调物体边缘

    这里是效果展示。

    角色的表情是基于blendshape制作实现的:

    1. 将眼睛,嘴巴和眉毛拆分为单独的部件,分别控制其表情变化
    2. 自研了一套面部表情插件,用于实现表情动画的及语音嘴型的自动映射
    3. 预定义了一个包含丰富表情的集合,在角色交互的时候根据需要从中选用以驱动面部表情。

    这里介绍了角色动画实现上的一些细节:

    在Unity中如果使用humanoid作为动画导入方式,当关节处旋转角度较大时,关节处的形状可能无法满足项目组对动画品质的期望, 解决方案是:
    1.1 在DCC中为每关节添加一个修正的blendshape,并将之一并导入到Unity以防止关节形变
    1.2 由一个脚本来根据关节旋转角度来对形状进行插值混合,为了确保效果,每个关节会需要分别制作两个blendshape,一个用于90度,另一个用于140度以对关节形变补偿

    1. 另一种解决方法是使用额外的骨骼进行关节修正,这种方法更容易制作,但是对于结构细节的优化效果不如上一种使用blendshape的方案

    流体和破碎是使用alembic格式,或者EXR贴图作为载体从Houdini或其他DCC工具导入的顶点动画实现的。

    基于贴图的顶点动画由于是在GPU上计算实现的,其执行效率及加载速度要快于alembic格式

    实时卡通渲染在今后可以继续改进和完善的地方:

    1. 实现所有类型材质完全可定制的风格化渲染,目前崩3只在人物皮肤和服装渲染中的应用了笔刷以获得笔触效果,后续会考虑将其扩展到整个场景的渲染,比如实现类似新海诚式的场景风格,以呈现有着独特且统一的风格化动画风格渲染。
    2. 进一步提高模型的渲染精度,最终期望实时呈现CG级的模型精度,比如可以考虑使用geometry shader或预烘培displacement map进行动态自适应的曲面细分,相比直接导入原始高模,它可以极大减少资源导入的开销和提升运行效率
    3. 优化整套流程解决方案,使之更易于实时调整和编辑,进一步提升运行效率以适合在游戏中使用

    参考

    [1]. Achieving high quality Anime style rendering on Unity
    [2]. 米哈游技术总监首次分享:移动端高品质卡通渲染的实现与优化方案

    相关文章

      网友评论

          本文标题:【Unite Tokyo 2018】Achieving high

          本文链接:https://www.haomeiwen.com/subject/helkudtx.html