Unity Graphics 群友交流记录贴(干货)
写在前面:本贴是 Unity Graphics 专栏交流群中各大佬交流Unity问题时的记录,仅供参考,如发现错误 请及时提出!谢谢。在此先谢过各位乐于帮助解惑的大佬,才有此笔记的诞生。持续更新及优化中...
Ps:此贴覆盖知识面很广泛,很多问题本人也是小白,引用都是大家的聊天记录,
仅供参考 仅供参考 仅供参考
更新时间:2019.12.19
1.粒子优化问题
1. 尽可能减少粒子的空白面积 最好用opaque的粒子
2. 减少粒子叠的层数 宁愿用spritesheet之类的 或者各种复杂一点的shader算法来提高单个粒子的效果 也不要叠太多粒子在同一块区域
3. 是可以用premultiplied alpha从前往后渲染粒子 然后用alpha test把多余的透明部分剔除后 写入stencil+1 当stencil超过一定值时则直接reject2.为何一个特效发射时内存占用的差距那么大?请教一下各位大佬
变卡的原因主要是因为切换回原来的render target的时候 tile gpu需要回读数据,如果你的render target切换之后 调用了glclear 那就没必要回读..setrendertarget之后立马clear. 我意思是说 glbindframebuffer之后马上glclear一下 而不是glbindframebuffer之后渲染 然后clear..bind -> clear -> render 这不适用于后处理吧,只适用于 两帧之间..3.同时读取和写入同一张贴图全屏特效
老机型不支持 读取和写入同一张贴图,不能再shader里读取当前的fbo
新机型基本都支持 framebuffer_fetch这个扩展 所以可以直接input和output用同一张
bind A -> bind B -> clear B -> Blit from A to B -> bind A -> OnRenderImage(B, null)
这个问题最完美的解决办法是用vulkan的subpass srp支持subpass
从默认的fbo 切换到另外一个fbo-> 全屏特效 -> 写入backbuffer
https://docs.unity3d.com/ScriptReference/Rendering.ScriptableRenderContext.BeginRenderPass.html?fbclid=IwAR3QxF6b-EuwpbEjumGTjzuZA_9S1px7Er0cZsXlmTMQXhome5lQPc60w4g
接让从原来的rt 读取内容 最后用graphics.blit(a, null)把destination设置为null 让unity把全屏特效的结果直接写到backbuffer上 这样就没必要再切换回A 再读取A的数据了MainCam.SetTargetBuffers(color.colorBuffer,depth.depthBuffer)
4.OnRenderImage相关
OnRenderImage 会先copy一次color0到新的RT 再切回来把拷贝的rt给你所谓source传给你,这个两次render target switch 会有一次pipelien resolve会触发 GPU整个管线flush,等于说你渲染了两帧,要避免这个问题 必须让camera的render target不为空 然后用那个render target作为source. OnRenderImage 会多一个 target 分辨率的 dc.
这个问题 旗舰机上看不出来,是为了保底最老的机器也能用,
OnRenderImage换到OnPostRender之后快了7ms多,
其实现在绝大部分机型都可以直接读取当前正在写入的framebuffer,但是一些老机器不支持 会花屏,这个问题只是为了让image effects可以在不支持framebuffer fetch的机型上可以运行,但是这个做法会导致整个GPU管线flush.正常情况 gpu是把所有的drawcall都排队好 在present的时候再一起执行的 但是这个做法会让GPU在blit的时候就渲染, 还有 blit type设置成never也可以再减少一次blitvoid OnPreRender()
{
mainRT = RenderTexture.GetTemporary(Screen.width, Screen.height, 24, RenderTextureFormat.Default,
RenderTextureReadWrite.Default, _MSAA);
GetComponent<Camera>().targetTexture = mainRT;
}
void OnPostRender()
{
GetComponent<Camera>().targetTexture = null;
RenderImage(mainRT, null);
RenderTexture.ReleaseTemporary(mainRT);
}5.alpha test的prepass是咋省的overdraw
延迟渲染的灯光剔除,大汗说是depth prepass + zequal pass ,真渲染时zequal prepass的overdraw无所谓,这个zequal不也是像素着色器跑完了才判断嘛,这个时候已经overdraw了.但是没有clip就有early-z了?我是这么理解的 prepass是alphaclip 渲染的时候就纯opaque...
全部alphatest在prepass写入深度,然后在geometry pass,zequal6.Shader 精度问题选择
1,向量类型必须float
2.所有的fixed全部写成half
#pragma fragmentoption ARB_precision_hint_fastest
实际上这个预编译指令,也就是把你写的所有fixed float全部在编译成机器语言时变成了half
https://opengl.gpuinfo.org/listreports.php?extension=GL_NV_gpu_program4
https://www.khronos.org/registry/OpenGL/extensions/NV/NV_fragment_program4.txt
也就是说只有针对PC有用 并且只对N卡有用 因为Mac和主机大多数都是A卡7.CPU和GPU是一帧渲染的吗
多线程渲染的话 CPU比GPU快了半帧多的时间, 主线程上一帧和渲染线程的上一帧不是同步的,一般帧率以渲染线程的帧率为准
非多线程渲染的情况呢那就是游戏逻辑 + 图形命令总共一帧8.請教一下,目前SRP的RenderQueueRange只有 all,opaque,transparent,如果想切一個alphaTest,建議怎麼做會比較好?
用图层layer 或者RenderingLayer 或者Shader Tage切
畫兩次,第一次用低解析度,第二次再畫到屏幕上。
我之前試過物件降解析度渲染到一張貼圖,然後反貼圖來再畫一次,由於這個物件的pixel shader很複雜,但vertex資料算少,結果性能大幅提升,反正這個物件效果本來就是糊糊的(比方雲)。大幅降低處理的像素,像海及假雲plane之類的都很好用。這個就是降解析度處理,反貼回去再畫雲及水plane的狀況
https://www.youtube.com/watch?v=DT_3-WDG8Ww9 famebuffer fetch的用法
https://gist.github.com/aras-p/ca86e6dddc46def4d1a810 StructuredBuffer
A:StructuredBuffer是一个只读的结构化缓冲区。
https://docs.microsoft.com/zh-cn/windows/desktop/direct3d11/direct3d-11-advanced-stages-cs-resources
https://forum.unity.com/threads/structuredbuffer-with-surface-shader.173000/ (如何使用)11 FrameBufferObject
A:通常包含一个系统所具有的所有缓冲器,但有时也可以认为是颜色缓冲器和
z 缓冲器的组合
FrameBuffer就是gpu里渲染结果的目的地,我们绘制的所有结果(包括color depth stencil等)都最终存在这个这里,有一个默认的FBO它直接连着我们的显示器窗口区域,就是把我们的绘制物体绘制到显示器的窗口区域。但是现代gpu通常可以创建很多其他的FBO,这些FBO不连接窗口区域,这种我们创建的FBO的存在目的就是允许我们将渲染结果保存在gpu的一块存储区域,待之后使用,这是一个非常有用的东西。
当渲染的结果被渲染到一个FBO上后,就有很多种方法得到这些结果,我们能想想的使用方式就是把这个结果作为一个Texture的形式得到,通常有这样几种方式得到这个贴图:
a) 将这个FBO上的结果传回CPU这边的贴图,在gles中的实现一般是ReadPixels()这样的函数,这个函数是将当前设为可读的FBO拷贝到cpu这边的一个存储buffer,没错如果当前设为可读的FBO是那个默认FBO,那这个函数就是在截屏,如果是你自己创建的FBO,那就把刚刚绘制到上面的结果从gpu存储拿回内存。
b) 将这个FBO上的结果拷贝到一个gpu上的texture,在gles中的实现一般是CopyTexImage2D(),它一般是将可读的FBO的一部分拷贝到存在于gpu上的一个texture对象中,直接考到server-sider就意味着可以马上被gpu渲染使用
c)将这个fbo直接关联一个gpu上的texture对象,这样就等于在绘制时就直接绘制到这个texure上,这样也省去了拷贝时间,gles中一般是使用FramebufferTexture2D()这样的接口。
那么unity的RenderTexture正是这种c)方式的一种实现,它定义了在server-side的一个tex对象,然后将渲染直接绘制到这个tex上。
https://blog.csdn.net/leonwei/article/details/54972653 12.Constant buffer
https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-resources-buffers-constant-how-to
https://developer.nvidia.com/content/how-about-constant-buffers13.ComputeBuffer
https://docs.unity3d.com/ScriptReference/ComputeBuffer.html
http://kylehalladay.com/blog/tutorial/2014/06/27/Compute-Shaders-Are-Nifty.html
https://zhuanlan.zhihu.com/p/5378595414.Shader Constants
Shader Constants - Win32 apps15 Shader Data
https://bell0bytes.eu/shader-data/16 什么是forward pixel kill ?FPk
Forward pixel kill(FPK)是arm mali gpu中的一种隐藏面消除方法,对于不透明的物体,当按照从后往前的次序渲染时,可以改善性能;与early zs test不一样,early zs test是对于从前往后的次序渲染可以kill掉overdraw,由于实际情况中,我们很难保证应用中渲染的物体都是从前往后的顺序,对于复杂的场景这很难做到的也不可能做到,所以设计了FPK与early zs互补来提高gpu的性能。17 如何抓帧Debug
memory profile renderdoc抓帧 snapdragon profileapitrace
ninja ripper黑色猎手18 warp是什么?
每一个pixel shader的调用称为一个thread
现在的GPU都是按WARP同时处理多个任务的,任务可以是pixel(如果是PS)或者thread(如果是CS)或者是vertex(如果是VS),PS输出是2*2个像素一个quad为单位,一个WARP对应多个quad,quad在屏幕上的位置不确定,如果是复杂mesh你可以理解为随机。如果是三角形的边缘或者小三角形实际光栅化后填不满2*2像素的quad,GPU会补充helper lane保证还是2*2一个quad输出。实际上pixel从来不是一个个处理的,这样做太没效率了,另外mipmap算不出来,讲道理,如果是在PC上开发,你弄一个AMD卡然后用AMD GPU PROFILER看一下,就全明白了19 branch相关
一个warp内所有分支一样的话,branch就没什么开销,最多有些浪费的vgpr,,比如doom那篇里就说了,用wave intrinsic,尽量保证一个warp内各个thread起码在读取的时候是读取同一地址,利用SGPR,减少divergence
不能处理,但是能尽量减少divergence 比如你的if条件是根据读某个buffer后的结果来算的,如果能保证每个thread都是读同一地址,那么读出的结果就相同,if的条件也相同,就减少divergence了
比如光照里做light loop,所有thread都读同一地址,即所有thread都处理同一光源,就会快很多20unity hdr一个通道有多少位
16,SRP可以选10或者16,一般都是用ARGBHalf,R11G11B10精度太低了,而且没有alpha,少了一个通道21 normal blend的正确算法是啥啊?
A:直接normalize(lerp(normal1,normal2,factor))Unity算法如下:
https://docs.unity3d.com/Packages/com.unity.shadergraph@6.5/manual/Normal-Blend-Node.html22 我利用compute shader对同一个mesh 算出来了很多不同的结算 存在了StructuredBuffer里面 ,但是我应该怎么batch它们呢?
DrawInstancedIndirectly只能是DrawProceduralIndirectDrawCluster23 UPP写一个函数直接输入一个HoudiniAsset的Profile文件然后函数里面直接拿这个Asset的某些信息直接转成StaticMesh输出,这样玩靠谱么
看这个StaticMesh的粒度了吧。。如果是河流,湖面这种本身也要根据地形去修改mesh布局还有uv什么的,这种方法确实可以。
其他的尽量还是走point cloud与asset结合的方式要好一点
反正能不生成staticmesh就尽量别生成。这个对整个资源管线的破坏性太大。24 shader里判断屏幕y轴的方向是哪个宏
#if UNITY_UV_STARTS_AT_TOP25 手机上是不是SetRenderTarget后必须ClearRenderTarget
是的,loadstoreaction要根据你的需求来,所以SRP里我尽量少SetRenderTarget,然后尽量clear。不能用Temporal 了吧。。setRenderTarget如果不需要load或者store的话 开销也不高。。比如你渲染fbo0 中间又去渲染fbo1 完了又切回来继续渲染fbo0 那就必须得load 这个就很费26 咋判断Camera是不是Editor里的啊。。
SceneView.lastActiveSceneView.camera27 LOD 方案的制定流程是怎样的呢?
我试过low poly + unlit 100多万顶点不会掉帧,让模型师在3Dmax里面用pro optimize降面 有问题的地方手动修一下。还有你可以试试 max那个hiz遮挡剔除 应该帮助很大,看来是没用过simplygon…,还有个办法就是把房子这些的低模直接弄成盒子 + 贴图。。堡垒之夜的骚操作https://shaderbits.com/blog/octahedral-impostors/
lod可以试一下cdlod https://github.com/fstrugar/CDLOD28:手机上怎么弄Tonemapping比较好。。?
//_ColorParams曝光度
half3 tonemapACES(half3 color)
{
#ifdef _TONE_MAPPING_ON
color *= _ColorParams.w;
#endif
// See https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
const half a = 2.51;
const half b = 0.03;
const half c = 2.43;
const half d = 0.59;
const half e = 0.14;
return saturate((color * (a * color + b)) / (color * (c * color + d) + e));
}
29:手机上做Alpha Test,先做一遍Depth prepass对性能有显著提升吗?
prepass 提高 alphatest
手机上不知道 PC上我给depth prepass去掉手动sort带来了性能提升,我是一般Depth prepass的时候也要做排序的.毕竟排序消耗很低,但是能减少zwrite消耗.做植被的话提升巨大/我这边是优化粒子去掉的depth prepass
https://zhuanlan.zhihu.com/p/58017068
看了大佬的文章懂了,就是 alpha test 在手机上依然会使 early z 失效,不过可以先画一个正常的,只快速写深度啥也不干,后面按照纯 opaque 去画,并且深度测试 设置为 Equal ,这样后面那一遍就能利用 early z 了,30:下雨特效怎么做
原文英文
https://seblagarde.wordpress.com/2012/12/27/water-drop-2a-dynamic-rain-and-its-effects/
二手文
https://gameinstitute.qq.com/community/detail/100054
https://connect.unity.com/p/jiao-cheng-zai-unityzhong-shi-xian-bi-zhen-de-xia-yu-xiao-guo31:Graphics类问题
别Graphics,一定要用CommandBuffer,然后SRP一起提交,否则没法走渲染线程会直接卡死主线程的commandbuffer也可以单独draw32:可以手绘hdr图 软件
krita33:使用SRP之后Camera Replace shader還有用處嗎?目前有沒有人在SRP下用Replace shader.
没用了,用DrawingSettings.overrideMaterial 我对不透明物体就用override,提供一個統一的cast shadow material就好34:UE4 bloom for unity
https://gist.github.com/CarlLee/4ef88caa0d39c36eda9e3b8e06751bab35:需要使用grab pass的特效来说呢,比如一个刀光挥过做空气扰动
grabpass看你要做什么效果 像全屏扰动其实可以直接读fbo的color attachment,或者用全屏网格模拟扰动的效果.比如画个64x64的全屏quad 然后vertex里面采样扰动贴图 然后vertex自己做偏移.这个手机上会比直接读color texture快,效果会差一些
拿个相机单独拍摄扰动的信息 或者把扰动的信息写到framebuffer的alpha通道,崩坏3就是这么搞的只不过他的扰动也是全屏特效实现的 不是网格这个办法36:在unity的shader里面能获取到摄像机裁剪平面的距离吗
LinearEyeDepth(1),reverse z就是037:Curve Texture
获得物体表面曲率信息,曲率高的地方,往外凸出的容易磨损,往内凹陷的容易积累污渍,可以用这个当蒙版制作磨损和污渍38:《Sky 光遇》中的水是怎么实现的
在普通水体的基础上 单独再拿个相机 拍摄一个跟着人物走的粒子系统 这个粒子系统决定水体在本来的液面高度的基础上 增加或者减少的高度(particle system)
粒子系统做消散 就是个放大动画 + 颜色渐变嘛39:《Sky 光遇》中的云是怎么实现的
vertex displacement + tessellation
猜想:
1. 基于噪声的 vertex displacement
2. per pixel的normal noise用来计算光照
3. 和场景、人物之间有depth fade
4. 云边缘处有基于某种噪声的随机discard 用来隐藏mesh的形状 造成一种面数很高的颗粒感错觉
云的交互怎么做的
人身上绑个球体,然后把顶点挤开 法线也要重新算,应该就是把接触到的点的法线改成球面指向圆心的法线
还有那个噪声的滚动速度会受人物所在位置的影响 风大的地方 噪声滚动速度会变快。给人一种是物理模拟出来的错觉40 linear 和 gamma 是啥gamma到linear是1/2linear到gamma是2?
我总结一下前面说的, gamma 模式下,勾不勾 srgb ,都是一样的,不会做任何处理。
linear 模式下,钩上 srgb ,untiy 会认为你是 srgb 空间下的图,但是我特么是 linear 模式,所以我给你 pow 了 2.2,让你变成 linar 空间;不勾选 srgb,那我认为你的图就是 linear 空间下的,不用做任何处理,可以直接在 linear 模式下使用。
但是,linear 模式下,framebuffer 要 pwo 0.45 从 linear 空间颜色转为 srgb 空间颜色存储。
至于 alpha 变不变,看公式, alpha 应该是不会变的,否则 alpha 也需要转换才对
中灰(0.5)的线性亮度应该是0.25,所以gamma到线性空间应该是pow 2.2
两个线性c1,c2颜色混合,正确的结果是c1*a + c2*(1-a)
然而,因为如实srgb空间的图,进行线性混合的时候就变成了:c1^0.45 * a + c2^0.45*(1-a)
pow(pow(colorA, 2.2) * alphaA + pow(colorB, 2.2) * (1-alphaA), 0.45)
SRBG :https://en.wikipedia.org/wiki/SRGB41computer shader里拿到深度图
SetGlobalTexture,从管线里设置后渲染的一个全局纹理,你SetRenderTarget然后DrawRenderers不就有了么?ZWrite on自己就画上去了,_CameraDepthTexture shader里访问这个纹理 也是这样出来,我改名叫_DepthTexture 这个好了...42Stencil相关
https://gameinstitute.qq.com/comunity/detail/127404
https://learnopengl-cn.readthedocs.io/zh/latest/04%20Advanced%20OpenGL/02%20Stencil%20testing/
https://segmentfault.com/a/1190000018440902
https://connect.unity.com/p/articles-dissolving-the-world-part-143Project Decal 弹孔、魔法阵(AE範圍)、圆影…等贴花效果
Decal实现的关键在于,其必须在光照运算和GI运算之前做完,所以要么Deferred要么Forward Cluster,再要么Depth prepass + Decalmap
forward decal我能想到的就两种,cluster,decal map
https://blog.csdn.net/bugrunner/article/details/7422732 Deferred Decal(延迟贴花)
https://makedreamvsogre.blogspot.com/2019/07/unity-project-decal.html?fbclid=IwAR0AhoT38w9_6_9WDiz5Vt3pIEi1wBTjf0jwgtwJr2G3QAIruSvkBYw12bY//台湾大佬的
https://www.slideshare.net/blindrenderer/screen-space-decals-in-warhammer-40000-space-marine-14699854
https://assetstore.unity.com/packages/tools/particles-effects/ink-painter-86210//unity免费的
https://zhuanlan.zhihu.com/p/43050863 //仿Splatoon喷漆效果
https://zhuanlan.zhihu.com/p/60293697//麦老师的
是在每个物体的shader中采样当前像素的decal贴图和法线 然后当前像素世界坐标反推盒子中的uv用来采样,可以改进一下就是不需要用世界坐标来求盒子内部uv 这样是每个像素多了个矩阵乘法 可以在vertex中算出盒子空间的viewdir向量 然后把这个向量插值传到frag中归一化加上像素深度就可以得到盒子空间的uv44 Depthonlyearly-z 相关
depthonly主要是为了一些需要用深度的特效做的,和camera target都不是同一个fbo,
新一点的gpu都有 early-z.拷贝慢是因为拷贝完了 你要切换回camera target的fbo 然后渲染透明物体 这时候需要从系统内存回读之前渲染好的贴图,这个回读是很慢的。可以用同一个depth buffer 切换不同的color buffer。render doc抓帧就看到,可以用来做depth prepass减少over draw 45 gc.collect优化相关
1.我们是把场景全部变成预支体,然后导出格子数据,异步加载,用ET框架,全部是对象池拿组件,我们写代码都是自己控制生命周期,是比较可控,就是自己管理ecs组件,回收都是手动回收
2.2019加了incremental好不少,分帧异步加载,先显示地形,之后植被,以前改造UE3做大地图的时候我们就这么做的,streaming后的加载和每隔若干帧的GC,都分摊到多帧去做
页:
[1]