欢迎参与讨论,转载请注明出处。

前言

本篇文章按理来说在三月便该发布了,因为插队原因延宕至今,不过好饭不怕晚,干就完了奥利给!阅读本文最好拥有一定的图形学知识,当然看个热闹也是好的。
  游戏画面的风格是一开始便要定下的大事,这在古法2D主要通过素材本身及后期调色决定,没有太多文章可作。而在现代游戏(尤其是3D)则会通过Shader在原本的元素上进行加料,如通过基于物理的渲染(PBR)将模型凸显出金属、石头、布料等材质倾向。而在早期为了凸显3D模型的立体感,一般会采用经验总结出来的冯氏光照模型(Blinn-Phong),这也是许多3D软件的默认方案,那将会让我们的模型长成这样:

  嗯,这有够雕塑风的,让我想起了当初名震一时的猴赛雷,有着异曲同工之妙:

  由此可见,对于讲究卡通风格的游戏,这种通用的光照模型肯定得枪毙,于是本文才会诞生。对于这类非写实方向的渲染方案,业界称之为NPR。而往下细分则是日式卡通渲染,其中佼佼者当属《罪恶装备》系列,而《崩坏3rd》也是不少人在这方面的启蒙者。当然美术这一块没有绝对的风格一致,渲染也不例外,所以Demo里的卡通渲染方案乃是个人的方案,不代表业界的标准实现与效果。
  Demo基于Unity2019.3开发,渲染管线为URP7.3.1,采用直接编写Shader的方式(HLSL),将一一介绍其中要点。本文所谓的卡通效果以日式2D赛璐璐风格为准,不论厚涂之类的风格。

着色

首先我们先抛开一切:冯氏光照不好那咱们就是了。直接把贴图显示了,什么料都不要加。

// 根据uv坐标获取对应贴图上的颜色half4 color = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);


  嗯,虽然很原始,但好歹没那股恶心感了,把投影也加上:

// 根据渲染管线提供的shadowCoord获取光照信息,并计算出投影颜色Light mainLight = GetMainLight(shadowCoord);
color *= mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation);


  哎呀,有了投影瞬间立体起来了,开始有《塞尔达传说:风之杖》内味了:

  要加投影记得添加Pass:ShadowCaster,并且获取光照信息也需要开启一定的宏,这些并非本文重点,详情请查阅URP的Shader实现。
  但只是如此还不够:颜色太鲜艳了,看久了会累。那么有两种方案:调色与着色,调色则是进行总体的颜色调节,使之不要这么鲜艳,着色则是根据模型面对光的吸收度决定明暗。这里还是选择着色:它将会增强模型的立体感。
  这里说的着色其实就是冯氏光照中的漫反射(Diffuse):当光照射到非平面的物体上,将根据与光的夹角决定吸收度(越是与光垂直的面越亮)。而在3D模型中,每个模型面都会往上发射一条射线,也就事实上构成了一条垂直于平面向量,这在数学中称之为法线(Normal)。我们可以使用**向量点积(Dot)**获取法线与光照方向之间的夹角,以此决定模型面的光亮程度。

half NDotL = dot(normal, lightDir); // 计算法线与光照方向的夹角系数
NDotL = saturate(NDotL); // 保证系数在0-1
color *= NDotL;


  啊这,这不是跟一开始差不多么?这是当然的,因为一开始便是冯氏光照的方案。其漫反射的思想其实并无问题,但原罪在于过渡太丰富了,每个模型面与光的夹角都不同,导致颜色都不同。整个模型看起来就过于立体,以至于产生了雕塑感。
  而在日式2D卡通的世界里(尤其是赛璐璐),着色并不会有太详细的过渡,只是到了某个角度统一涂暗,反之为亮,最多在两者之间加点过渡而已。那么便基于此思想进行改造即可:

half NDotL = dot(normal, lightDir); // 计算法线与光照方向的夹角系数// 根据_DiffuseRange约束系数,输出0-1的值
// 由于使用了smoothstep,在接近_DiffuseRange上下限时会做柔滑处理,使之产生过渡感
// _DiffuseRange的参考值为0.5, 0.7
half v = smoothstep(_DiffuseRange[0], _DiffuseRange[1], NDotL);// 根据根据v的值决定输出_LightRange范围内的值
// _LightRange的参考值为0.9, 1
v = lerp(_LightRange[0], _LightRange[1], v);color *= v;


  还不错,这下便为模型划分了明暗,并在两者之间做了过渡,这种方式称之为二值化。着色并没有采用很明显的暗色,只是想凸显一点立体感,以及让画面更柔和,不那么刺眼罢了。当然目前可以说是非常不明显了,这是有原因的,且待后续调色。

描边

接下来需要补上日式2D卡通不可或缺的一部分:描边(Outline),描边有助于划分物体,明确空间上的层次,并提供一定的风味。
  关于描边的实现方式,业界主要有模型多画一遍并将边缘外扩以及屏幕后处理的方案。前者方案在日式游戏较为流行,优点在于实现简单,性能也还算过得去,缺点是必须开抗锯齿不然没眼看。后者实现方式多样,并且根据实现方式能达到不一样的效果(如一定程度的内描边),但有些更适合搭配延迟渲染(Deferred Rendering),而这代表着对显卡带宽与光照方案有要求。
  另外在显示方案上也有区别,有追求任何缩放下描边大小不变的,也有自然派的。有让描边纯色的,也有要让描边根据贴图颜色决定的。本人采用的是模型外扩、自然缩放、根据贴图颜色决定的描边方案。
  多显示一遍模型在Unity增加一个Pass即可,并且开启正面剔除(只显示背面,不然会干扰到正常模型)。并且在顶点着色器对模型顶点进行外扩,外扩的方向由所在模型面的法线决定。而颜色方面则在片元着色器根据贴图颜色进行置暗显示即可:

// 顶点着色器half4 viewPos = mul(UNITY_MATRIX_MV, input.positionOS); // 将顶点从模型空间转为观察空间
half3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, input.normalOS); // 同上,将法线转为观察空间
viewPos += float4(normalize(normal), 0) * 0.0075; // 顶点沿法线外扩
output.positionCS = mul(UNITY_MATRIX_P, viewPos); // 将顶点从观察空间转为裁剪空间output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap); // 提取uvreturn output;
// 片源着色器
color *= SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv); // 提取贴图颜色return color * 0.3; // 压暗

果不其然,没有抗锯齿的话就很搓,跟早期的跑跑卡丁车似的。安排一波MSAA8x:

  哎,这就舒服多了,当然实际上由于小泥人的关系,4x和8x实际上看不出区别,而2x也算可以接受的效果,这么看来能耗也还好。当然关于描边实际上还有内描边这个大题,但小泥人不需要这么丰富的细节,这就很舒服。

发光

目前模型的显示还欠缺一些发光的元素,如一般头发和武器会有一些高光效果。这在冯氏光照称之为镜面光照(Specular):本质上与漫反射一样,只是由视角方向与光照方向相加,并与法线做点积获得两者的夹角系数,如此便可实现根据摄像机与光照运动结合决定模型高光的位置。
  当然仅此而已是不够的,显而易见仅此而已的话将会如漫反射一般范围很大,而高光实际上只需要一点即可。实际上会将之范围缩小:

half3 halfVec = normalize(lightDir + viewDir); // 将摄像头方向与光照方向相加
half NdotH = dot(normal, halfVec); // 与法线点积,获取夹角系数
NdotH = saturate(NdotH); // 保证在0-1half v = pow(NdotH, _Smoothness); // 缩小夹角系数的值,由于NdotH在0-1,所以pow后会变得更小,_Smoothness参考值为8-64
color += color * v; // 在原有颜色的基础上叠加

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c5JP9lox-1589122401873)(https://musoucrow.github.io/images/lbbn_shading/10.png)]
  与之前一样,这样的高光过渡太强了,不够卡通,将之二值化:

half v = pow(NdotH, _Smoothness); // 缩小夹角系数的值,由于NdotH在0-1,所以pow后会变得更小,_Smoothness参考值为8-64
v = step(_SpecularRamp, v); // 小于_SpecularRamp的值将为0,反之为1
v = v * _SpecularStrength; // 定义高光强度,参考值为0.2color += color * v; // 在原有颜色的基础上叠加


  这样的高光就更有手绘的感觉了,牛屎一块。但很显然对于头发而言光是一块牛屎高光是不够的,让美术自由的进行创作显然是更好的方案。于是引入了发光贴图(Emission),其本身很简单:就是在最后把发光贴图的内容显示出来即可。而之所以要单独划分贴图而不是画死在原贴图,在于要自由的控制透明度甚至曝光,以及让发光参与单独的光照运算(与高光类似的方法,摄像机视角与光照方向相加后与法线点积)。

到了目前仍缺一个日式2D卡通的一个特性:边缘光(Rim),一般为了表达物体处于光亮的环境下,属于光溢出的一种表达,有助于提升画面的层次感。实现原理也很简单:视角方向与法线点积,根据夹角系数取得当前视角下的模型边缘部分,为之加光即可。

half VDotN = dot(viewDir, normal); // 视角方向与法线点积,获取夹角系数
VDotN = 1 - saturate(VDotN); // 取反,方便计算half v = smoothstep(_RimRange[0], _RimRange[1], VDotN); // 与漫反射部分类似,做二值化,参考值为0.4-1
v = step(0.5, v); // 小于0.5的部分都不要了
v = v * _RimStrength; // 设定边缘光强度,参考值为0.1color += color * v; // 叠加


  发光的构成大致如此,目前也许看起来不够明显,实是尚未调色所致,且看下文。

调色

先来看看目前的效果:

  首先是整体颜色风格不符合主题,这个场景属于有着岩浆的密室,应该符合昏暗以及灼热的色调,使用Split Toning进行调色:

  嗯,至少色调上像样了,但还是缺乏灼热的感觉,上Bloom看看:

  哎呀,看着只是稍微亮了点的样子,那是因为Bloom需要配合HDR使用,将颜色突破0-1的限制下进行运算,才能做到光溢出的效果:

  唔……这溢出的实在是有限,因为目前还处于Linear颜色空间,显示器对于颜色会进行处理,使得颜色之间的区间变小(明暗不明显),需要转成Gamma才能抵消之:

  成了,如此便得出了昏暗且灼热的场景风格,高对比度(亮者更亮、暗者更暗)的画面。

后记

这算是本人进入图形渲染的一个里程碑,感觉这的确是个美术活。技术不过是让你能进入赛道罢了,真正决定效果的还得看美术的理念。

Demo的卡通渲染方案相关推荐

  1. 【非真实渲染】【卡通渲染技术点介绍】

    阅读指南 文本介绍卡通渲染的基本技术,实现会放在另外的文档 关键词 Cel Shading,ToonShading,色块.色调,各向异性,描边,高光 特征 看起来像手绘的图片 少渐变(指光影的变换), ...

  2. 卡通渲染及其相关技术总结

    原文链接https://blog.uwa4d.com/archives/usparkle_cartoonshading.html 这是侑虎科技第246篇原创文章,感谢作者洛城供稿,欢迎转发分享,未经作 ...

  3. 从《BLAME!》说开去——新一代生产级卡通真实感混合的渲染方案Maneki

    <BLAME!>是Polygon Pictures Inc.(以下简称PPI)创业33周年以来制作的第一部CG剧场电影,故事来自于贰瓶勉的同名漫画作品(中文译名为<探索者>或者 ...

  4. unity 3d物体描边效果_从零开始的卡通渲染描边篇

    序言: 一直对卡通渲染非常感兴趣,前后翻找了不少的文档,做了一些工作.前段时间<从零开始>的手游上线了,试着渲染了一下的其中模型,觉得效果很不错.打算写一个专栏记录其中的渲染技术.在后面的 ...

  5. Unity-ShaderLab 逆向还原《原神》角色卡通渲染思路与实现(保姆级教学)-2

    一转眼已经有一个多月没更新了额,时间过得真快.今天终于决定要更新Part 2了. ----那我们就继续吧. 上篇已经讲完了漫反射.高光和光照贴图,本篇将会介绍角色背光时边缘光与基于阈值图的面部阴影的实 ...

  6. Unity Shader卡通渲染 · 高清渲染管线·HDRP

    Unity Shader卡通渲染 · 高清渲染管线·HDRP 前言 最近在研究HDRP管线中的卡通渲染,就想着能不能把官方的UCTS移植到HDRP管线里面去,说干就干,到昨天晚上上传了github,今 ...

  7. 二次元卡通渲染之描边

    前言 本文为"优梦创客"原创文章,您可以自由转载,但必须加入完整的版权声明 更多学习资源请加QQ:1517069595获取(企业级性能优化/热更新/Shader特效/服务器/商业项 ...

  8. 日式卡通渲染笔记(罪恶装备 碧蓝幻想 原神 战双)

    **裁边漫反射(StepDiffuse) 传统的漫反射是这样** float NL = dot(N,L); float3 Diffuse = max(0,NL);//也可以除以PI 而卡通渲染里希望明 ...

  9. [图形学] 《Real-Time Rendering》卡通渲染

    原文:<Real-Time Rendering,Third Version> Toon Shading章节 11.1 卡通着色 正如不同的字体能给文字带来不同的风格一样,不同的渲染风格也有 ...

最新文章

  1. Python基础02-序列及通用操作
  2. Django(part41)--中间键Middleware
  3. 在STM32CubeMX生成的MDK5工程上添加RT-Thread Nano后双击工程名无法打开.map文件的解决方法
  4. 【文末有赠书】从历史角度讲现代数学
  5. 对bmp文件内存压缩 与 解压缩
  6. git学习(五)分支操作和解决冲突
  7. 解决电脑右键新建没有文本文档的问题
  8. 如何读取H264文件获得每一帧的数据(VsParserPro)
  9. Maven的安装与配置
  10. google chrome 浏览器 必备插件
  11. WordPress直接调用头像地址
  12. 单核cpu多核cpu如何执行多线程
  13. Logic Synthesis And Verification Algorithms Gary D. Hachtel Fabio Somenzi 第十章
  14. 使用记录6_发布微信小游戏
  15. Flink基础系列8-Flink on yarn运行wordcount程序
  16. java ca认证_java编程方式生成CA证书
  17. 幻核退出 “数字藏品有何用”阶段性无解
  18. openssl RSA 内存读取密钥
  19. 《Oracle Concept》第三章 - 12
  20. 程序员成就技术大拿之路

热门文章

  1. 获取安卓应用包名和入口 Activity
  2. shell编程实例练习
  3. 什么是归一化,它与标准化的区别是什么?
  4. 纽约市街道规划设计与治理启示
  5. python opencv设置不同的视频编解码器参数
  6. 【推荐】2022年公用事业行业研究报告产业发展前景市场投资行情分析白皮书(附件中为网盘地址,报告持续更新)
  7. mongoose http 源码解析(1)
  8. 诸葛亮86字的家训 影响一生!
  9. oracle ssd加速,评测 | Intel Optane SSD 加速 SmartX 超融合在 Oracle 等场景下的系统性能...
  10. 攻防世界MISC练习区(SimpleRAR、base64stego、功夫再高也怕菜刀)