Cocos Creator - 塞尔达的3D渲染风格,能在小游戏跑起来?
塞尔达的3D渲染风格,能在小游戏跑起来?
渲染系统作为游戏引擎的核心模块,是引擎画面表现力的天花板,直接决定了游戏所能输出给玩家的内容上限。Cocos Creator 3.x 的渲染系统,从架构到设计都是以面向未来、高性能、跨平台为目标,支持开发者制作出更加精致的游戏画面。新版本 Cocos Creator 3.1 已发布,欢迎大家前往下载体验!
近日引擎渲染组使用 Cocos Creator 3.1,参考塞尔达/原神的吉卜力(Ghibli) 卡通渲染风格做了有趣的尝试,大家先看一段编辑器里的操作视频:https://v.qq.com/x/page/i3246ueusbf.html?start=27
由宫崎骏开创的吉卜力风格,并不追求对真实世界的高度逼真再现,而是从色彩、明暗、对比度等方面进行风格独特的艺术表达,在绘画感上更接近于水彩,并大量使用亮色作为主色调,通过艺术化的颜色来展现世界,而非纯粹追求写实,使画面更有卡通幻想色彩。
重要的事情写在前面:
1. 上述视频里的 demo 的所有素材和源码都已开源
仓库地址 https://github.com/cocos-creator/cartoon-vegetation
2. 可以扫描下方二维码直接进入微信小游戏体验,方便懒得下载编译的朋友:
这里强调一下,构建发布到小游戏平台可运行,只是为了尝试性能的极限情况如何,以及方便大家快速体验。在性能内存约束最大的小游戏上都能运行的话,PC Web、手机原生的性能就更没问题了。
小游戏版本的已知问题:
- 默认设置下,由于 ShadowMap 精度不够导致树叶有些闪动。在高端安卓手机上可以通过游戏内「左上角齿轮」 -> 开启「High Quality」解决该问题。但 iPhone12 由于没有 JIT,开启 High Quality 之后会掉帧到30以下。
- 已知在部分安卓手机上由于不支持 WebGL 的 float texture 导致「Bend Glass」,也就是角色行走处的草被压平效果无法正确出来。该问题在 iPhone 上没有。
(哎,所以引擎的全平台适配真是个苦力活)
下文将详细介绍植被渲染的部分以及模型与人物互动,希望对大家有所帮助。
01 工具
引擎版本:Cocos Creator 3.1
DCC工具:Blender
Demo 中的人物是一个塞尔达爱好者的同人作品,作者提供了很多人物模型来给大家免费使用。
地址:https://twitter.com/artstoff
由于下载下来的模型是没有动作的,可以在以下网站上搜索一些简单的动作来使用。
地址:https://www.mixamo.com/
02 植被渲染
初始状态
植被初始状态相当于引擎默认的 unlit 材质,只能设置贴图和基础颜色。整个画面都是绿色,显得比较平淡,没有任何氛围和辨识度。
vec2 uv = mainTiling.xy + mainTiling.zw * v_uv;
vec4 col = texture(mainTexture, uv);
添加修改色
我们可以添加草地的修改色,并基于噪声贴图得到一个平滑随机数 0-1,在原有基础色进行平滑过渡,这样草地颜色会变得更丰富明亮。
float rand = texture(randMap, worldPos.xz * randMapTiling).r;
col.rgb = mix(col.rgb, hue.rgb, rand * hue.a);
模拟 AO
一般草地的根部或者树叶根部受光度比较少,因此颜色应该比叶尖处更暗一些,我们可以简单利用 uv 值来得到草地根部到顶端的明暗度差,基于这个数据来模拟计算 AO 值,或者把明暗信息在建模时预先存储在顶点颜色中。
float getMask () {#if Mask_Type == Mask_Channel_Colorreturn a_color.r;#elif Mask_Type == Mask_Channel_Uvreturn 1. - a_texCoord.y;#elsereturn 0.;#endif
}
图中使用 AO mask 作为颜色输出,可以明显看到 AO mask 的值从顶部到根部逐渐变小。
float mask = getMask();
float ao = mix(col.a, col.a * mask, ambientOcclusion);
col.rgb *= ao;
把 ambientOcclusion 暴露为材质参数,结合之前颜色输出的效果如下图:
摆动草地
草地颜色已经比较丰富了,现在可以赋予草地生命力,让它随风摆动起来。
先简单让它按照 Sine(正弦) 曲线随时间摆动,然后通过 windStrength 和 windSpeed 调节参数。
需要注意的是能被吹动的只有叶尖,根部是不需要摆动的。这和我们之前得到是 AO Mask 明暗度的过度是一样的。所以我们可以利用之前计算好的 Mask 值乘上 Sine,来做简单模拟。
vec4 offset = vec4(0.);float strength = windStrength;
float sine = sin(windSpeed * (cc_time.x ));
sine = sine * mask * strength;// 计算 xz 平面偏移
offset.xz = vec2(sine);// 计算 y 方向弯曲度
float windWeight = length(offset.xz) + 0.0001;
windWeight = pow(windWeight, 1.5);
offset.y = windWeight * mask;
我们发现草左右来回大幅度摆动看起来是比较奇怪的,正常情况下草地被吹动后回摆的幅度是小而自然的。
因此需要把 Sine 计算结果的范围由 (-1, 1) 重新映射到 (0, 1) 上,并通过参数 windSwinging 来调节摆动幅度。
sine = mix(sine * 0.5 + 0.5, sine, windSwinging);
现在看上去好一些了,但是摆动太规整没有层次感。所以我们需要给每个顶点计算不一样的摆动值,这里简单使用模型坐标系下的坐标值计算顶点强度。
float f = length(positionOS.xz) * windRandVertex;
// 重新计算 sin 值,rand 为之前用 noise 贴图获取的噪声值,rand随机范围0-1
float sine = sin(s.speed * (cc_time.x + rand + f));
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2VeA9gJ9-1621391745230)(https://mmbiz.qpic.cn/mmbiz_gif/jlMCD4Fz8dhCgKAbAPATINxLiacWElD4PUUsoIYYglF2xRccBElMHWkluxicKVnicstibbNyaAoxSMzZg8WuZOEEkA/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1)]
阵风效果
风不是一直连续刮过来的,一般是一股一股风吹过来。下图来自现实中阵风吹过草地的效果,可以看到草地在局部中会形成明暗变化。
我们可以使用噪声贴图来模拟这一效果。
vec2 gustUV = (worldPos.xz * windGustFrequency * windSpeed) + (cc_time.x * windSpeed * windGustFrequency) * -windDirection。xy;
float gust = texture(windMap, gustUV).r;
gust *= windGustStrength * mask;// col 为之前计算的结果
col.rgb += (gust * v_color.a * windGustTint);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hAdADBeR-1621391745232)(https://mmbiz.qpic.cn/mmbiz_gif/jlMCD4Fz8dhCgKAbAPATINxLiacWElD4PfVK2evezpkbEt4kKAe5Mvdp3uuWjCsOhWCcvB5vywbzPlTZAhkCgng/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1)]
雾效与天空盒
到现在草地的效果已经有了,不过玩家还感受不出场景的纵深感,并且场景的背景还是默认色。我们可以利用引擎内置的雾效和天空盒来增加画面细节。
天空盒对于增强画面感是非常重要的,当处于玩家视角的情况下,大概有四分之一到一半的画面是由天空盒来填充的,选择一个合适的天空盒非常重要。
这个场景我选了一个偏淡的天空盒,搭配偏白色的雾效可以使场景尽头和天空盒的色调比较接近。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DcIopbMD-1621391745232)(https://mmbiz.qpic.cn/mmbiz_gif/jlMCD4Fz8dhCgKAbAPATINxLiacWElD4P4Hr3c75AMHBN6ltJUGMfj5e26D3YFqYhZVJXhXtLibfv7ic82aCmh4TQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1)]
阴影
这次风格化渲染的实现中没有使用光照模型来计算光照效果,而是使用阴影计算的结果来增加画面细节的。
// getShadowAttenuation 参考了引擎内部计算阴影的逻辑来获取阴影强度
float getShadowAttenuation () {float shadowAttenuation = 0.0;#if CC_RECEIVE_SHADOW// cc_shadowInfo 的定义可以在引擎中 cc-shadow.chunk 文件中找到, 其中的数据格式为 :// x -> width; y -> height; z -> pcf; w -> bais;// z -> pcf 对应的是场景中设置 pcf 选项的值float pcf = cc_shadowInfo.z + 0.001;// CCGetShadowFactorXX 为引擎内部 PCF 阴影计算方法, 后面的数字表示采样 shadowmap 的次数,采样次数越多获得的阴影模糊效果越好,表现得越柔软,消耗的性能也越高if (pcf > 3.0) {shadowAttenuation = CCGetShadowFactorX25();}else if (3.0 > pcf && pcf > 2.0) {shadowAttenuation = CCGetShadowFactorX9();}else if (2.0 > pcf && pcf > 1.0) {shadowAttenuation = CCGetShadowFactorX5();}else {shadowAttenuation = CCGetShadowFactorX1();}#endifreturn shadowAttenuation;
}float shadowAttenuation = getShadowAttenuation();
**PCF** 阴影设置:![12](./pics/12.png)// 获取阴影强度,并暴露参数 shadowIntensity 自由调节阴影强度
shadowAttenuation = 1. - min(shadowAttenuation, shadowIntensity);col.rgb *= shadowAttenuation;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cOAprmRz-1621391745233)(https://mmbiz.qpic.cn/mmbiz_gif/jlMCD4Fz8dhCgKAbAPATINxLiacWElD4PKCIk55xn9PicaQxWwBQ8ibWibWmicF5z3o7ibMX2ibxaoeNP3cWxWqv352dg/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1)]
半透明效果
这里说的半透明效果不是指玻璃那种能透过物体看到其他物体的效果,而是指阳光能穿过树叶继续照射的效果。当我们的视线向着阳光的方向看树叶的时候,会发现树叶显得更加明亮。
按照上面的描述我们可以概括为当树叶到摄像机的方向,与阳光到树叶方向一致的时候树叶应该显得更加明亮,使用 overlay 叠加效果可以简单模拟明亮的效果。
// 主光源也就是太阳光的方向
vec3 ld = normalize(cc_mainLitDir.xyz);// viewDirectionWS 为树叶到摄像机的方向
vec3 viewDirectionWS = normalize(cc_cameraPos.xyz - worldPos.xyz);// translucency 暴露为材质参数方便调整效果
float VdotL = max(0., dot(viewDirectionWS, ld)) * translucency;
VdotL = pow(VdotL, 4.) * 8.;float tMask = VdotL * shadowAttenuation;vec3 tColor = col.rgb + BlendOverlay(cc_mainLitColor.rgb * cc_mainLitColor.w, color);col.rgb = mix(col.rgb, tColor, tMask);
与植被的互动
当玩家在草地上移动时,草地受到玩家碰撞挤压,应该是会有明显向周围弯曲的。
要做到这一点,我们需要将希望产生交互的物体绘制到一张高度贴图上,贴图中的信息包括物体的高度、物体在X、Z 轴上挤压的方向、力度。
渲染到高度图中的信息:
float mask = -v_normal.y * heightStrength;// * v_color.r;
float height = (v_position.y + heightOffset);
// 将值从 (-1, 1) 重新映射到 (0, 1)
vec2 dir = (v_normal.xz * extendStrength) * 0.5 + 0.5;
vec4 heightMapInfo = vec4(dir.x, height, dir.y, mask);
生成高度图的方法是用一个垂直向下的摄像机拍摄所有需要互动的物体,在 Demo 中摄像机会一直跟随主角移动,可以随时修改摄像机拍摄的范围。
当渲染物体到高度图上的时候,我们并不需要把原有主角整个完整渲染上去,因为主角的面数一般会比较多,为了节约一些性能,可以用一个大小相近但是面数比较少的物体来做近似渲染。
参考下图,使用一个圆柱体来代替主角渲染到高度图上,并且我们可以自由改变圆柱体大小来控制渲染范围。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rqprTMmh-1621391745235)(https://mmbiz.qpic.cn/mmbiz_gif/jlMCD4Fz8dhCgKAbAPATINxLiacWElD4P7lEnLQ00GG3fnvwnOM2gNGGez2k8R2eVb3sOKqsF8TYnggialklN6NQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1)]
下面再看下在草地材质中如何获取到高度图里面的信息:
// cc_grass_bend_uv 是在自定义管线里面计算的结果
// cc_grass_bend_uv.xy 为高度图摄像机的世界坐标
// cc_grass_bend_uv.z 为高度图摄像机拍摄的范围// 使用像素的世界坐标值减去高度图摄像机的世界坐标再除以范围就可以得到高度图中的 uv 坐标
vec2 getBendMapUV(in vec3 wPos) {vec2 uv = (wPos.xz - cc_grass_bend_uv.xy) / cc_grass_bend_uv.z + 0.5;return uv;
}// cc_grass_bend_map 就是高度贴图了
vec4 getBendVector(vec3 wPos) {vec2 uv = getBendMapUV(wPos);vec4 v = texture(cc_grass_bend_map, uv);//Remap from 0.1 to -1.1v.x = v.x * 2.0 - 1.0;v.z = v.z * 2.0 - 1.0;return v;
}
以上就是 Cocos 引擎渲染组为大家献上的吉卜力卡通风格渲染的全部分享,视频、源码、小游戏体验、实现步骤讲解都已奉上。
Cocos Creator - 塞尔达的3D渲染风格,能在小游戏跑起来?相关推荐
- 塞尔达的3D渲染风格,能在小游戏跑起来?
渲染系统作为游戏引擎的核心模块,是引擎画面表现力的天花板,直接决定了游戏所能输出给玩家的内容上限.Cocos Creator 3.x 的渲染系统,从架构到设计都是以面向未来.高性能.跨平台为目标,支持 ...
- Cocos creator实现飞机大战空中大战《战击长空》小游戏资源及代码
Cocos creator实现飞机大战空中大战小游戏资源及代码 最近在学习Cocos Creator,作为新手,刚刚开始学习Cocos Creator,刚刚入门,这里记录一下飞机大战小游戏实现.搜索微 ...
- 塞尔达传说gba_1986版塞尔达 回顾34年经典系列历代作品 满分最多系列游戏
塞尔达系列的男主林克已经陪伴我们34年了,小蟹最早是在GB塞尔达梦见岛,当时是日语,也没有资料可查,也是各种试玩到中间,实在过不了.正式玩过中文是GBC塞尔达时空之章和大地之章,这二个版本也是第一次塞 ...
- Unity Shader 卡通渲染 (三):仿塞尔达荒野之息 Shader(顶点色控制细节)
上一篇传送门: https://blog.csdn.net/qq_27534999/article/details/100925621 顶点色在卡通渲染中有挺多应用,本篇会在上一篇的基础上,运用模型顶 ...
- 塞尔达风之杖技术分析-角色渲染和面部表情
塞尔达风之杖技术分析-角色渲染和面部表情. https://zhuanlan.zhihu.com/p/26140321 首发于游戏开发启示录 塞尔达风之杖技术分析-角色渲染和面部表情 拳四郎 游戏 ...
- Unity Shader 卡通渲染 (一):仿塞尔达荒野之息 Shader(简易版)
温馨提示: 本系列文章面向那些 Shader 刚刚入门,想寻求进一步提升的群体,如果对 Shader 一无所知的话,建议自行搜索其他 Shader入门教程观看学习,再食用本系列文章. 前言: 说起卡通 ...
- 被刷屏的塞尔达来了,附源码!
渲染系统作为游戏引擎的核心模块,是引擎画面表现力的天花板,直接决定了游戏所能输出给玩家的内容上限.Cocos Creator 3.x 的渲染系统,从架构到设计都是以面向未来.高性能.跨平台为目标,支持 ...
- 7句话让Codex给我做了个小游戏,还是极简版塞尔达,一玩简直停不下来
点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 梦晨 萧箫 发自 凹非寺 量子位 | 公众号 QbitAI 什么,7 ...
- RenderDoc塞尔达荒野之息抓帧分析
RenderDoc是一种抓帧工具,主要用来分析游戏开发中渲染流程,官网: https://renderdoc.org/ 我是用的版本是RenderDoc_1.14_64.zip 塞尔达荒野之息使用Ce ...
最新文章
- python3调用OCR识别
- 100内奇数之和流程图_IATF 16949体系资料之过程流程图,收藏备用!
- substring not found什么意思_英文写作中,除了not,你还会其他否定表达吗?
- spark2.0.1 安装配置
- vue项目中 axios请求拦截器与取消pending请求功能 - 年少、 - 博客园
- CodeForces - 76E Points
- 移动应用开发商的生存之道
- 拓端tecdat|R语言Black Scholes和Cox-Ross-Rubinstein期权定价模型案例
- selenium IDE下载及使用
- 21天通关python 磁力_python 磁力链接搜索器
- isupper()函数
- Java技术未来十年发展
- ospf配置小实验及安全认证
- 小型元器件介绍:排阻
- 对抗样本生成算法复现代码解析:FGSM和DeepFool
- 临床执业助理医师(综合练习)题库【2】
- 《2015互联网安全年报》,移动端成重灾区,黑灰产日益成熟
- linux中的du命令和df命令和fdisk命令
- invalid button size rid: 5fd188c8-1d752f57-0d53f85f
- FX3U控制松下服务器位置不准,三菱FX3UPLC如何控制松下伺服_.docx
热门文章
- 在JSP中使用数据库
- ABAQUS 二次开发 简单插件制作
- Openlayers 圆的操作
- 场景法(流程图法)、错误推测法
- 现在CCIE还是敲门砖吗?
- SolidWorks 2010 SP0.0 三维机械设计
- 数的进制转换:十进制转二进制、十六进制转二进制、二进制转八进制
- TMS320F2812需要注意的几点
- FileNotFoundException: /storage/emulated/0/Pictures/1582: open failed: EACCES (Permission denied)
- layui动态渲染上传文件功能点击选择文件没反应解决方案