unity3d dc多个plan拼接的地面 怎么降低dc 用的同一个材质

程序写累了,就来玩玩酷跑小游戏吧,嘿嘿。
雨松MOMO送你一首歌曲,嘿嘿。
UGUI研究院之全面理解图集与使用(三)
UGUI研究院之全面理解图集与使用(三)
围观196363次
编辑日期: 字体:
UGUI的图集打包与工作原理,整整看了一天多,终于看明白了~晕~还是记录一下我研究的成果,也希望大家在下面给我留言我们一起讨论一下。
先说说UGUI的Atlas和NGUI的Atlas的区别,NGUI是必须先打出图集然后才能开始做界面。这一点很烦,因为始终都要去考虑你的UI图集。比如图集会不会超1024 ,图集该如何来规划等等。而UGUI的原理则是,让开发者彻底模糊图集的概念,让开发者不要去关心自己的图集。做界面的时候只用小图,而在最终打包的时候unity才会把你的小图和并在一张大的图集里面。然而这一切一切都是自动完成的,开发者不需要去care它。
如下图所示,Editor-&Project Settings 下面有sprite packer的模式。Disabled表示不启用它,Enabled For Builds 表示只有打包的时候才会启用它,Always Enabled 表示永远启用它。 这里的启用它就表示是否将小图自动打成图集。
我的选项是Always Enabled 。因为开发的时候我们需要清楚的看到现在是几个Draw Call,从而才能优化小图。在最终打包的时候unity会自动构建大的图集,可是我开发的时候就想看图集会占几个Draw Call,这怎么办呢?如下图所示,首先将你的图片拖入unity中,将同一图集的所有图片的packing tag设置成一个名子即可。
注意你的图片不能放在Resources文件夹下面,Resources文件夹下的资源将不会被打入图集,切记(也就是在这里混淆了我很久)。然后在Windows-&Sprite Packer 里,点击packer 在这里你就可以预览到你的图集信息。图集的大小还有图集的格式等等很多参数我们都是可以控制的,也可以通过脚本来设置。我在下一篇文章里详细说这个(请期待嘿嘿)。
图集的预览紧紧是让你看看你的图集大概张什么样子。那么我们的图集的这张图片保存在了哪里呢?它保存在和Assets文件夹同级的目录,Libary/AtlasCache里面。你不用管它,也不要删除它,就算你删除了也没用因为只要你打包,它就会生成并且会打到包中。
此时在Hierarchy视图中创建两个Image对象。如下图所示,我们可以清楚的看到此时我的draw call已经被合并成了1 。
这两个图片是我是在Editor模式下预先拖入Hierarchy视图中的,可是如果我想运行时根据图片的名子来动态创建精灵该如何?可是unity根本没有提供加载图集的方法,也没有提供加载图集上某个图片的方法。 因为UGUI就不像让开发者有图集的这个概念,可是我们肯定是要实现这个需求的。。怎么办呢?
第一个设想,先把散=小图打包成图集,然后再把所有散图拷贝在Resources文件夹下,这样运行时就能用Resources.load了。
第二个设想,还是先把小图打成图集,然后把所有小图关联在prefab上,拷贝在Resources文件夹下,这样运行时也能用Resources.load了。到底那个靠谱呢? 给大家看一个图大家就知道答案了。
如下图所示,打成图集的图片如果在放在Resources那么资源就变成双份了。。 所以我们只能把小图关联在Prefab上,把所有的Prefab放在Resources下面,这样就不占用多余的空间了。
好了,现在方法我们已经掌握,那么就开始写工具吧。如下图所示可以按文件夹分,每一个文件夹就是一个图集。然后每一张小图创建一个Prefab,Prefab的名子就起小图的名子,文件关联在Resources下面。
代码比较简单,我就不注释了。
123456789101112131415161718192021222324
[MenuItem ("MyMenu/AtlasMaker")] static private void MakeAtlas() {
string spriteDir = Application.dataPath +"/Resources/Sprite";
if(!Directory.Exists(spriteDir)){
Directory.CreateDirectory(spriteDir);
DirectoryInfo rootDirInfo = new DirectoryInfo (Application.dataPath +"/Atlas");
foreach (DirectoryInfo dirInfo in rootDirInfo.GetDirectories()) {
foreach (FileInfo pngFile in dirInfo.GetFiles("*.png",SearchOption.AllDirectories)) {
string allPath = pngFile.FullName;
string assetPath = allPath.Substring(allPath.IndexOf("Assets"));
Sprite sprite = Resources.LoadAssetAtPath&Sprite&(assetPath);
GameObject go = new GameObject(sprite.name);
go.AddComponent&SpriteRenderer&().sprite = sprite;
allPath = spriteDir+"/"+sprite.name+".prefab";
string prefabPath = allPath.Substring(allPath.IndexOf("Assets"));
PrefabUtility.CreatePrefab(prefabPath,go);
GameObject.DestroyImmediate(go);
然后是运行时的代码。
123456789101112131415161718192021222324252627
using UnityEngine;using System.Collections;using UnityEngine.UI;&public class UIMain : MonoBehaviour {& void Start ()
CreatImage(loadSprite("image0"));
CreatImage(loadSprite("image1")); }& private void CreatImage(Sprite sprite ){
GameObject go = new GameObject(sprite.name);
go.layer = LayerMask.NameToLayer("UI");
go.transform.parent = transform;
go.transform.localScale= Vector3.one;
Image image = go.AddComponent&Image&();
image.sprite = sprite;
image.SetNativeSize(); }& private Sprite loadSprite(string spriteName){
return Resources.Load&GameObject&("Sprite/" + spriteName).GetComponent&SpriteRenderer&().sprite; }&}
因为这两个图是在同一个图集上,所以drawcall就是1了。这样我们就可以根据图片的名子来运行时加载图片了。
接下来就是Assetbundle了,如果我们的图集需要在线更新那该怎么办呢? 其实Assetbundle比Resources要更简单一些,无论如何我们要先开始打图集。
12345678910111213141516171819202122232425262728293031323334
[MenuItem ("MyMenu/Build Assetbundle")] static private void BuildAssetBundle() {
string dir = Application.dataPath +"/StreamingAssets";&
if(!Directory.Exists(dir)){
Directory.CreateDirectory(dir);
DirectoryInfo rootDirInfo = new DirectoryInfo (Application.dataPath +"/Atlas");
foreach (DirectoryInfo dirInfo in rootDirInfo.GetDirectories()) {
List&Sprite& assets = new List&Sprite&();
string path = dir +"/"+dirInfo.Name+".assetbundle";
foreach (FileInfo pngFile in dirInfo.GetFiles("*.png",SearchOption.AllDirectories))
string allPath = pngFile.FullName;
string assetPath = allPath.Substring(allPath.IndexOf("Assets"));
assets.Add(Resources.LoadAssetAtPath&Sprite&(assetPath));
if(BuildPipeline.BuildAssetBundle(null, assets.ToArray(), path,BuildAssetBundleOptions.UncompressedAssetBundle| BuildAssetBundleOptions.CollectDependencies, GetBuildTarget())){
static private BuildTarget GetBuildTarget()
BuildTarget target = BuildTarget.WebPlayer;#if UNITY_STANDALONE
target = BuildTarget.StandaloneWindows;#elif UNITY_IPHONE
target = BuildTarget.iPhone;#elif UNITY_ANDROID
target = BuildTarget.Android;#endif
return target;
如下图所示,我的assetbundle已经打出来了。
然后把UIMain.cs在改一改。
12345678910111213141516171819202122232425262728293031323334
using UnityEngine;using System.Collections;using UnityEngine.UI;&public class UIMain : MonoBehaviour {& AssetBundle assetbundle = null; void Start ()
CreatImage(loadSprite("image0"));
CreatImage(loadSprite("image1")); }& private void CreatImage(Sprite sprite ){
GameObject go = new GameObject(sprite.name);
go.layer = LayerMask.NameToLayer("UI");
go.transform.parent = transform;
go.transform.localScale= Vector3.one;
Image image = go.AddComponent&Image&();
image.sprite = sprite;
image.SetNativeSize(); }& private Sprite loadSprite(string spriteName){#if USE_ASSETBUNDLE
if(assetbundle == null)
assetbundle = AssetBundle.CreateFromFile(Application.streamingAssetsPath +"/Main.assetbundle");
return assetbundle.Load(spriteName) as Sprite;#else
return Resources.Load&GameObject&("Sprite/" + spriteName).GetComponent&SpriteRenderer&().sprite;#endif
如下图所示,依然还是一个drawcall。
衷心希望有经验的朋友在留言处给我提提意见, 或者大家一起讨论讨论。。 我们共同为把NGUI干掉的目标而奋斗,嘻嘻。
本文固定链接:
转载请注明:
雨松MOMO提醒您:亲,如果您觉得本文不错,快快将这篇文章分享出去吧 。另外请点击网站顶部彩色广告或者捐赠支持本站发展,谢谢!
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
如果您愿意花10块钱请我喝一杯咖啡的话,请用手机扫描二维码即可通过支付宝直接向我捐款哦。
您可能还会对这些文章感兴趣!一个prefab 的drawcalls是3个
如果复制100个场景中的drawcalls
会增加吗 ?
回答送花花
&一个prefab 的drawcalls是3个 & 如果复制100个场景中的drawcalls & 会增加吗 ?
726 || this.offsetHeight>700){if(this.offsetWidth/726 > this.offsetHeight/700){this.width=726;}else{this.height=700;}}" style="max-width:726max-height:700" title="点击查看原图" onclick="if(this.parentNode.tagName!='A') window.open('http://www.ceeger.com/forum/attachment/1307/thread/2_18503_bec95cd0af03395.png');" />
&
如果每一个prefab中的3个对象上面使用不同的材质 那???
例如复制的prefab1(clone)上面分别使用材质1,2,3
prefab2(clone)上面分别使用材质11,21,31
以此类推100个之后 & &场景中的drawing calls是多少啊 & 为什么???
请各位大神帮帮忙 &谢谢;
要评论请先&或者&
大神在哪里 &
自己试试嘛。。。
一个材质球占一个drawcall & 100个物体用一个材质球drawcall 还是1
对的,看材质球是否一样,如果一样,Unity会一起绘制,然后Drawcall就是1。可以在Game Statistics里看到当前场景Drawcall的数量。
嗯 这个具体的你可以看一下drawcall的优化,类似于如何降低drawcall值,里面的材质球的复用,缩放比例之类的有关的,同一类的可以进行批处理,同一批处理的就使用一个drawcall
但是我也有点糊涂,因为如果说复用材质球可以降低dc的值,但是一个prefab复制成5个结果还是消耗了5个dc,这样貌似有点矛盾。期待大神解答
将多个物体渲染在一个drawcall的过程称为batching,有太多因素可以打破batching了,参看官方手册
1.非等比tansform的scale能打破batching
2. 接受实时阴影的物体不会被batching
3. Multi-pass 的shader也会打破batching,
...还有很多其他因素,自己差手册吧。。。
我面前有碗饭 吃下去肯定不饱 现在有100碗能饱吗 请大神帮忙
:但是我也有点糊涂,因为如果说复用材质球可以降低dc的值,但是一个prefab复制成5个结果还是消耗了5个dc,这样貌似有点矛盾。期待大神解答 是不是你的一个Prefab &本来就5个 DCUnity优化技巧(下)-GAD腾讯游戏开发者平台Unity(37)
Unity可以勾选static来进行静态合批, 运行时也可以 StaticBatchingUtility.Combine()来进行合批。但是你仔细观察一下。。
如下图所示,这里我有两个相同Shader、相同参数的材质。
我都勾选了static以后运行发现dc还是会多一个。因为unity不会帮你自动合并这种情况的材质。如果你打包成android的apk后,解开包可以看见这里还是会有两个文件, GUID对应的就是这两个材质。因为场景里有地方分别引用了这两个材质。
所以项目里如果这种材质比较多的话, 会增加DrawCall 、Android平台下的话多少也会影响安装速度。(一两个可能看不出来,优化效率是积少成多能避免则避免)
为什么会这样?
1.Unity会自动根据fbx来生成一个对应的材质文件。(虽然可以禁止FBX生成材质,但是2号问题没法让美术避免)
2.美术在做场景的时候可能会无意间创建出来很多相同shader、相同参数、相同贴图、的材质出来。(个人觉得这个很难让美术避免)
如何解决这个问题?
1.美术来避免,我觉得很难。因为美术做场景都是一点点做。发现这里少个材质,自己就创建一个。发现需要某个shader自己就选一个。 做着做着他们也不知道自己之前有没有用过相同的材质。。所以冗余材质自然就出来了。。。
2.程序来做检查了。
Unity没有提供这样的API。但是我想了一个办法来判断两个不同文件的材质是否参数完全相同。就是利用unity的forceText 来对比。如果两个材质完全一样,除了名字以外别的信息是完全一样的。。
static bool Compare(Material m1, Material m2)
EditorSettings.serializationMode = SerializationMode.ForceT
string m1Path = AssetDatabase.GetAssetPath(m1);
string m2Path = AssetDatabase.GetAssetPath(m2);
if(!string.IsNullOrEmpty(m1Path) && !string.IsNullOrEmpty(m2Path))
string rootPath = Directory.GetCurrentDirectory();
m1Path = Path.Combine(rootPath,m1Path);
m2Path = Path.Combine(rootPath,m2Path);
string text1 = File.ReadAllText(m1Path).Replace(" m_Name: " + m1.name ,"");
string text2 = File.ReadAllText(m2Path).Replace(" m_Name: " + m2.name ,"");
return (text1 == text2);
我又做了一个方法来批量合并替换当前场景重复的Material,原理就是查找所有的MeshRenderer找到相同的就合并。(算法还有优化空间, 不过我测试了一下我们场景速度还可以接受)
static private void DeleteSameMaterial ()
Dictionary&string,string& dicMaterial = new Dictionary&string, string&();
MeshRenderer[] meshRenderers = Resources.FindObjectsOfTypeAll&MeshRenderer&();
string rootPath = Directory.GetCurrentDirectory();
for(int i =0; i& meshRenderers.L i++)
MeshRenderer meshRender = meshRenderers[i];
Material[] newMaterials = new Material[meshRender.sharedMaterials.Length] ;
for(int j =0; j & meshRender.sharedMaterials.L j++)
Material m =
meshRender.sharedMaterials[j];
string mPath = AssetDatabase.GetAssetPath(m);
if(!string.IsNullOrEmpty(mPath) && mPath.Contains("Assets/"))
string fullPath = Path.Combine(rootPath,mPath);
string text = File.ReadAllText(fullPath).Replace(" m_Name: " + m.name ,"");
if(!dicMaterial.TryGetValue(text,out change))
dicMaterial[text] =
change = mP
newMaterials[j] = AssetDatabase.LoadAssetAtPath&Material&(change);
meshRender.sharedMaterials = newM
在一个合适的地方标记一下 EditorSceneManager.MarkAllScenesDirty(); 不然有可能没法保存~
最后,欢迎大家帮我测试一下。也欢迎大家提出宝贵意见~&p&为何要引入四元数?首先是因为欧拉角有万向节死锁的问题。&/p&&p&3D游戏或者3D电影中,比如黑客帝国中酷炫的旋转是怎么实现的?&/p&&figure&&img src=&https://pic4.zhimg.com/v2-979bc20dbd7e9cf3ebdefb0c5912317b_b.jpg& data-rawwidth=&320& data-rawheight=&179& class=&content_image& width=&320&&&/figure&&p&旋转的算法有很多,这里主要介绍其中一种:欧拉角。&/p&&p&&b&1 欧拉角&/b&&/p&&p&&b&1.1 欧拉角的算法思想是什么&/b&&/p&&p&陌生的你来到了成都,站在盐市口茫然四顾,想知道春熙路怎么走?&/p&&p&这个时候你选择了去问路,得到了两种回答:&/p&&ul&&li&往东经104°04′、北纬30°40′走&/li&&li&右转后一直走&/li&&/ul&&p&第一种回答,告诉了你春熙路的绝对坐标,可是很反人类啊!&/p&&p&第二种回答,告诉了你春熙路的相对坐标,很具有操作性。&/p&&p&欧拉角算法的思想就是采用的第二种回答的方式,优点在于很好理解。&/p&&p&&b&1.2 具体实现步骤&/b&&/p&&p&&a href=&//link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Euler_angles& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&维基百科&/a& 中,有这么一副动图,清楚的表明了如何通过欧拉角来完成旋转:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-35da80e428ca5750491ffd_b.jpg& data-rawwidth=&368& data-rawheight=&348& class=&content_image& width=&368&&&/figure&&p&具体来拆解下旋转步骤,先看图:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-df645c9b0d0a01504d5f_b.jpg& data-rawwidth=&400& data-rawheight=&451& class=&content_image& width=&400&&&/figure&&p&图中有两组坐标:&/p&&ul&&li&&img src=&//www.zhihu.com/equation?tex=xyz& alt=&xyz& eeimg=&1&& 为全局坐标,保持不动&/li&&li&&img src=&//www.zhihu.com/equation?tex=XYZ& alt=&XYZ& eeimg=&1&& 为局部坐标,随着物体一起运动&/li&&/ul&&p&旋转步骤如下:&/p&&ul&&li&物体绕&b&全局&/b&的 &img src=&//www.zhihu.com/equation?tex=z& alt=&z& eeimg=&1&& 轴旋转 &img src=&//www.zhihu.com/equation?tex=%5Calpha+& alt=&\alpha & eeimg=&1&& 角&/li&&li&继续绕&b&自己&/b&的 &img src=&//www.zhihu.com/equation?tex=X& alt=&X& eeimg=&1&& 轴(也就是图中的 &img src=&//www.zhihu.com/equation?tex=N& alt=&N& eeimg=&1&& 轴)旋转 &img src=&//www.zhihu.com/equation?tex=%5Cbeta+& alt=&\beta & eeimg=&1&& 角&/li&&li&最后绕&b&自己&/b&的 &img src=&//www.zhihu.com/equation?tex=Z& alt=&Z& eeimg=&1&& 轴旋转 &img src=&//www.zhihu.com/equation?tex=%5Cgamma+& alt=&\gamma & eeimg=&1&& 角&/li&&/ul&&p&这里有一副动图很直观的展示了旋转过程(角度标记的有点不一样: &img src=&//www.zhihu.com/equation?tex=%5Cpsi+& alt=&\psi & eeimg=&1&& 对应 &img src=&//www.zhihu.com/equation?tex=%5Calpha+& alt=&\alpha & eeimg=&1&& , &img src=&//www.zhihu.com/equation?tex=%5Ctheta+& alt=&\theta & eeimg=&1&& 对应 &img src=&//www.zhihu.com/equation?tex=%5Cbeta+& alt=&\beta & eeimg=&1&& , &img src=&//www.zhihu.com/equation?tex=%5Cphi+& alt=&\phi & eeimg=&1&& 对应 &img src=&//www.zhihu.com/equation?tex=%5Cgamma+& alt=&\gamma & eeimg=&1&& ),图来自&a href=&//link.zhihu.com/?target=https%3A//gfycat.com/fr/gifs/detail/flakyconventionalbarnowl& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&gfycat&/a&:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-c74e8dbd170834_b.jpg& data-rawwidth=&240& data-rawheight=&138& class=&content_image& width=&240&&&/figure&&p&可能你感到奇怪,为什么第一步是绕着全局坐标旋转?因为要和世界保持联系,否则就和世界完全没有关系了。&/p&&p&还不理解?没有关系,自己动手试试(有三个可以操作的点,分别对应三个角度):&/p&&figure&&img src=&https://pic2.zhimg.com/v2-549df3a0ac9f82cc63ccb1_b.jpg& data-rawwidth=&2300& data-rawheight=&804& class=&origin_image zh-lightbox-thumb& width=&2300& data-original=&https://pic2.zhimg.com/v2-549df3a0ac9f82cc63ccb1_r.jpg&&&/figure&&blockquote&此处有互动内容,&a href=&//link.zhihu.com/?target=http%3A//www.matongxue.com/madocs/442.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&点击此处前往操作。&/a&&/blockquote&&p&很显然,按照不同的旋转步骤,旋转的结果是不一样的。&/p&&p&就好比刚才问路的时候,回答你,“左转再右转”,和“右转再左转”,肯定到达的地点是不一样的。&/p&&p&我们需要把上面的旋转步骤按照顺序标记为 &img src=&//www.zhihu.com/equation?tex=zXZ& alt=&zXZ& eeimg=&1&& ,加上角度就是一个完整的欧拉角:&/p&&p&&img src=&//www.zhihu.com/equation?tex=zXZ-%28%5Calpha+%2C+%5Cbeta+%2C+%5Cgamma+%29& alt=&zXZ-(\alpha , \beta , \gamma )& eeimg=&1&&&/p&&p&&b&2 万向节死锁(Gimbal Lock)&/b&&/p&&p&局部坐标是很直观,但是导致欧拉角有一个重大缺陷,万向节死锁!&/p&&p&本节大部分参考了博文: &a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/AndrewFan/article/details/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&欧拉角与万向节死锁(图文版)&/a& ,博主: &a href=&//link.zhihu.com/?target=http%3A//blog.csdn.net/andrewfan& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&andrewfan&/a& 。&/p&&p&&b&2.1 什么是万向节(Gimbal)&/b&&/p&&blockquote&&b&平衡环架(英语:Gimbal)为一具有枢纽的装置,使得一物体能以单一轴旋转。由彼此垂直的枢纽轴所组成的一组三只平衡环架,则可使架在最内的环架的物体维持旋转轴不变,而应用在船上的陀螺仪、罗盘、饮料杯架等用途上,而不受船体因波浪上下震动、船身转向的影响。&/b&&br&&br&----维基百科&/blockquote&&p&长这个样子:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-2f3d8c9c2de8c7f225abd_b.jpg& data-rawwidth=&250& data-rawheight=&250& class=&content_image& width=&250&&&/figure&&p&中间有一根竖轴,穿过一个金属圆盘。金属圆盘称为转子,竖轴称为旋转轴。转子用金属制成,应该是了增加质量,从而增大惯性。竖轴外侧是三层嵌套的圆环,它们互相交叉,带来了三个方向自由度的旋转。&/p&&p&看着不停转来转去,有点晕,接下来看下静态的。图来自百度百科:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-115ad26c55_b.jpg& data-rawwidth=&269& data-rawheight=&220& class=&content_image& width=&269&&&/figure&&p&&b&2.2 欧拉角与万向节&/b&&/p&&p&其实欧拉角的工作方式与万向节几乎一样。&/p&&p&看几幅动图就知道(图来自 &a href=&//link.zhihu.com/?target=http%3A//www.ogre3d.org/tikiwiki/tiki-index.php%3Fpage%3DEuler%2BAngle%2BClass& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Euler Angle Class&/a& )。&/p&&p&这个旋转叫pitch,中文是俯仰:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-c9097bcf4ad39e39c684a2fdae7ef7d8_b.jpg& data-rawwidth=&256& data-rawheight=&192& class=&content_image& width=&256&&&/figure&&p&这个旋转叫Yaw,中文叫偏航:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-61f08ff3a9b8e2749d31_b.jpg& data-rawwidth=&256& data-rawheight=&192& class=&content_image& width=&256&&&/figure&&p&这个旋转叫Roll,中文叫桶滚:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-bba8ed67cb1f4a6f495a157df0a5208d_b.jpg& data-rawwidth=&256& data-rawheight=&192& class=&content_image& width=&256&&&/figure&&p&可以看出,确实工作方式和万向节一样。&/p&&p&&b&2.3 死锁的产生&/b&&/p&&p&为了解释清楚问题,画了一个简单的万向节示意图(金属圆盘就省略了,丑点儿也就别管了):&/p&&figure&&img src=&https://pic3.zhimg.com/v2-78acd2fb18e37d77252f6_b.jpg& data-rawwidth=&326& data-rawheight=&325& class=&content_image& width=&326&&&/figure&&p&把三个Gimbal环用不同的颜色做了标记,底部三个轴向,RGB分别对应 &img src=&//www.zhihu.com/equation?tex=XYZ& alt=&XYZ& eeimg=&1&& 。&/p&&p&假设现在这个万向节被放在一艘船上,船头的方向沿着+Z轴,也就是右前方。&/p&&p&&b&2.3.1 桶滚&/b&&/p&&p&现在假设,船体发生了摇晃,是沿着前方进行旋转的摇晃,也就是桶滚。由于转子和旋转轴具有较大的惯性,只要没有直接施加扭矩,就会保持原有的姿态。由于上图中绿色的活动的连接头处是可以灵活转动的,此时将发生相对旋转,从而出现以下的情形:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-c59686dbabcd7c68df8178cf_b.jpg& data-rawwidth=&420& data-rawheight=&340& class=&content_image& width=&420&&&/figure&&p&&b&2.3.2 俯仰&/b&&/p&&p&再次假设,船体发生了pitch摇晃,也就是俯仰。同样,由于存在相应方向的可以相对旋转的连接头(红色连接头),转子和旋转轴将仍然保持平衡,如下图:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-5ecfced6a8a_b.jpg& data-rawwidth=&420& data-rawheight=&340& class=&content_image& width=&420&&&/figure&&p&&b&2.3.3 偏航&/b&&/p&&p&最后假设,船体发生了yaw摇晃,也就是偏航,此时船体在发生水平旋转。相对旋转发生在蓝色连接头。如下图:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-29ea182b7bb1f34662ca_b.jpg& data-rawwidth=&420& data-rawheight=&340& class=&content_image& width=&420&&&/figure&&p&最终,在船体发生Pitch、Yaw、Roll的情况下,万向节都可以通过自身的调节,而让转子和旋转轴保持平衡。&/p&&p&&b&2.3.4 死锁&/b&&/p&&p&现在看起来,这个万向节一切正常,在船体发生任意方向摇晃都可以通过自身调节来应对。然而,真的是这样吗?&/p&&p&假如,船体发生了剧烈的变化,此时船首仰起了90度(这是要翻船的节奏。。。。),此时的陀螺仪调节状态如下图:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-fcbc59ef7cd9c346a7ec2373dfca903e_b.jpg& data-rawwidth=&360& data-rawheight=&332& class=&content_image& width=&360&&&/figure&&p&此时,船体再次发生转动,沿着当前世界坐标的+Z轴(蓝色轴,应该正指向船底)进行转动,那么来看看发生了什么情况:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-cabe060d9bebb30d976c7035164bcc75_b.jpg& data-rawwidth=&420& data-rawheight=&340& class=&content_image& width=&420&&&/figure&&p&现在,转子不平衡了,万向节的三板斧不起作用了。它失去了自身的调节能力。那么这是为什么呢?&/p&&p&之前万向节之所以能通过自身调节,保持平衡,是因为存在可以相对旋转的连接头。在这种情况下,已经不存在可以相对旋转的连接头了。&/p&&p&那么连接头呢?去了哪里?显然,它还是在那里,只不过从上图中,我们清楚地看到:&/p&&ul&&li&红色连接头:可以给予一个相对俯仰的自由度。&/li&&li&绿色连接头:可以给予一个相对偏航的自由度。&/li&&li&蓝色连接头:可以给予一个相对偏航的自由度。&/li&&/ul&&p&没错,三个连接头,提供的自由度只对应了俯仰和偏航两个自由度,桶滚自由度丢失了。&/p&&p&我们可以回头去试试之前的 &a href=&//link.zhihu.com/?target=http%3A//www.matongxue.com/madocs/442.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&操作页面&/a& ,在下面这样子的情况下其实就是死锁了:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8b612cbd78cd8a0ffc5e85_b.jpg& data-rawwidth=&2300& data-rawheight=&804& class=&origin_image zh-lightbox-thumb& width=&2300& data-original=&https://pic2.zhimg.com/v2-8b612cbd78cd8a0ffc5e85_r.jpg&&&/figure&&p&&b&3 视频&/b&&/p&&p&还不懂?没有关系,这里还有做得非常好的视频,以供参考:&/p&&a class=&video-box& href=&//link.zhihu.com/?target=https%3A//v.qq.com/x/cover/ew/ew.html& target=&_blank& data-video-id=&611136& data-video-playable=&true& data-name=&欧拉角旋转以及万向节死锁_腾讯视频& data-poster=&https://pic3.zhimg.com/v2-346d49b10eee41a0c38de.jpg& data-lens-id=&&&
&img class=&thumbnail& src=&https://pic3.zhimg.com/v2-346d49b10eee41a0c38de.jpg&&&span class=&content&&
&span class=&title&&欧拉角旋转以及万向节死锁_腾讯视频&span class=&z-ico-extern-gray&&&/span&&span class=&z-ico-extern-blue&&&/span&&/span&
&span class=&url&&&span class=&z-ico-video&&&/span&https://v.qq.com/x/cover/ew/ew.html&/span&
&/a&&p&我对视频主要内容总结如下:&/p&&ul&&li&万向节死锁的根源在于欧拉角的定义方式&/li&&li&万向节死锁的结果,不是说不能旋转了,而是会导致旋转不自然&/li&&li&要规避万向节死锁,需要选择合适的旋转顺序(有12种旋转顺序)&/li&&/ul&&p&&b&4 总结&/b&&/p&&p&在编程中很难规避死锁问题,所以现在很多时候都使用四元数实现旋转,四元数那又是另外的话题了。&/p&
为何要引入四元数?首先是因为欧拉角有万向节死锁的问题。3D游戏或者3D电影中,比如黑客帝国中酷炫的旋转是怎么实现的?旋转的算法有很多,这里主要介绍其中一种:欧拉角。1 欧拉角1.1 欧拉角的算法思想是什么陌生的你来到了成都,站在盐市口茫然四顾,想知…
上篇实现了地形本身的切割,加载,以及Lightmap的处理。其实对于Unity来说,实现模型的动态加载更加困难。主要有两个难点,第一个是合批问题,动态加载,没法做合批处理。另一个则是lightmap处理,上一节地形的lightmap处理,相对来说很简单,因为地形是有规则的,我们直接把lightmap按比例切出来就能对应上,模型的lightmap就比较复杂了。最烦的是,这两个难点还互相参插。&/p&
我们先来看第一个问题,合批。&/p&
Unity提供了Mesh.CombineMeshs的接口:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-ac8a8d11ef_b.png& data-rawwidth=&1324& data-rawheight=&483& class=&origin_image zh-lightbox-thumb& width=&1324& data-original=&https://pic4.zhimg.com/v2-ac8a8d11ef_r.jpg&&&/figure&&br&&p&
针对于我们的项目,主要是有两点,一是要把同一区块的模型材质相同的合并到一起这里是需要真合并的,即:combine参数填true,具体操作就是把同一区块的模型通过坐标收集起来,按材质排序,然后进行Combine,需要注意的是这里所说的材质一样,包括,lightmap也是一样的,后续会谈到这个问题。再就是尽量把Mesh合并成SubMesh,按照Unity的规则是定点数少于6W合并到一起,比较省效率。这两个合并操作都是通过上面的这个接口很容易就实现了。&/p&&p&
第二个问题是lightmap处理:&/p&&p&
在解决这个问题之前,我们先来看看,Mesh上的LightMap数据。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-7ceae7b1369db_b.png& data-rawwidth=&486& data-rawheight=&408& class=&origin_image zh-lightbox-thumb& width=&486& data-original=&https://pic4.zhimg.com/v2-7ceae7b1369db_r.jpg&&&/figure&&p&我们关心的主要是BakedLightmap下的数据,这个数据对应在Unity脚本中是,MeshRender的lightmapIndex,lightmapScaleOffset。LightMapIndex表示这个Mesh引用的是第几张LightMap图,可以LightmapSettings.lightmaps[index]来找到这张图,Tile和Offset就是Unity材质的Tile和Offset了。通过上面这几个数据,其实就能知道指定的某个模型,对应到的lihgtmap是哪一块了。&/p&&p&
我们知道,lightmap烘焙完后,在那几张图上是完全无规则的,你根本不知道我这些都渲染到哪了,都有可能同一区块的几个物体,可能分布在好几张lightmap上,我们应该尽可能地调整,使得他们放到一张上。&/p&&p&
我们依然先获取区块上的所有模型,因为前面提到的,材质一样才能合并Mesh,所以要进行材质排序,如果有SubMesh的,这里要进行分开处理,即:三个SubMesh,当成三个模型进行处理。&/p&&p&
接下来就要开始拆分lightmap了,依然通过OpenExr,上面我们已经知道每个模型对应到哪张图片的哪一块了,我们只需要一个一个从源lightmap上抠出来,填到一个新的Lightmap。这里涉及到填充算法问题,可以参考NGUI生成图集的算法。拆完后,需要转换UV2用来对应上拆好了的lightmap。这里需要注意的是,为了精度问题,尽量要保证UV2的范围在0-1,然后算出新的lightmapScaleOffset。这一步说起来很简单,但是操作起来很繁琐,再还要结合上面的合并Mesh,写代码的时候一定要保证头脑很清晰。&/p&&p&
至此,模型加载基本思路完成。其实思路好像很简单,三五行就说完了,里边的坑很多,我写模型分块的代码花的时间大概是地形分块的两三倍。下面我说一下我遇到的一些坑吧。&/p&&p&
CombineMesh的时候,构建CombineInstanc的时候需要传入Mesh,千万不要直接用meshFilter.sharedMesh,因为我们需要把UV2设置进去,再进行Combine操作,不能用以前的Mesh,我当时想着,那我改完UV2,Combine完,然后再把UV2改回来不就完事了么,千万不要这么想当然,因为在Unity中一个Mesh只生成一个,在不同物体上是用引用连接的,而不同物体上你可能需要不同的UV2,你修改了一个,那么另外一个也会修改。所以可以用Object.Instantiate复制出一个Mesh,然后进行操作。&/p&&p&
CombineMesh完,一定要调用MeshUtility.Optimize进行Mesh优化,如果不进行这个,顶点数会非常多,假设,如果有两个Mesh都是三个SubMesh,用的同一个材质,我们把他们合并,那么得到的定点数将是合起来的三倍,他是把模型上所有的顶点都放到一起,不管你用到没用到,MeshUtility.Optimize能清除没用到的顶点,此外,再调用一下mesh.RecalculateNormals和mesh.RecalculateBounds。再就是前面说的合并不能超过6W顶点,Unity是直接限制了,如果超过了,会报错。&/p&&p&
LihgtMap按照上面说的步骤操作完,最后进游戏一看,会有好多黑边。因为在转换LightMap和UV2的过程中,LightMap本身又是整型,会存在误差,可以通过做一次高斯模糊改观,不知道还有没其他好办法。反正lightmap做高斯模糊也不太能看出来,就是有阴影的地方会出现软阴影,其他的缺点暂时没发现。&/p&&p&
还是希望Unity能源生支持这个,我这种做法,一个是代码复杂度太高,一个是效率可能存在一些问题,还可能会出现一些未知问题,因为我这套方案是没有项目验证可行的,我只拿了Demo的几个场景测试了一下。&/p&
上篇实现了地形本身的切割,加载,以及Lightmap的处理。其实对于Unity来说,实现模型的动态加载更加困难。主要有两个难点,第一个是合批问题,动态加载,没法做合批处理。另一个则是lightmap处理,上一节地形的lightmap处理,相对来说很简单,因为地形是有…
&h2&0. 例行的啰嗦&/h2&&p&上篇关于技术的文章已经是10月底的事情了,上个月写了半篇关于创业的文章,写到后来觉得无趣,就停下了——谁会愿意看一个无名小卒的无聊感慨呢?又是临近月底了,其实可写的内容不少,只是时间不多,就选最近花了大约半个星期时间搞的这个优化来聊聊Fast Shadow Receiver这个插件的使用吧。&/p&&h2&1. 起因&/h2&&p&关于Unity中的动态阴影,已经有挺多帖子聊过这个话题了,比如这篇&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttps%253A%252F%252Fblog.uwa4d.com%252Farchives%252Fsparkle_shadow.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Unity移动端动态阴影总结》&/a&,还有&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttp%253A%252F%252Fqiankanglai.me%252F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&钱康来&/a&博客里的这篇&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttp%253A%252F%252Fqiankanglai.me%252F%252F14%252Funity-projector%252F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《利用Projector实现动态阴影》&/a&和&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttp%253A%252F%252Fqiankanglai.me%252F%252F23%252Fplanar-shadow%252F& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Planar Shadow》&/a&等等。&br&无论是最简单的基于Planar投影的方案还是稍微“老式”一些的Projector的方案,乃至目前比较主流的ShadowMap的方案其实都各有优劣和对应的应用场景,它们之间的原理和差异不是本文的重点,有兴趣的同学也可以很容易地找到相关的论文或者博客来看。&/p&&p&我们项目本着不要重复造轮子的想法,一直坚持使用Unity原生的ShadowMap的方案来做动态阴影。而且UWA也做过一些动态阴影方案效率的对比,自己的轮子能做得比有源码的官方好的并不多,更何况我们这种地表有起伏,高配需要支持多角色动态阴影的“大型”MMORPG游戏,ShadowMap已经是最适合的方案了。&/p&&p&然而!人生总会有然而,否则就太平淡无味了不是?……&/p&&p&大约1个多月前,我发现了这个问题——&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttps%253A%252F%252Fanswer.uwa4d.com%252Fquestion%252F5acfbcc70fd5ecc%252FUnity%5B8%2525AD%59D%5E6%581%590%5E6%5B9%5B8%25258EShadowmap%59A%5E5%2525AE%2E8%2525AE%2525BE%5BD%2525AE%586%5E7%2525AA%5E9%5AE%5A2%252598& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&《Unity中静态合批与Shadowmap的宏设置冲突问题》&/a&,简单来说,静态合批首先对场景物体进行了排序,保证结果正确,但是当引入了动态阴影之后,会去修改物体接受阴影的宏(这也是一种优化,因为有采样和阴影计算的消耗,所以关闭掉宏着色器的效率更高),导致原本排序好的物体无法正常进行合批,因为着色器的宏不一样了,从而导致之前静态合批之后理论上可以做到很低的Batch数值增加了很多,使得场景渲染的效率大幅下降。&/p&&p&这个问题在想清楚原因之后,在依然想要使用Unity的ShadowMap的前提下感觉是没有什么特别简单的优化方案的,于是就暂时搁置下来,直到上周的时候对游戏各个效果对于帧率的影响在真机上做了一个定量的测试之后,才发现问题远比想象中的严重……&/p&&figure&&img src=&https://pic1.zhimg.com/v2-b08b92be5fc4e53fdbdadcb63cbb9313_b.jpg& data-size=&normal& data-rawwidth=&881& data-rawheight=&332& class=&origin_image zh-lightbox-thumb& width=&881& data-original=&https://pic1.zhimg.com/v2-b08b92be5fc4e53fdbdadcb63cbb9313_r.jpg&&&figcaption&各个效果对于帧率影响的定量测试结果&/figcaption&&/figure&&p&上面的测试是在中配机型小米Max2上进行的,可以看出阴影的开关与否导致一帧的时间消耗有9.5ms左右的差异,是所有效果中影响最大的!而ShadowMap自身渲染消耗不应该有这么大的差异才对,观察了下Batch数量的差异,单纯场景的Batch数量大约会从25增加到150左右,这有点超出我们之前制定的美术规范了。&/p&&p&在中配效果下,我们只有主角自己开启了动态阴影,因此最初的一个想法就是引入另外一套阴影绘制方案,比如&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttps%253A%252F%252Fwww.assetstore.unity3d.com%252Fcn%252F%2Fcontent%252F35558& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dynamic Shadow Projector&/a&,来专门针对主角进行阴影的绘制。虽然我个人很不喜欢同时使用两套技术方案,但目前看起来这似乎是在不降低效果的前提下唯一的选择了。&/p&&h2&2. Dynamic Shadow Projector插件&/h2&&blockquote&This simple Unity asset provides a few components to render a shadow onto a render texture so that the render texture can be used with Blob Shadow Projector. Blob Shadow Projector is usually used for dropping a round blurry shadow which is not suitable for a skinned mesh object. This asset enables a projector to drop a dynamic shadow which is perfect for skinned mesh objects.&/blockquote&&p&Dynamic Shadow Projector插件的原理比较简单,将角色的阴影绘制到一张rt上,然后使用Unity的Projector组件将这张rt作为绘制输入,再绘制一遍接受阴影的物体。阴影的rt是每帧更新的,也就做到了可以让带有动画的角色阴影是实时变化的。&/p&&p&试用了一下,还是比较简单易上手的,几个组件正确设置之后就可以看到效果了,由于是针对单个角色的,因此使用比较小的rt就可以做到比shadowmap更加精细的效果,但是如果想让一个projector处理多个角色,一旦扩大projector的范围,阴影效果质量的下降就比shadowmap的方法还要厉害。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-19a63af2c7542dbf118fe2ec_b.jpg& data-size=&normal& data-rawwidth=&639& data-rawheight=&425& class=&origin_image zh-lightbox-thumb& width=&639& data-original=&https://pic4.zhimg.com/v2-19a63af2c7542dbf118fe2ec_r.jpg&&&figcaption&128*128的rt只投影一个Cube的情况下rt的使用率和阴影质量&/figcaption&&/figure&&figure&&img src=&https://pic1.zhimg.com/v2-65eefc474915bea8211c66_b.jpg& data-size=&normal& data-rawwidth=&656& data-rawheight=&443& class=&origin_image zh-lightbox-thumb& width=&656& data-original=&https://pic1.zhimg.com/v2-65eefc474915bea8211c66_r.jpg&&&figcaption&512*512的rt投影三个Cube的情况下rt的使用率和阴影质量&/figcaption&&/figure&&p&上面两张图分别给出了模拟使用一个Projector针对单个角色进行投影和多个角色进行投影的效果对比图,在下面的那张图中,三个Cube的距离相隔并不远,但是即使使用了512*512的rt,明显可以看到其阴影已经有了锯齿感,距离更大的时候锯齿更加严重。&br&那我为什么纠结于一定想要使用一个Projector来进行多个角色的动态阴影绘制呢?因为对于每一个Projector来说,绘制阴影的时候都需要把接受阴影的模型完整重回一遍,从下面抓帧的截图可以看出,三个Cube分别使用三个不同的Projector,地表平面需要绘制三遍。这其实就是Projector的方法不太适合移动设备上多个物体都需要进行动态阴影绘制的原因。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-ceacbd0da1be5e5a7014894_b.jpg& data-size=&normal& data-rawwidth=&1061& data-rawheight=&889& class=&origin_image zh-lightbox-thumb& width=&1061& data-original=&https://pic1.zhimg.com/v2-ceacbd0da1be5e5a7014894_r.jpg&&&figcaption&多个Projector的时候接收阴影的地表绘制抓帧截图&/figcaption&&/figure&&p&我们的地表使用了Terrain制作,转为Mesh之后的三角形数量一般在大几千的水平,多遍绘制对于整体面数的增加还是很可观的,虽然在我们的中配下只有主角接受动态阴影,只需要多一遍地表模型的绘制,拿一次Draw Call和几千面的消耗换取100+次Batch的减少,理论上已经够划算了,但是我还有些不太甘心,于是想尝试下Dynamic Shadow Projector推荐配合“服用”的Fast Shadow Receiver插件。&/p&&h2&3. Fast Shadow Receiver的试用&/h2&&p&&a href=&https://link.zhihu.com/?target=https%3A//link.jianshu.com/%3Ft%3Dhttps%253A%252F%252Fwww.assetstore.unity3d.com%252Fen%252F%2Fcontent%252F20094& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Fast Shadow Receiver&/a&插件是很久前我就关注过的一个插件,钱康来在他的博客里也有提到。我一直保持一个敬而远之的心态,一是因为从经验上来说ShadowMap没有接受阴影方需要重绘的问题,只是宏的改变,效率应该挺高的(没想到影响了Static Batching);二是对于运行时对mesh进行暴力重建一直心存怀疑,担心其对于CPU和内存的额外压力。&/p&&p&购买了插件,将其引入我自己本地的项目工程,玩了玩Demo之后,尝试将其和Dynamic Shadow Projector结合一起使用。和AssetStore上对于这个插件的评论一样,这个插件的文档的确有些晦涩,大约玩了三四个小时的时间才正式在游戏中跑通整个流程,过程不详述了,几个小坑记录一下:&/p&&ol&&li&可能是官方被吐槽文档太难读,所以做了一套Wizard,一步步走教你怎么配置,然而我按照步骤做完之后并没有得到正确的结果,反而因为Wizard隐藏了背后的部分设置步骤导致我无法正确理解过程,从而难以排查原因。而且Wizard是针对特定的需求,未必是我自己想要的效果。最终我还是按照Demo工程里的组件逐个对照配置实现的效果。&/li&&li&LayerMask设定需要注意,为了优化效率,Projector组件上有Igore Layers的设定,在Draw Target Object上,也有Layer Mask的设定用于标识要绘制的节点下哪些Layer会被绘制,最终的ShadowReceiver组件也会属于某一个Layer,比如默认的Default。这几个Layer如果设定有问题,会导致最终没有影子被绘制出来。我因为这里的失误多花了1个小时的时间调试各种参数,如果你在使用中遇到了奇怪的问题,可以把自己设置的各种Layer梳理一遍,保证逻辑上的正确性。我当时的问题之一是把ShadowReceiver所在的GameObject归入到了Default Layer,而Projector又Igore掉了Default Layer,导致结果不正确。&/li&&li&Fast Shadow Receiver的插件制作者估计没有经受过中国美术的洗礼,除了文档晦涩之外,代码中对于容错的兼容考虑得也不周全……我们场景中有几千个物体,在最初测试的时候没有花心思标记所有的地表接受阴影的物体,索性将所有物体都进行标注,结果MeshTree的生成一直存在问题,查了下是因为我们场景中存在一个Mesh对象为miss状态的GameObject导致的,做一下兼容就好了,当然根本上也要美术去修复掉mesh miss的问题。&/li&&/ol&&p&总之,经过一系列的尝试,最终在我们自己的工程内使用正式的美术资源跑通了整个流程,也对于Fast Shadow Receiver的原理有了更深的理解:它使用Mesh Tree这样一个继承自Scriptable的类在离线阶段来预计算并存储需要接受阴影的地表网格信息,并且提供BinaryMeshTree、OctMeshTree和TerrainMeshTree三种类型来应对不同的场景。运行时,它提供MeshShadowReceiver这样的组件,根据Projector的设定实时计算出来接受阴影的地方需要覆盖的那些面片,生成一个新的网格作为阴影接收者的网格对象进行渲染,从而做到可以将原本几千面的模型只需要几十个面就可以绘制出来,因为毕竟需要绘制动态阴影的只有镜头前的部分区域。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-1c5a6bd9de78b40d2bbd1_b.jpg& data-size=&normal& data-rawwidth=&711& data-rawheight=&418& class=&origin_image zh-lightbox-thumb& width=&711& data-original=&https://pic3.zhimg.com/v2-1c5a6bd9de78b40d2bbd1_r.jpg&&&figcaption&Fast Shadow Receiver的Demo中的示例截图&/figcaption&&/figure&&h2&4. 和ShadowMap的结合以及集成&/h2&&p&在最初的设想中是针对单独的主角使用Projector方式的动态阴影,然后用Fast Shadow Receiver进行优化,在Demo中看到Fast Shadow Receiver支持ShadowMap的方案时也没有多想。后来在和同事讨论这个问题的时候聊到Projector的动态阴影方案和ShadowMap的动态阴影方案的优劣,被问到两种方案是不是有可能做一个结合,然后想起了在Demo中看到了使用Fast Shadow Receiver来优化ShadowMap的例子。正好也在纠结我们抽离式的战斗中在中等配置下的效果,如果使用Projector,需要多几张rt的绘制是否合算,那如果可以用Fast Shadow Receiver结合之前的Shadow Map方案,对于目前结构的改动是最小的,也不必引入第二套动态阴影的产生方案,只相当于用新的插件在中配下解决场景静态合批的问题,这似乎是非常理想的一个方案。&/p&&p&沿着这个思路,学习了一下Fast Shadow Receiver中关于ShadowMap的例子,看上去也非常简单。在理解了原理的情况下,只是让场景内的其他Render组件的Receive Shadow属性都更改为false,然后只让Fast Shadow Receiver生成的那样一个面片读取生成的ShadowMap进行阴影的绘制即可,这样额外增加1个Draw Call和几十个面的渲染消耗,就可以做到和之前相似的效果,中高配置的切换逻辑也更加简洁。&/p&&p&我们先来看一下最后经过修改敲定下来的制作步骤,然后再聊一些其中的设计细节。&/p&&p&&b&1. 统一将场景中的Mesh相关的组件放置到同一个GameObject下。&/b&这一条原本没有一条硬性的规定,完全看场编同学自觉,其实整理之后Unity中的Hierarchy面板也会更加干净整洁;&/p&&figure&&img src=&https://pic2.zhimg.com/v2-8f3c28f00d2a7cea84247b5def3be420_b.jpg& data-size=&normal& data-rawwidth=&409& data-rawheight=&187& class=&content_image& width=&409&&&figcaption&场景Mesh统一放入ArtRoot根节点下&/figcaption&&/figure&&p&&b&2. 标记接受阴影的物体。&/b&这一步是一个有点琐碎的工作,需要美术标记出来哪些物体是接收阴影的,BinaryMeshTree是根据这些标记出来的物体来进行网格的预处理的。标记的物体过少会出现应当接受阴影的物体没有阴影效果,而过多会导致BinaryMeshTree的数据内容过多,加载变慢、检索速度降低,内存占用也会很多。由于我们目前只在中配下使用,所以对于这部分只要求地表和表现明显的物体加入到标记中。Fast Shadow Receiver只支持Layer和RenderType的过滤方式,在我们场景中有些物体已经被标记过了其他有逻辑意义的Layer,因此我针对这点进行了改造,增加了Tag的过滤,和Mask Layer&b&取或&/b&的方式来进行处理,并且为美术提供了方便的快捷键进行快速标注。我自己测试,我们游戏内的场景,标注加上验证需要的耗时大约也就半个小时到2个小时不等。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a51f6d46a016f8fcf9ab00ab_b.jpg& data-size=&normal& data-rawwidth=&560& data-rawheight=&510& class=&origin_image zh-lightbox-thumb& width=&560& data-original=&https://pic1.zhimg.com/v2-a51f6d46a016f8fcf9ab00ab_r.jpg&&&figcaption&提供FastReceiver的Tag进行标注&/figcaption&&/figure&&p&&b&3.创建BinaryMeshTree。&/b&我们最终选择使用BinaryMeshTree这种结构,它和OctMeshTree的区别见下图。其实这个步骤还需要更多的测试来做对比,因为官方也明说small和large的界限具体是什么。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-e0e2b94c9c8006894fee3a8a9257439e_b.jpg& data-size=&normal& data-rawwidth=&1041& data-rawheight=&367& class=&origin_image zh-lightbox-thumb& width=&1041& data-original=&https://pic1.zhimg.com/v2-e0e2b94c9c8006894fee3a8a9257439e_r.jpg&&&figcaption&两种不同的MeshTree对比&/figcaption&&/figure&&p&创建BinaryMeshTree的过程也很简单,插件提供了右键Create菜单的支持:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-0b97fa9ae9d1_b.jpg& data-size=&normal& data-rawwidth=&596& data-rawheight=&771& class=&origin_image zh-lightbox-thumb& width=&596& data-original=&https://pic4.zhimg.com/v2-0b97fa9ae9d1_r.jpg&&&figcaption&创建BinaryMeshTree&/figcaption&&/figure&&p&&b&4. 生成Mesh Tree。&/b&在标注完接收阴影的物体之后,就可以选中创建好的BinaryMeshTree,填写其Root Object为场景的根节点,设置好Layer进行build。我们建议美术检查最后创建完毕之后给出的build信息中对于内存的占用要小于2M,这是一个编辑几个场景之后的经验值而已,还需要更多验证。&/p&&figure&&img src=&https://pic3.zhimg.com/v2-dbb04f19b08e_b.jpg& data-size=&normal& data-rawwidth=&279& data-rawheight=&577& class=&content_image& width=&279&&&figcaption&Mesh Tree生成时Layer的配置&/figcaption&&/figure&&figure&&img src=&https://pic1.zhimg.com/v2-cb1f628b852d6f56feaa156_b.jpg& data-size=&normal& data-rawwidth=&278& data-rawheight=&83& class=&content_image& width=&278&&&figcaption&Build之后的Mesh Tree信息统计&/figcaption&&/figure&&p&&b&5. 配置Projector和Mesh Tree信息。&/b&这部分为了简化美术的配置工作,大部分的配置逻辑都写在了代码中,只需要美术复制一份prefab出来,将新创建的Mesh Tree信息设置正确即可。需要注意这份prefab是不保留在场景内的,编辑完毕Apply后会从场景中删除掉。&/p&&figure&&img src=&https://pic4.zhimg.com/v2-2a217f2fa2b7bf6f95c9c262ae91965d_b.jpg& data-size=&normal& data-rawwidth=&665& data-rawheight=&704& class=&origin_image zh-lightbox-thumb& width=&665& data-original=&https://pic4.zhimg.com/v2-2a217f2fa2b7bf6f95c9c262ae91965d_r.jpg&&&figcaption&创建BinaryMeshTree&/figcaption&&/figure&&p&这里一共只使用了两个组件,一个是图中LightProjector对象上的LightProjector组件,用于设置阴影使用的方向光对象以及一些Projector的参数,比如跟随的Target对象,扩展的Bound范围等;另外一个是MeshShadowReceiver组件,关联Mesh Tree数据,场景渲染物体的根节点和Projecter对象,一些Fast Shadow Receiver的裁剪、更新方式等属性也可以在这里进行设置。&/p&&p&&b&6. 在资源根节点上添加Shadow Receiver Controller组件,并进行配置。&/b&这一组件是我们自己实现的,用于控制Fast Shadow Receiver的开关,它会根据游戏配置在场景加载、游戏配置切换等逻辑中对Fast Shadow Receiver进行设置。并且基于这一组件实现对于Mesh Tree的懒加载功能。&/p&&figure&&img src=&https://pic1.zhimg.com/v2-fdf3aae94fe2025b8aae7_b.jpg& data-size=&normal& data-rawwidth=&689& data-rawheight=&335& class=&origin_image zh-lightbox-thumb& width=&689& data-original=&https://pic1.zhimg.com/v2-fdf3aae94fe2025b8aae7_r.jpg&&&figcaption&Shadow Receiver Controller组件配置&/figcaption&&/figure&&p&&b&7. 在游戏运行状态下进行测试。&/b&上述配置完毕之后,就可以在游戏逻辑的中等配置下看到优化后的阴影效果了,可以跑跑游戏进行测试。&/p&&p&大部分细节已经在上述步骤中描述了,这里再说明以下几个地方:&/p&&p&a) &b&Projector和MeshShadowReceiver组件是不默认放在场景里的。&/b&这是由于当地表物体较多的时候,Mesh Tree的加载是有时间消耗的(遇到过一个测试例子,Mesh Tree的大小有18M左右,在PC上需要5s以上的情况,具体原因没有细查),也会有额外的内存消耗,因此这里一方面建议美术确保这个文件不会特别大,另一方面通过Lazy Load的方式,在需要的时候才加载,来保证在高配和低配的情况下,不需要任何额外的CPU和内存开销。&br&b) &b&为美术提供更多便利的工具来标记信息。&/b&由于标记地表是一个相对琐碎的工作,验证标记是否合理也是一个件需要花费很多时间和精力的事情,除了前面提到的快捷键可以一键标注,还推荐通过Layer的显隐功能,以及我们自己开发的Tag显隐功能进行快速检查和问题定位。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-4cd26d78bcb7_b.jpg& data-size=&normal& data-rawwidth=&448& data-rawheight=&432& class=&origin_image zh-lightbox-thumb& width=&448& data-original=&https://pic2.zhimg.com/v2-4cd26d78bcb7_r.jpg&&&figcaption&Unity原生的Layer过滤功能&/figcaption&&/figure&&h2&5. 优化结果和代价&/h2&&p&使用同样的测试方式,对比优化前后的游戏运行帧率和时间消耗:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-a0ad10bbb844e49b62dcbe_b.jpg& data-size=&normal& data-rawwidth=&879& data-rawheight=&172& class=&origin_image zh-lightbox-thumb& width=&879& data-original=&https://pic1.zhimg.com/v2-a0ad10bbb844e49b62dcbe_r.jpg&&&figcaption&优化前后的性能消耗对比&/figcaption&&/figure&&p&可以看到,使用Fast Shadow Receiver在小米 Max2上有大约7.2ms的性能提升,帧率从26上升到33,这其中有Batch数量降低的功劳,应该也有场景物体不需要采样ShadowMap贴图带来的渲染性能提升,更加具体的数据就没有去测试了。剩余的1.5ms的时间消耗包括了ShadowMap的绘制以及Fast Shadow Receiver的更新消耗,这是后续的优化对象,但这次优化已经有很大的提升了,中配下整体效率提升了20%,已经是难得的&b&“神级优化”&/b&了。当然,这建立在场景通过关闭Shadow接收的宏能够降低较大Batch数量的前提下。&/p&&p&这次优化的收益是很大的,但它也不全是一种无损优化,需要付出的代价有这么几点:&/p&&ol&&li&&b&美术工作量。&/b&需要美术同学针对场景进行地表接收阴影物体的标注,虽然提供了快捷的工具,但是依然需要花费一些时间成本。&/li&&li&&b&部分物体不再会受到动态阴影的影响。&/b&在之前基于ShadowMap的方案中,几乎所有的物体都可以标记为接收阴影,而且可以保证效果的正确性,但是目前这种方案如果要做到这点会导致Mesh Tree对于内存的占用较多,对于外部的大世界场景也不适应,因此会有出现一些小石头等物体不会接收角色阴影的问题,这是一些效果的降低,但目前看是可以接受的范围内。&/li&&li&&b&和静态阴影的融合与ShadowMap的方案不同。&/b&ShadowMap的方案是在场景绘制的时候进行处理的,一次像素着色的过程中会采样lightmap和shadowmap两张贴图,这就可以判断出该像素点是否在静态阴影之中,这样可以做到比如在屋檐下或者树荫下这样的静态阴影中,角色的实时阴影可以和静态阴影做一个较好的融合,如下图所示。&br&&/li&&/ol&&figure&&img src=&https://pic2.zhimg.com/v2-ccb910f23_b.jpg& data-size=&normal& data-rawwidth=&1456& data-rawheight=&901& class=&origin_image zh-lightbox-thumb& width=&1456& data-original=&https://pic2.zhimg.com/v2-ccb910f23_r.jpg&&&figcaption&基于ShadowMap的方案动态阴影和静态阴影的融合效果&/figcaption&&/figure&&p&而使用Fast Shadow Receiver方案之后,就比较难做融合的效果,除非在新生成的mesh中保存之前mesh的uv2信息以及使用的lightmap贴图信息,再做一次lightmap的采样。但这比较麻烦,性价比也不高,于是在静态隐形中的角色动态阴影的效果就变成了如下图所示的样子。&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-daa6c703b31af3fabb8165_b.jpg& data-size=&normal& data-rawwidth=&1438& data-rawheight=&898& class=&origin_image zh-lightbox-thumb& width=&1438& data-original=&https://pic4.zhimg.com/v2-daa6c703b31af3fabb8165_r.jpg&&&figcaption&使用Fast Shadow Receiver方案的效果&/figcaption&&/figure&&p&除了这些之外的代价就是程序这边花费了大约半个多星期的时间来学习和集成这套方案,但是从优化结果上看,还是收获很大,非常值得的~&/p&&h2&6. 一个Projector同时处理多个角色的动态阴影&/h2&&p&由于我们是类似回合制的抽离式战斗方式,即玩家进入战斗后整场战斗都会发生在一小块固定区域内,这里其实对于ShadowMap结合Fast Shadow Receiver的方案是一个非常合适的应用场景——只需要在进入战斗前生成一次阴影接收的面片,&b&整场战斗中都不需要对其进行修改和变动&/b&!&br&我们将LightProjector的Target锁定为战斗的中心区域点,然后通过修改Bound的方式扩大其投射范围到整个战场。前面已经讨论过基于Projector的动态阴影方案的一个问题是当projector较大的时候rt的使用率较低,导致阴影质量骤降的问题,但因为我们使用的是ShadowMap的阴影方案,因此扩大Projector的范围并不会影响阴影精度,也不需要处理多个Projector带来rt数量、draw call增加等问题。&/p&&figure&&img src=&https://pic2.zhimg.com/v2-b670be265c12fde4ac3d94_b.jpg& data-size=&normal& data-rawwidth=&897& data-rawheight=&677& class=&origin_image zh-lightbox-thumb& width=&897& data-original=&https://pic2.zhimg.com/v2-b670be265c12fde4ac3d94_r.jpg&&&figcaption&战场中多个角色共用一个Light Projector的方案&/figcaption&&/figure&&h2&7. 总结和展望&/h2&&p&Fast Shadow Receiver这种通过CPU的实时计算来换取GPU的渲染性能的方案,正好解决了我们场景静态合批被动态阴影打断的问题,大大提升了我们游戏在中配下的帧率,是近期所做的优化中效果最为显著的一个了,因此也记录一下详细的过程在这里分享出来。&br&对于这个插件的感觉,在这一周的逐渐熟悉、应用、修改的过程中,也从心存怀疑到由衷赞叹。目前针对这个插件的魔改还不多,除了前面提到的增加Tag的支持、建立Mesh Tree的时候缺少一些对于资源的错误兼容之外,只修改了部分Component的默认参数,更加适合我们项目的设定,让美术和程序可以更加方便地使用。它在运行时对于内存的分配和CPU的性能消耗也让我们满意,因此在这里也帮这个插件做一下广告——别被它的文档和使用过程吓到,用好之后,你的游戏效率可以获得很大的提升~&/p&&p&至于未来,当中配下的效果和效率都被验证可以接受之后,可能考虑优化一些它的效果,将它也应用到高配下,当然,对于贴花等需要处理高低不平地面效果的地方,也可以考虑使用这个插件进行效率的优化。&/p&&p&PS:从Fast Shadow Receiver的启发来思考场景静态合批被打断的问题,其实另外一个思路是自己来做哪些物体需要被接受阴影的判断。Unity内部肯定也是有这样的判定逻辑来设置各个场景Render的宏,由于Shadow的距离设定较大,Unity的判定范围也过广,导致了虽然我们在中配下只有角色渲染阴影,但是接收阴影的物体数量过多,从而导致Batch被频繁打断的问题。仿照Fast Shadow Receiver,使用一个跟随角色的投影,和场景物体相交来判断有哪些物体需要被设置为接收阴影,由于角色脚下的物体可能只会有几个,因此Batch的数量也只会增加几个。目前没有沿着这个思路来做的原因之一也是地表物体的面数实在是有点多,Fast Shadow Receiver对于面数的降低也是我们想要的优化之一。&/p&&p&日于杭州家中(圣诞快乐~ _)&/p&
0. 例行的啰嗦上篇关于技术的文章已经是10月底的事情了,上个月写了半篇关于创业的文章,写到后来觉得无趣,就停下了——谁会愿意看一个无名小卒的无聊感慨呢?又是临近月底了,其实可写的内容不少,只是时间不多,就选最近花了大约半个星期时间搞的这个优…
&p&转载请注明出处。&a href=&//link.zhihu.com/?target=http%3A//www.ixulin.com& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&ixulin.com&/span&&span class=&invisible&&&/span&&/a&&/p&&p&原文链接:
&a href=&//link.zhihu.com/?target=https%3A//ixulin.github.io//talk-bake-in-unity/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&谈光照图烘焙技巧 - 徐小厨 | 大道至简&/a&&/p&&p&最近给美术整理的,这是知乎上最类似的问题了,所以挂在这,一个是看是否有人有类似的问题可以参考,二来刷一下自己网站的外链,增加被爬虫抓到的几率。。。&/p&&p&知乎貌似不能像markdown或者引用html代码样外链图片,大家点上面的链接看吧。&/p&&p&-------------------------------------------------------------------------------------&/p&&p&第二次更。周末在科目二集训和考试的时候发现忽然被很多人点赞和关注,受宠若惊,感谢大家认可和支持。之前因为知乎不支持直接粘贴图片,又怕麻烦,只放了第一张图片,既然大家这么捧场,还是把配图都加上~~
~~ [捂脸][允悲]&/p&&p&另外更多文章请访问我的博客,还有一些文章的补充和更新,还是会以博客为主,知乎上的回答尽量过来修改~~&/p&&p&--------------------------------------------------------------------------------------&/p&&p&&b&LightMap烘焙技巧&/b&&/p&&blockquote&前言:弱弱的说我是一个程序猿。
科普名词解释:
全局照明(GI):是指除了直接光之外包括天空,物件之间间接光照的总和。
环境闭塞(AO):是指间接光在物件间相互遮挡反射不充分导致的微弱阴影。通常在直接光照的暗部才会比较明显。
天光:从头顶来的天空颜色造成的间接照明,特别是在晴朗的天气。
LightMap:光照贴图
HDR:颜色高动态区间,指颜色亮度可以超过1,移动平台暂且不支持
间接照明(间接反射):指光源照亮A物体,A物体变亮以后充当间接光源对近距离内B物体有照亮的作用。这种现象只有当物体B在直接光照(比如阳光)的阴影里才会明显。&/blockquote&&h2&&b&足够多的明暗对比&/b&&/h2&&ul&&li&&b&适当&/b&打平灯光以增加阴影面积(暗部更多更容易做效果) &/li&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-6ef9c62eaeeae71bbfd8d4_b.jpg& data-rawwidth=&894& data-rawheight=&669& class=&origin_image zh-lightbox-thumb& width=&894& data-original=&https://pic1.zhimg.com/v2-6ef9c62eaeeae71bbfd8d4_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&&b&适当&/b&增大模型增加阴影面积(暗部更多更容易做效果)&/li&&/ul&&figure&&img src=&https://pic2.zhimg.com/v2-a3f77893afb51b893ad7dd_b.jpg& data-rawwidth=&988& data-rawheight=&695& class=&origin_image zh-lightbox-thumb& width=&988& data-original=&https://pic2.zhimg.com/v2-a3f77893afb51b893ad7dd_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&通过&b&只投射不接收&/b&来增加假阴影(作假)(不接受投影是因为看不见所以可以节省这种物体的渲染和它占用的litmap面积)&/li&&/ul&&figure&&img src=&https://pic2.zhimg.com/v2-38f7cf3ac3b232e_b.jpg& data-rawwidth=&1036& data-rawheight=&768& class=&origin_image zh-lightbox-thumb& width=&1036& data-original=&https://pic2.zhimg.com/v2-38f7cf3ac3b232e_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&模拟全局光照&/b&&/h2&&ul&&li&足够&b&充分&/b&的&b&暗部&/b&照明(暗部会有一种明快的感觉,用美术语言说就是暗部很“通透”)&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-61d5db56acdd4e4f6bcce2_b.jpg& data-rawwidth=&1168& data-rawheight=&784& class=&origin_image zh-lightbox-thumb& width=&1168& data-original=&https://pic3.zhimg.com/v2-61d5db56acdd4e4f6bcce2_r.jpg&&&/figure&&p&&br&&/p&&figure&&img src=&https://pic4.zhimg.com/v2-05ac093d9b2dfa45a8738027_b.jpg& data-rawwidth=&1166& data-rawheight=&793& class=&origin_image zh-lightbox-thumb& width=&1166& data-original=&https://pic4.zhimg.com/v2-05ac093d9b2dfa45a8738027_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&天光的使用:&/li&&ul&&li&理论上越晴朗的天气阴影暗部越偏蓝(同理也是为了有全局照明的效果,用美术语言说就是暗部很“通透”)(哪天北京晴空万里的时候去大楼的阴影里看看是不是有蓝色,为了对比饱和度的覆盖问题,可以拿一张白纸)&/li&&/ul&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-2f3b24a55c7c8c62d036c075b69a315c_b.jpg& data-rawwidth=&940& data-rawheight=&637& class=&origin_image zh-lightbox-thumb& width=&940& data-original=&https://pic1.zhimg.com/v2-2f3b24a55c7c8c62d036c075b69a315c_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&对&b&间接反射&/b&的&b&夸张&/b&:&/li&&ul&&li&比如守望先锋花村的樱花树下会有很强烈的蓝紫色,实际上是模拟粉红的樱花与天光蓝色的叠加造成的视觉,不过暴雪的美术有更加夸张的表达。(实现方法实际是在暗部增加与区域环境颜色相似的点光,实时GI现在效率跟不上,另外也不可能这么强。)&/li&&li&小幅&b&夸张&/b&:&/li&&/ul&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-f570e8d447bb1ff09eb6f_b.jpg& data-rawwidth=&1078& data-rawheight=&741& class=&origin_image zh-lightbox-thumb& width=&1078& data-original=&https://pic4.zhimg.com/v2-f570e8d447bb1ff09eb6f_r.jpg&&&/figure&&p&&br&&/p&&ul&&ul&&li&&b&更夸张&/b&的做法:现实世界中不会有这么强烈的间接反射,就像叶子把光间接反射出来了一样,守望先锋就是例子&/li&&/ul&&/ul&&figure&&img src=&https://pic2.zhimg.com/v2-79cd0e59331cc46ebbb4e1eba6eb6fb1_b.jpg& data-rawwidth=&1166& data-rawheight=&712& class=&origin_image zh-lightbox-thumb& width=&1166& data-original=&https://pic2.zhimg.com/v2-79cd0e59331cc46ebbb4e1eba6eb6fb1_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&其实以上技巧都是借鉴自守望先锋&/li&&ul&&li&这是一个人的分析,只要&b&有心去找,仔细去看,去思考&/b&,网上有很多文章有可以借鉴的地方(因为实时GI还开销太大,暴雪真是能通过技巧把能用的东西用到最好)
&a href=&https://zhuanlan.zhihu.com/p/& class=&internal&&原文链接&/a&&/li&&/ul&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-96281dac58f267dd097ffd657f75e576_b.jpg& data-rawwidth=&856& data-rawheight=&492& class=&origin_image zh-lightbox-thumb& width=&856& data-original=&https://pic3.zhimg.com/v2-96281dac58f267dd097ffd657f75e576_r.jpg&&&/figure&&p&&br&&/p&&ul&&ul&&li&还有一个对&b&环境光源&/b&的理解
&a href=&https://www.zhihu.com/question//answer/& class=&internal&&原文链接&/a&&/li&&/ul&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-b3c32aad4ed3dd6cd658a7_b.png& data-rawwidth=&682& data-rawheight=&897& class=&origin_image zh-lightbox-thumb& width=&682& data-original=&https://pic4.zhimg.com/v2-b3c32aad4ed3dd6cd658a7_r.png&&&/figure&&p&&br&&/p&&ul&&li&祭上守望先锋的场景,很夸张的暗部间接光照,实际上是通过&b&暗部打烘焙点光&/b&来实现的。更多细节请&a href=&//link.zhihu.com/?target=https%3A//ixulin.github.io//talk-scene-design& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&参考我的另外一篇文章&/a&。&/li&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-702eb17eb0e0c1ea5f0e5db0e20cccd0_b.jpg& data-rawwidth=&1474& data-rawheight=&848& class=&origin_image zh-lightbox-thumb& width=&1474& data-original=&https://pic1.zhimg.com/v2-702eb17eb0e0c1ea5f0e5db0e20cccd0_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&AO的使用&/b&&/h2&&ul&&li&AO能更真实的模拟现实世界,能使暗部有&b&更明显的立体感&/b&,用美术的话说就是暗部足够“实”,“不飘”&/li&&ul&&li&基础知识请参考&a href=&//link.zhihu.com/?target=https%3A//sassybot.com/blog/lightmapping-in-unity-5/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&这篇文章&/a&,这里讲了什么是AO,什么是间接反射等&/li&&li&暗部的&b&接缝处&/b&AO效果最明显&/li&&/ul&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-49b79ea3d749a02a4ca546d532f80036_b.png& data-rawwidth=&1150& data-rawheight=&693& class=&origin_image zh-lightbox-thumb& width=&1150& data-original=&https://pic3.zhimg.com/v2-49b79ea3d749a02a4ca546d532f80036_r.png&&&/figure&&p&&br&&/p&&h2&&b&LightMap面积的优化&/b&&/h2&&ul&&li&LightMap面积(精度)说明:(&b&一个格子就是一个像素点&/b&,也就是占用在lightmap上面的面积)&/li&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-af348f2d6d68e9c7ceec62_b.jpg& data-rawwidth=&1178& data-rawheight=&819& class=&origin_image zh-lightbox-thumb& width=&1178& data-original=&https://pic3.zhimg.com/v2-af348f2d6d68e9c7ceec62_r.jpg&&&/figure&&ul&&ul&&li&通过&b&Scale In LightMap&/b&这个参数可以单独调整单个物体的精度&/li&&/ul&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-ed2d78e88dd20ce546157f_b.jpg& data-rawwidth=&617& data-rawheight=&618& class=&origin_image zh-lightbox-thumb& width=&617& data-original=&https://pic4.zhimg.com/v2-ed2d78e88dd20ce546157f_r.jpg&&&/figure&&p&&br&&/p&&ul&&li&更合理的分配LightMap面积(想要好效果又要节省lightmap面积,就是&b&要研究,并总结经验,没有捷径&/b&)&/li&&ul&&li&只投射不接受的物件Scale In Lightmap填0就行&/li&&li&地面 & 物件,重要物件 & 一般物件, 视觉中心 & 边远地区,大物件 & 小物件&/li&&/ul&&/ul&&figure&&img src=&https://pic2.zhimg.com/v2-aef8fc7d13c3ff0d305f249d_b.jpg& data-rawwidth=&1006& data-rawheight=&813& class=&origin_image zh-lightbox-thumb& width=&1006& data-original=&https://pic2.zhimg.com/v2-aef8fc7d13c3ff0d305f249d_r.jpg&&&/figure&&p&&br&&/p&&ul&&ul&&li&&b&全部&/b&(或者几乎全部)在明处或者&b&全部&/b&在暗处的可以用&b&最低分辨率&/b&烘焙(比如当前光线下的这个椅子和不管影子多么正都是在暗处的这面墙壁,这种情况下,这两个lightmap精度可以填0.01),需要注意的是必须参加烘焙只是精度可以很低(因为纯白或者纯黑被压缩大小是不会损失精度的)&/li&&/ul&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-8ccd576f46c07fab8df64a1efe4a08f8_b.jpg& data-rawwidth=&986& data-rawheight=&598& class=&origin_image zh-lightbox-thumb& width=&986& data-original=&https://pic1.zhimg.com/v2-8ccd576f46c07fab8df64a1efe4a08f8_r.jpg&&&/figure&&p&&br&&/p&&ul&&ul&&li&地面以下,地图的侧面,或者根本看不见的东西,烘焙占用面积请控制到最小(最惨的情况请参考下面说的浪费)&/li&&/ul&&li&避免完全没有必要的浪费&/li&&ul&&li&像这种&b&完全在别的物体以下&/b&,还参加烘焙的完全没有必要,而且浪费很大&/li&&/ul&&/ul&&figure&&img src=&https://pic2.zhimg.com/v2-e12c19ed3e70dfbd73de5_b.jpg& data-rawwidth=&1034& data-rawheight=&713& class=&origin_image zh-lightbox-thumb& width=&1034& data-original=&https://pic2.zhimg.com/v2-e12c19ed3e70dfbd73de5_r.jpg&&&/figure&&p&&br&&/p&&ul&&ul&&li&树叶花草等透贴类似的推荐&b&只投射,不接受&/b&,而且接受烘焙阴影效果不见得好,会很“脏”&/li&&/ul&&/ul&&figure&&img src=&https://pic3.zhimg.com/v2-1d84c1da3e352_b.jpg& data-rawwidth=&1048& data-rawheight=&713& class=&origin_image zh-lightbox-thumb& width=&1048& data-original=&https://pic3.zhimg.com/v2-1d84c1da3e352_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&技巧及问题&/b&&/h2&&ul&&li&发现阴影比较虚,不实&/li&&ul&&li&检查是否间接光照反弹次数过大(可以理解成有过大的衍射效果)&/li&&li&增加&b&被投影物体&/b&的大小(比如放大花盆)(很显然)&/li&&li&增加&b&投影至物体&/b&所占lightmap面积(比如树叶投影到地面比较虚,则提高地面的精度)&/li&&/ul&&li&效果不好或者黑点、漏光过多&/li&&ul&&li&检查2u是否打开&/li&&li&检查顶点分布是否有异常,uv分布是否均匀或过少&/li&&li&如果都没问题而且这个物件很重要,则增加精度&/li&&li&自定义2u(高级,不推荐使用)&/li&&/ul&&li&过小物体效果太差&/li&&ul&&li&如果确实过小,先看能不能放大,或者说这么小的东西有没有存在的必要或者是否可替代(很显然)&/li&&li&如果确实需要,可以不参加litmap烘焙,而直接去diffuse贴图上花盆贴图的底部蹭两笔黑的&/li&&li&这个花盆之前参加烘焙会有很大的漏光和黑点,其实不需要参加烘焙,而且效果比烘焙更好,还能节省lightmap&/li&&/ul&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-e5f068ad4ab810a3ce60_b.jpg& data-rawwidth=&602& data-rawheight=&542& class=&origin_image zh-lightbox-thumb& width=&602& data-original=&https://pic1.zhimg.com/v2-e5f068ad4ab810a3ce60_r.jpg&&&/figure&&p&&br&&/p&&h2&&b&压缩的效果优化&/b&&/h2&&ul&&li&通过我烘焙的经验,以kasaierxueyuannew场景为例,发现烘焙10-12张1024压缩到512,比直接烘焙2-3张1024的贴图来的精度和效果都更好&/li&&ul&&li&10张512 = 2.5张1024,而以之前的烘焙策略烘焙3-4张1024效果没有我这个好&/li&&li&此外通过合理分配lightmap精度,&b&能节省2-3张lightmap&/b&,没有仔细分配之前,保证效果的情况下能烘焙14张左右,现在这版只使用了11张,如果还细抠还能省1张甚至更多&/li&&/ul&&/ul&&figure&&img src=&https://pic4.zhimg.com/v2-f00e8f430c419ed4617cebf_b.png& data-rawwidth=&433& data-rawheight=&329& class=&origin_image zh-lightbox-thumb& width=&433& data-original=&https://pic4.zhimg.com/v2-f00e8f430c419ed4617cebf_r.png&&&/figure&&p&&br&&/p&&ul&&li&从1024压缩到512肯定会损失精度,但是通过&b&仔细的分配lightmap精度&/b&能使这种&b&损失达到最小&/b&&/li&&ul&&li&方法一:&b&地面最有用&/b&,所以怎么省都&b&不要吝啬地面的精度&/b&,地面不管接受多少阴影,所占用的lightmap面积是一样的,所以多打点阴影(不增加消耗,还可以做效果,何乐而不为)&/li&&li&方法二:&b&纯背光&/b&和&b&纯亮光&/b&区域可以减少lightmap精度,因为压缩不影响(就像纯黑纯白的贴图压缩大小没有损失一样),而&b&复杂的结构&/b&并且能&b&同时受到阳光和阴影&/b&的影响,则也不需要节省精度,因为如果本来精度就不高,则压缩更惨&/li&&li&方法三:压缩大小比压缩格式带来的损失小,如果压缩格式影响巨大,可以&b&不压缩格式&/b&,而在&b&其他地方想想办法怎么省&/b&(省的办法参考上面)。所以推荐烘焙高质量的光照图来&b&压大小&/b&。&/li&&li&方法四(重要):&b&尽量使用大阴影&/b&,在不压缩情况下本来就很小很虚的阴影,被压缩了只会更惨,优化方法参考以上&/li&&li&方法五:&b&不推荐&/b&使用&b&复杂的结构&/b&,酌情使用,在压缩的时候很容易损失过大&/li&&/ul&&/ul&&h2&&b&个人经验&/b&&/h2&&ul&&li&暗部补光,感觉上并&b&不是光越多越好&/b&,最好灯光颜色来表达周围环境的间接光照,过多颜色的灯光会让人觉得没有重点和乱&/li&&li&点光通过&b&减小光照强度&/b&、&b&增加照明范围&/b&能得到更好的环境间接光的效果,还能防止有比较突兀的亮斑&/li&&li&个人倾向于用&b&天空材质球&/b&来代替全局环境光来烘焙,如果是晴朗天气,用默认纯蓝的天空即可&/li&&li&白模的情况下感觉lightmap很舒服而且很干净,而最终效果图的lightmap效果没有那么明朗。所以猜测为diffuse贴图的饱和度问题,因此推荐使用&b&较低饱和度的颜色&/b&,否则lightmap的颜色很容易被盖掉(不保证正确,希望美术同学尝试)&/li&&/ul&&h2&&b&跨平台的问题&/b&&/h2&&ul&&li&因为烘焙Lightmap用的exr,也就是HDR颜色编码,而现在手机平台不支持HDR编码,导致在手机平台和PC平台的解码方式不一样,所以不能使用过强的光来烘焙光照图,否则会造成android与pc差异巨大,因此&b&不推荐使用光照强度超过3&/b&的灯光来渲染。&/li&&/ul&&h2&&b&演示&/b&&/h2&&ul&&li&可以从&a href=&//link.zhihu.com/?target=https%3A//ixulin.github.io/attach/demo/test-bake3.rar& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&此处下载&/a&演示场景&/li&&ul&&li&给一张全貌&/li&&/ul&&/ul&&figure&&img src=&https://pic1.zhimg.com/v2-be7ad83ebcb8_b.png& data-rawwidth=&1270& data-rawheight=&789& class=&origin_image zh-lightbox-thumb& width=&1270& data-original=&https://pic1.zhimg.com/v2-be7ad83ebcb8_r.png&&&/figure&
转载请注明出处。原文链接: 最近给美术整理的,这是知乎上最类似的问题了,所以挂在这,一个是看是否有人有类似的问题可以参考,二来刷一下自己网站的外链,增加被爬虫抓到的几率。。。知乎貌似…
&figure&&img src=&https://pic4.zhimg.com/v2-b6d44d9f67_b.jpg& data-rawwidth=&883& data-rawheight=&414& class=&origin_image zh-lightbox-thumb& width=&883& data-original=&https://pic4.zhimg.com/v2-b6d44d9f67_r.jpg&&&/figure&&h2&&b&写在前面&/b&&/h2&&p&阴影是计算机图形学中一个很重要的部分,阴影的加入使得物体更加具有立体感,也有助于我们理解物体间的相互位置关系和大小。&/p&&p&实时阴影的实现方法有很多种,shadowMap适用性最好,但性能开销也大,有时候我们的项目其实并不需要那么通用的阴影,我们只需要一个“适用某些特定场合”的一个“看起来正确”的实时阴影。&/p&&p&本文所说的,就是一种利用顶点投射的方法实现的实时阴影技术,在一些阴影质量要求不高,地面平整的项目是一个非常合适的方案,现在很火的手游《王者荣耀》就用了类似的技术。&/p&&p&(经提醒,这个技术叫平面投影阴影(Planar Projected Shadows)技术,由Jim Blinn 1988年提出。&a href=&https://link.zhihu.com/?target=http%3A//www.twinklingstar.cn//tech-of-shadows/%2321_Blinns& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&twinklingstar.cn/2015/1&/span&&span class=&invisible&&717/tech-of-shadows/#21_Blinns&/span&&span class=&ellipsis&&&/span&&/a&)&/p&&p&&br&&/p&&h2&&b&原理&/b&&/h2&&p&忽略自身阴影不谈,如果我们只考虑物体在地面上的阴影的话,其实就可以把这个问题简单概括为求一个物体每一个顶点在某个平面上的投影位置了。&/p&&p&&br&&/p&&h2&&b&公式推导&/b&&/h2&&p&说到求投影,我当时首先想到的就是点积,后来经群友提醒,其实可以更进一步简化为求相似三角形,这样理解起来似乎还更简单些,以下是推导过程,为了简化计算,我们在二维空间内进行推导。&/p&&p&根据已知条件,我们可以得到一个这样的题目:已知平面坐标系内一个单位向量L(Lx,Ly),坐标系内一点M(Mx,My),求点M沿着L方

我要回帖

更多关于 db plan dc plan 的文章

 

随机推荐