在平行光照中加入Cookie遮罩

在平行光照中加入Cookie遮罩

这是第166篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间10分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群2:793972859(原群已满员)


本期目录:

  • 在平行光照中加入Cookie遮罩
  • ToLua table使用耗时优化
  • 关于镜面反射的一些优化
  • 在Unity中用BVH做一个动态遮挡剔除插件
  • PlayableBehaviour类中某个类内部的变量的问题

Rendering

Q:我们自己写的Shader,一般的平行光能接受光照和阴影的处理。但美术想在灯光上加一个Cookie遮罩,结果模型就是暗暗的。

    Pass
    {
        Name "FORWARD"
        Tags { "LightMode"="ForwardBase"}
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #include "UnityCG.cginc"
        #include "AutoLight.cginc"
…
        uniform fixed4 _LightColor0;
…
…
       struct VertexInput
       {
       UNITY_VERTEX_INPUT_INSTANCE_ID
       half4 vertex : POSITION;
       half3 normal : NORMAL;
       half2 texcoord : TEXCOORD0;
       };

        struct VertexOutput
        {
            half4 pos : SV_POSITION;
            half2 uvMain : TEXCOORD0;
            half3 normalDir : TEXCOORD1;
    LIGHTING_COORDS(2, 3)
        };
…

        fixed4 frag(VertexOutput i) : SV_Target
        {

在这里取_LightColor0.rgb,如果有Cookie,则这里全是000全黑的;没有设Cookie,则取到的就是灯光的颜色。

这是怎么回事,一定要再加一个ForwardAdd Pass来处理吗?

美术想要一个外加的光影变化,渲染氛围(比如:森林里明暗错落的感觉),但又不想DrawCall翻倍,有好的实现方式吗?谢谢。

下面一张图没加Cookie就比较平面、呆板。

A:目前的思路只能参考一个简化迷雾的方式,因为这边地图还算平,所以可以用这个方式。和地面一样高度放个Mesh面片,然后Shader渲染层级调成这样:

Tags{ “Queue” = “Transparent+150” “IgnoreProjector” = “True” “RenderType” = “Transparent”}

这么想来这个方式稍微改改,还能做简单的多云天气效果。(混合方式可能还得改改,美术说建筑有些颜色失真)

Shader "Studio1/Flow2"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _MashAlpha("阴影透明度",Range(0.00, 1.00)) = 0.60
        [KeywordEnum(OFF, ON)] _MoveUV("uv移动", Float) = 0
        _MoveSpeed("uv移动速度",Float) = 1
    }
    SubShader
    {
        Tags{ "Queue" = "Transparent+150" "IgnoreProjector" = "True" "RenderType" = "Transparent"   }
        LOD 100

        Blend SrcAlpha OneMinusSrcAlpha
        ZWrite Off
        ZTest Off
        Cull Back


        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #pragma multi_compile _MOVEUV_OFF _MOVEUV_ON
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float _MashAlpha;
#ifdef _MOVEUV_ON
            float _MoveSpeed;
#endif
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed2 tep=i.uv;

                #ifdef _MOVEUV_ON
                //让uv随时间变化
                tep.x +=_Time.y * _MoveSpeed;
                tep.y +=_Time.y * _MoveSpeed;
                #endif

                fixed4 col = tex2D(_MainTex,tep);
                col = fixed4(col.rgb, col.a*_MashAlpha);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

感谢题主真木@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5cfdd15f18013226f621cc3d


Lua

Q:一般来说会觉得Lua的table缓存起来用比较好。但我自己打印试了一下感觉Vector3这种小容量的table,New起来比用缓存起来改值的开销小很多。请问一下,这两种用法的不同对性能的影响大吗?平时需要去关注吗?

附图:(左边是缓存的耗时,右边是New的耗时竟然有8倍的差距)

A1:你写成如下代码就好了:

local VSet = Vector.Set

for i = 1, 100000000 do
VSet(c, a.x+b.x, a.y+b.y, a.z+b.z)
end

你的案例比较的是通过Metatable调用函数以及直接调用函数看谁更快。因为主流的Lua面向对象实现,使用obj:XXX()方式调用函数,都是通过Metatable去完成的,这种做法虽然很适合支持继承,但是多了一次Metatable的查找,性能肯定是会变差的。把Vector.Set缓存一下,就节省了这些查找时间。

另外:

local VNew = Vector.New

也比直接Vector.New要快,节省了一次字段查找时间(在Vector里查找New字段)。

这种优化可以用在任何有函数调用的地方,在消耗比较高的地方做就好了,不需要整个代码都这样。
感谢招文勇@UWA问答社区提供了回答

A2:这涉及到测试方法的问题:

1、测试前后必须要GC一下,防止前面积累的内存在后面阶段的测试造成影响。
2、测试的量级要和游戏内运行的量级要一致。量级太大或太小都不利于反映影响程度。
3、减少外部调用,可以把外部的实现直接写在循环里。如果非要用外部的接口,应该对此类接口也做好性能评估。

最后,说一下我最近做这一块的性能测试结论:复用Vector比New一个更快,并且不会造成内存分配和GC造成的耗时。

感谢hy@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5d0a221d9ca82b592a9d393a


Rendering

Q:现在游戏里某些场景的地面用了实时镜面反射,反射的开销有点大,面数峰值有20多万。

用FrameDebugger看了下,发现某些不太需要的部分也做了反射,导致DrawCall和面数增加了不少,例如:

1、角色描边,假设一个角色6000面,描边6000,反射角色6000,反射描边6000,一个角色就要24000面。反射描边这部分根本看不见,完全可以去掉。
2、模型LOD,反射贴图本身清晰度并不高,用低级的模型LOD也可以。

针对这两个问题,我想了下解决方法:

1、在渲染反射贴图的OnWillRenderObject方法里,把Shader.globalMaximumLOD强制改成100,LOD100的Shader没有描边,计算也少很多,渲染完再改回原来的。
2、在渲染反射贴图的OnWillRenderObject方法里,把QualitySettings.lodBias强制改成0,让摄像机用低级模型LOD渲染,渲染完再改回原来的。

现在我的问题是,实时镜面反射贴图每帧都要渲染,也就是说每帧我都会修改Shader.globalMaximumLOD和QualitySettings.lodBias的值,这个操作会不会导致什么问题?

自己打了个包试了下,Profiler里看来回设置Shader.globalMaximumLOD和QualitySettings.lodBias的时间,比节省下来渲染的时间还多。

那么有没有什么别的优化方法呢?除了控制反射物体的层级以外。(这个打算做,在目前项目的情况下还有一些难点要解决)

A1:不知道这个能不能帮助到题主,Material.SetShaderPassEnabled在反射相机渲染的时候关闭描边的那个Pass,不知道性能如何,仅提供个思路。
感谢王宇@UWA问答社区提供了回答

A2:我后来想了个办法,用ReplacementShader。用一个只有diffuse的Shader Replace,这样1是没有描边,并且能降低计算量;2是只会渲染不透明物体,可以少掉特效和3D UI的DrawCall。

感谢题主deviljz@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5d0b4f5f20084a38c25ecfbc


Culling

Q:本人项目中物体都是动态加载的,所以Unity自带的遮挡剔除功能用不了,也用过一些动态遮挡提出的插件InstantOC等,但是这些插件都是基于射线检测所有的物体,所以在项目中CPU的计算消耗时间很长,有点得不偿失。

所以打算在Unity中用BVH做一个动态遮挡提出插件,请问有什么思路可以提供吗?谢谢。

A:自己用BVH算的话也是算遮挡关系的吧?要结合视线做较为准确的计算,消耗应该也不少。

不知道题主的场景物件动态加载只是动态加载,还是加载完成之后就不动了,还是说场景重点物体都是动态的,或者场景是随机生成的。如果是前者,遮蔽关系也是可以预先离线计算好的;如果是后者,可能的确只能用动态计算了。

这块消耗大,可能还不如直接暴力的距离剔除和LOD。看你截图,不透的东西也比较多,所以遮挡剔除主要还是减少DrawCall,省下来的CPU时间,看看和做剔除的时间哪个更多吧。

感谢贾伟昊@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5ce7977efd7bd665736194d0


Timeline

Q:Timeline播放可以录制变量变化,俗称手K帧。我看到例子里可以记录PlayableBehaviour类内部的变量变化,比如浮点、向量等等,目前希望实现在自定义的PlayableBehaviour内维护自定义的config类实例。但是使用时发现,如果录制过config实例里的变量有修改,播放时config实例为空引用,所以想请教如何正确操作?是否是因为config的初始化时机不正确。没有K帧操作时,config作为成员变量不用写New来初始化,这时没有报错或运行的问题。

A:在Unity论坛也发帖子问了下,自定义的Behaviour里不能用内部类来录制变化,必须使用结构体。

感谢题主赵东玥@UWA问答社区提供了回答,欢迎大家转至社区交流:
https://answer.uwa4d.com/question/5ceacd47fd7bd665736194ec


今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
UWA学堂:edu.uwa4d.com
官方技术QQ群:793972859(原群已满员)