请选择 进入手机版 | 继续访问电脑版

查看: 190|回复: 0

[原创] 移动端大规模草渲染的实现

[复制链接]

6

主题

8

帖子

205

贡献

初级UU族—3级

Rank: 3Rank: 3

积分
205
发表于 2018-11-6 11:40:59 | 显示全部楼层 |阅读模式
先上最终效果:

https://v.qq.com/x/page/e0777z5hbqa.html

在群里经常看到草的问题,也会和他们一起讨论。结果现在不得不自己也开始做了,就想把一些东西给汇总一下, 然后看做出来效果如何。

按照做拼接地表的经验,我一开始就打算使用程序生成网格,通过尽量多的顶点去做,尽量不适用alpha test.原因在于使用了alphatest之后,early z就 无效了,但对于草来说,early z应该是特别重要的可以提升性能的点。
不过为了看效果,一开始还是用最简单的方式,用程序生成了一个大网格,放上贴图,看上去是这样:


首先在手机上测试了一下,法线帧率堪忧,如果视角再平一点,估计就卡了,目前这样子大概有8w个面,还可以接受。因为后面本来就有优化策略,所以打算还是继续先做效果。草肯定不会这么均匀,我先打乱下草的分布。
感觉有点差强人意,真实的草不应该是这么杂乱,而且打乱之后变稀疏了,就更丑了。调整下疏密度:

稍微舒服了点, 这里觉得颜色太单一,但又不想通过额外的参数来控制颜色变化,想了一下其实可以通过顶点色或者顶点索引。顶点索引需要在shader里多几步计算,考虑到性能,还是先往顶点色里塞,如果后续需要做其他东西,我觉得可以通过uv2来塞额外数据。

考虑到自然风,打算模仿一个dx 草下面的一个算法:

//用位置信息作为随机种子#ifdef GRASS_OBJECT_MODE        float3 randCalcPos = p[0].objectSpacePos;#else        float3 randCalcPos = oPos;#endif进一步获得xz的随机fixed randX = rand(randCalcPos.xz + 1000) * _Disorder * 2 - _Disorder;fixed randZ = rand(randCalcPos.xz - 1000) * _Disorder * 2 - _Disorder;//Random value from 2D value between 0 and 1inline float rand(float2 co){        return frac(sin(dot(co.xy, float2(12.9898,78.233))) * 43758.5453);}//If grass is looked at from the top, it should still look like grass#ifdef GRASS_TOP_VIEW_COMPENSATION        fixed topViewCompensation = 1 + pow(max(0, dot(viewDir, up)), 20) * 0.8;        width *= topViewCompensation;                        fixed2 windDir = wind(randCalcPos, fixed2(randX, randZ) * (topViewCompensation));#else        fixed2 windDir = wind(randCalcPos, fixed2(randX, randZ));#endifinline fixed2 wind(float3 pos, fixed2 offset){        float3 realPos = float3(pos.x * cos(_WindRotation) - pos.z * sin(_WindRotation), pos.y, pos.x * sin(_WindRotation) + pos.z * cos(_WindRotation));//这个是绕原点旋转之后的新坐标,具体推倒可以参考https://blog.csdn.net/u012138730/article/details/80320162        fixed2 windWaveStrength = _WindParams.x * sin(0.7f*windStrength(realPos)) * cos(0.15f*windStrength(realPos));        windWaveStrength += windRipple(realPos);        fixed2 wind = fixed2(windWaveStrength.x + offset.x, windWaveStrength.y + offset.y);        return fixed2(wind.x * cos(_WindRotation) - wind.y * sin(_WindRotation), wind.x * sin(_WindRotation) + wind.y * cos(_WindRotation));}//这个风力函数比较复杂,用四个正余弦函数弄出的一个波动效果inline fixed windStrength(float3 pos){        return pos.x + _Time.w*_WindParams.y + 5*cos(0.01f*pos.z + _Time.y*_WindParams.y * 0.2f) + 4*sin(0.05f*pos.z - _Time.y*_WindParams.y*0.15f) + 4*sin(0.2f*pos.z + _Time.y*_WindParams.y * 0.2f) + 2*cos(0.6f*pos.z - _Time.y*_WindParams.y*0.4f);}inline fixed windRippleStrength(float3 pos){        return sin(100*pos.x + _Time.y*_WindParams.w*3 + pos.z)*cos(10*pos.x + _Time.y*_WindParams.w*2 + pos.z*0.5f);}inline fixed2 windRipple(float3 pos){        return _WindParams.z * fixed2(windRippleStrength(pos), windRippleStrength(pos + float3(452, 0, 987)));}
//lod是这个草要分成几段,是曲面细分那边的内容for(fixed i = 1; i <= lod; i++){        fixed segment = i*invLod;        fixed sqrSegment = segment*segment;        //segment是草的长度的百分比,pos就是最终的实际高度        float3 pos = float3(up*segment*realHeight);        //xz要加上风所带来的影响,        pos.xz += windDir.xy * sqrSegment * stiffnessFactor;        //高度也要矫正        pos.y  -= length(windDir) * sqrSegment * 0.5f * stiffnessFactor;        fixed uvHeight = segment;        viewDir = normalize(rendererPos - pos);        fixed3 localUp = pos - lastPos;        //Simple grass has no texture, so the mesh has to look like a blade of grass        pIn.vertex =  float4((pos - width * groundRight * (1 - sqrSegment)).xyz, 1);        getNormals(localUp, lightDir, groundRight, /*out*/ pIn.normal, /*out*/ pIn.reflectionNormal);        triStream.Append(geomToFrag(pIn));        //Simple grass has no texture, so the mesh has to look like a blade of grass        pIn.vertex =  float4((pos + width * groundRight * (1 - sqrSegment)).xyz, 1);                getNormals(localUp, lightDir, groundRight, /*out*/ pIn.normal, /*out*/ pIn.reflectionNormal);                        triStream.Append(geomToFrag(pIn));        lastPos = pos;}
最终我只采纳了windStrength的部分,ripple部分在面数不够的情况下,草扭曲的样子不是很好看。

然后开始优化性能,首当其中的就是alpha test,因为为了风场动画,顶点本来就不少了,于是干脆直接用硬边。
性能从10帧跳到24帧,确实提高很多,硬边也还可以接受,然后发现另一个问题,就是手机上跑久了很抖,应该是数值过大引起的,需要将大数值取余。这样做了之后基本可以达到要求了。暂时先不考虑性能的进一步优化,因为我发现是我的手机太差了,换了台手机其实帧率可以到60帧。我更想处理的是和草的交互,因为图中这个球其实是压着草的,但因为没有交互,看上去很奇怪。

但在这之前还是需要处理一个问题,就是草是单面的,从其他方向看就会是一条线,一般是用billboard技术,但对我这种做法却不适用,因为billboard的重点是中心点,而我这么一大片网格,中心点和草是没啥关系的。于是我就换了一种做法,堆了两层草。用十字星的形式,这样的坏处是增加了面数,但好处也很明显,让整个草看上去更加自然。

接下来要处理交互了。

想了一下方案,首先草根据对应rt里的坐标读取像素,根据像素确定歪的方向,并且做歪曲。那么如何得到这张rt呢?
第一步是球的正面下压,我的想法是根据不同朝向,分别转成颜色值并显示出来,最终渲染到rt中。

shader代码如下:

v2f o;float4 vertex = v.vertex;o.vertex = UnityObjectToClipPos(v.vertex);o.pos = mul(unity_ObjectToWorld, v.vertex);float r = v.vertex.x + 0.5;float b = v.vertex.z + 0.5;float len = length(v.vertex.xz * 2);o.color.r = r;o.color.g = pow(1 - len, 1);o.color.b = b;o.color.a = 1;return o;



然后是运动轨迹,原理一样,只不过需要通过运动动态创建网格,效果如下:



这样rt就是用摄像机从正上方去看它,一切都很完美了。

实际测试的时候发现一个大问题,就是如果草只有一部分顶点被压住,那么草会扭曲成很难看的样子,解决方案是把草的位置浓缩成一个点,草上面的所有顶点都用浓缩的这个点来判断,这样草的位置就一致了。

                    v2f vert(appdata v, uint vid : SV_VertexID)                    {                        v2f o;                        float index = floor(vid / ((_GrassSeg + 1) * 2));                        float grid = _GrassRange / _GrassNum;                        float row = floor(index / _GrassNum);                        float col = index % _GrassNum;                        float4 objectPos = float4(-_GrassRange / 2 + row * grid, 0, -_GrassRange / 2 + col * grid, 1);                        float4 worldPos1 = mul(unity_ObjectToWorld, objectPos);                        float4 vertex = v.vertex;                                                                                        float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;                        float3 randCalcPos = worldPos;                        float2 windDir = wind(randCalcPos);                                                        float4 grassuv1 = mul(GrassMatrix, worldPos1);                        float2 grassuv2 = grassuv1.xy / grassuv1.w * 0.5 + 0.5;#if UNITY_UV_STARTS_AT_TOP                        grassuv2.y = 1 - grassuv2.y;#endif                        float4 n = tex2Dlod(_GrassTex, float4(grassuv2, 0, 0));                        n.xz = (n.xz - 0.5) * 2;                        float2 off = (windDir.xy) * pow(v.vertex.y * 2, 2);// +n.xyz;                        worldPos.y -= 10 * dot(off, off);                        float2 newOff = off * (1 - n.g) + normalize(n.xz) * v.vertex.y * n.g * 0.6;                        worldPos.y *= (1 - n.g);                        //off = float2(-0.3, 0) * v.vertex.y;                        worldPos.xz += newOff;                                                        o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1));                        //o.pos = UnityObjectToClipPos(vertex);                        o.uv = v.uv;                        half3 worldNormal = UnityObjectToWorldNormal(v.normal);                        half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));                        o.diff = nl * _LightColor0;                        o.diff *= v.color;                        o.diff.rgb += +ShadeSH9(half4(worldNormal, 1));                        TRANSFER_SHADOW(o)                        return o;                }
最后是插件地址:

https://www.assetstore.unity3d.com/en/?stay#!/content/132241
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表