粒子系统

粒子特效 打包成 AB 包后 Mesh 无法正常显示

Meshes must be read/write enabled to work on the Particle System. 
If you assign them in the Editor, Unity handles this for you.
But if you want to assign different meshes at run time, you need to check the Read/Write Enabled setting in the Import Settings.

当需要重新指派 Mesh 时,需要打开 Read/Write 的设置,但是 UnityEditor 会自动执行这个操作.

****注意:****​1.如果是 AB 包的话,UnityEditor 并不会执行这个操作.需要在打包前设置 FBX.2.打应用包时,也会出现相同的情况.

aa763f4c0b71366b5d42c7248e33276b.png

粒子的 GPUInstancing 只合并粒子系统本身的,不会合并所有粒子系统的,即使是同一个材质球,并且粒子系统需要走 pragma instancing_options procedural:ParticleInstancingSetup

https://docs.unity3d.com/cn/2019.4/Manual/PartSysInstancing.html

在粒子系统的 renderer​上要 Enable Mesh GPU Instancing

3ad29f2eec3d21b0ec94ba160ff0f305.png

总之粒子系统的 GPUInstancing 和其他的不相同,切记

动画与骨骼

  1. 动画 transform ​和 SkinnedMeshRenderer​分开时,如果动画不动,一般是骨骼没绑对 。需要绑定 bones ​和 rootbone
  2. rootbone​一般表示模型的 float3(0,0,0)​位置,更改 rootbone 一般不会影响动画、顶点位置法线这些。
  3. SkinnedMeshRenderer ​一般留意 bones ​,rootbone ​, bindPose ​以及 boneWeights

Shader

编码注意事项

  1. Shader​中属性非必要不要写float​,只有一些涉及坐标计算\深度的时候才需要的
  2. 单纯一个 if,≈ 4 个指令消耗。但是如果分支里有大量逻辑代码,执行数量就 == 双倍。如果单条件分支,就没啥消耗了
  3. shader​中 ifelse​,带有采样的时候,虽然某个分支可能没有采样,但是申请的寄存器会多一些(GPU 中可能不知道哪里用到,就都申请了)
  4. UNITY_UNROLL​只可用在常量上,非常量消耗更大
  5. Shader​中使用了 UsePass​在真机上 SRP Batcher​可能会出问题,Batcher 失败。留意下
  6. Shader​中少用矩阵、SinCos 这些函数
  7. shader​中 CBuffer​这种传 float​数组,最好也按 float4​/Vector​来传,用 float​/int 数组有问题

矩阵与坐标变换

  1. 向量矩阵这些相乘的时候,位移需不需要也要留意,想法线方向这些,直接拿 float3 来乘就好,不要带位移

  2. TransformObjectToWorldNormal 的原理是什么?

    查看其源代码,它根据是否定义了 UNITY_ASSUME_UNIFORM_SCALING​宏而使用两种方法之一。当定义时,它调用 TransformObjectToWorldDir​,忽略平移部分(因为处理的是方向向量),但向量会均匀缩放,需要归一化。不假定均匀缩放时,法线向量必须反向缩放,需要乘以模型转置矩阵 UNITY_MATRIX_I_M​再归一化。可通过 #pragma instancing_options assumeuniformscaling 启用优化。

  3. unity_WorldTransformParams.w 在三个坐标轴存在奇数个负缩放时为 -1 否则为 +1,因为在存在奇数个负缩放时,左右手系会发生转换,需要进行修正。

  4. UNITY_MATRIX_M​比 ObjectToWorld​要好些,在 GPU Instancing​中,可能赋给 UNITY_MATRIX_M​的值是对的,而 ObjectToWorld 可能只针对单个物体

  5. UNITY_MATRIX_V[2].xyz 表示相机的朝向,即 C# 代码里的 transform.forward

渲染状态与混合

  1. Cull Back​剔除背面的几何形状,判断标准是三角形的反面,跟摄像机正对的是没关系的。反之 Cull Front​也一样,Unity 中一般是顺时针

  2. Shader​中 Tags { "RenderType" = "Transparent" "Queue" = "Transparent" }​里面的 Queue​只是 CPU​为了渲染顺序进行区分的。至于混合 Blend​在那个队列都可以使用,一般在 Transparent​里面使用。这个是 GPU​操作的,是和颜色缓冲区挂钩。

  3. 多个像素深度相同的时候,使用 Blend​混合的时候,同一个点多个像素值混合会出现问题。这个时候可以使用 Stencil​模板,让一个像素通过,其他像素不通过,保证 blend 不出问题

  4. shader​中 Clip​不可以借用 ifelse​报错,得使用 keyword

    //错误示例
    half4 frag_outline(v2f i):SV_Target
    {
        if(_MainTexAlphaCutOff)
        {
            half4 MainTexture = tex2D(_MainTex, i.uv0.xy); // 不要在if中采样贴图
            clip(CutOff - MainTexture.a);                  // 不可以在if中执行Clip
        }
    }
    
  5. Shader 中模板缓冲以及深度缓冲区的值都是在模板测试和深度测试都结束之后才赋值的

  6. Shader 中渲染顺序很重要,有些情况不对,注意下渲染的顺序

  7. Shader 中渲染时,两个对象分别有 A、B pass 时,是先画对象 1 的 A pass,再画对象 1 的 B pass,然后再画对象 2 的

  8. OpenGL 没有采样器的概念,完全读图的配置

    DX 和 Vulkan 有采样器,数量限制 16,可以根据采样器点采样或者双线性采样

  9. 不用看 Mesh 什么样的,多大,其实看的还是转到裁剪空间下的占多少,这个才是最终画的大小。采样贴图消耗,实际也是看采样 UV 的范围。

深度与阴影

  1. Wclip = -Zview​裁剪空间下 W​ == -视图空间下 Z
  2. DDX​和 DDY​可以重建法线,但是是非平滑的。float3 normal = normalize(cross(ddy(i.objPos), ddx(i.objPos)));
  3. 正交相机下视图空间 z​就是线性深度,w​是 1;透视相机下视图空间 z​就是非线性深度,w​就是线性深度,z/w 就是去采样深度图的那个
  4. Unity 建议正常不透 2000,AlphaTest​ 2450,半透 3000,半透的时候可以关闭 ZWrite​。但是 3000 队列的会从后往前画,有可能会人物重叠产生 OverDraw
  5. 解决阴影渗漏(痤疮 Acne)使用深度偏差

纹理采样与颜色空间

  1. 在顶点着色器中采样贴图 _NoiseMap.SampleLevel(sampler_NoiseMap, noiseuv, 0)​。不能使用 tex2D​,原因是 GPU​计算贴图时需要使用适当的 mipmap​,mip​级别的选择需要经过片段着色器阶段才能获取,旧着色器需要使用 tex2Dlod 才能在顶点着色器中采样

  2. _Color("Color", Color) = (1, 1, 1, 1) 颜色空间说明:

    空间 属性 行为
    Gamma _Color 输入啥就是啥
    Linear _Color Pow(2.2)
    Linear [Gamma]_Color Pow(2.2)
    Linear [Linear]_RimCol 没用,仍会 Pow(2.2)
    Linear [HDR]_Color 是线性的,输入啥就是啥
    Linear [HDR][Gamma]_Color Pow(2.2)

    5526e4fb16621f1292ecb436862ec113.png

    71f2a65af2c9cff1d4ce27731eb46796.png

  3. 贴图里面的颜色和 Unity 里面取出来的值不一定是一样的,可能你数组定义颜色 0.5f​存到 png​里面有可能会转的,就 GetPixel​、SetPixel 根据颜色空间或者啥的给转了

  4. 正常美术出图单独的只存在法线信息的法线贴图,使用 Type 为 Default​且 sRGB​和使用 Normal Map 是一样。

UI 渲染

  1. UI​中的 color​会存在 Vertex Color​中,包括 Text​、Image​这些,这么做是为了合批。在 Unity 2022 高版本中 Canvas 上存在选项 Vertex Color Always In Gamma Color Space
  2. UI 中画模型的情况,sortingOrder​的优先级最高(越小的越先画),同等 SortingOrder​情况下看 RenderQueue​的大小(越小的越先画),然后再看 SortingCriteria criteria = SortingCriteria.CanvasOrder | SortingCriteria.CommonTransparent;

CommandBuffer 与渲染管线

depthDesc.graphicsFormat = GraphicsFormat.None 只写深度,省一半消耗,因为颜色缓冲清除了。具体在 PC 还是安卓忘记了

// 绘制全屏mesh
cmd.SetViewProjectionMatrices(Matrix4x4.identity, Matrix4x4.identity);
cmd.DrawMesh(CoreUtils.fullscreenMesh, Matrix4x4.identity, material, 0, pass);
cmd.SetViewProjectionMatrices(camera.worldToCameraMatrix, camera.projectionMatrix); // 恢复相机矩阵
CommitCommandBuffer(cmd);

cmd.SetRenderTarget​的 RenderBufferLoadAction​表明加载进这个 RT 之后,需不需要留意里面的内容,在这之后才是 clearDepth​、clearColor 也就是清除里面的内容。所以不能瞎填

cmd.SetRenderTarget(_LowResTransparentHandleTexture.Identifier(), RenderBufferLoadAction.Load, RenderBufferStoreAction.Store);
cmd.ClearRenderTarget(false, true, Color.clear);
renderer.CommitCommandBuffer(cmd);

cmd.Blit​相当于切了一次 RT

深度缓冲从一个 RT 切换到另一个 RT 的时候,RT 大小不等时,即 cmd.SetRenderTarget​使用了 Load​数据也是不对的,需要在 shader Blit 的时候设置 SV_Depth 啥的去设置,这还不如直接使用深度图

同一个 CommandBuffer​内连续 DrawMesh​/Blit​/SetGlobal​/SetRenderTarget​等操作不需要中途调用 context.ExecuteCommandBuffer(cmd)​;但如果后续要调用 ScriptableRenderContext.DrawXXX​系列 API,则需要先 context.ExecuteCommandBuffer(cmd)​且 cmd.Clear()

cmd.SetRenderTarget​使用 RenderBufferLoadAction.DontCare​之后,cmd.ClearRenderTarget​是否需要清除 color,可以根据 shader 里面混合模式以及有无 Clip 等操作判断。一般全屏后效,像素都能覆盖到,默认 One,Zero 混合可以考虑不清除

Y 翻转相关:

写入阶段:只要使用 Unity 构造的投影矩阵(GL.GetGPUProjectionMatrix​ / SRP 内置矩阵),Unity 会在生成矩阵时自动加入平台所需的 Y 翻转,因此渲染到 RT 或屏幕时方向统一;但如果你绕开它(手写投影矩阵、直接用 Matrix4x4.Perspective、手动做 NDC→clip→screen、或自定义 SRP 不用 Unity 的矩阵),就必须自己处理翻转。

上屏阶段:当 RT → 最终 CameraTarget​时,Unity 会根据平台原点差异自动在 UV 层面做 Y 翻转(_ProjectionParams​、UNITY_UV_STARTS_AT_TOP​等),确保最终显示方向一致。注意 SceneView​是上屏到 RT,是不会处理的;GameView​是 CameraTarget 会处理的。