开启辅助访问
 找回密码
 注册帐号

扫一扫,访问微社区

开发者专栏

关注:2005

当前位置:游戏蛮牛 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在游戏蛮牛原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 2499|回复: 173

[慕容小匹夫] 利用GPU实现无尽草地的实时渲染

  [复制链接]  [移动端链接]
排名
1877
昨日变化

44

主题

287

帖子

2952

积分

Rank: 9Rank: 9Rank: 9

UID
44527
好友
55
蛮牛币
2460
威望
0
注册时间
2014-9-12
在线时间
552 小时
最后登录
2017-10-26

专栏作家活力之星认证开发者

发表于 2017-10-9 10:01:08 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
0x00 前言
在游戏中展现一个写实的田园场景时,草地的渲染是必不可少的,而一提到高效率的渲染草地,很多人都会想起GPU Gems第七章
《Chapter 7. Rendering Countless Blades of Waving Grass》中所提到的方案。
现在国内很多号称“次世代”的手游甚至是一些端游仍或多或少的采用了这种方案。但是本文不会为这个方案着墨过多,相反,接下来的大部分内容是关于如何利用Geometry Shader在GPU生成新的独立草体的。
0x01 一个简单的星型
传统的方式,即将模型数据从CPU传递给GPU,GPU再根据这些数据进行渲染的方式在渲染大规模的草体时,往往会忽略单个草体的模型细节。因为单个草体的建模如果过于细致,则渲染大片的草地就需要传递很多多边形,从而造成性能的下降。
因此,一个渲染大片草地的方案往往需要满足以下条件:
  • 单个草的多边形不能过多,最好一棵草只用一个quad来表示
  • 从不同的角度观察,草都必须显得密集
  • 草的排布不能过于规则,否则会不自然
综上,渲染草体时的经典结构——星形就出现了。

这样,简单的星形结构既满足了单棵草的面数很低同时也兼顾了从不同角度观察也能够显得密集。 而让草随风而动也很简单,只需要根据顶点的uv信息找出上面的几个顶点,按照自己规则让顶点移动就可以了。
[AppleScript] 纯文本查看 复制代码
if (o.uv.y > 0.5)
{
    float4 translationPos =
        float4(sin(_Time.x * _TimeFactor * Pi ), 0, sin(_Time.y * _TimeFactor * Pi ), 0);
    v.vertex += translationPos * _StrengthFactor;
}
现在很多游戏在渲染草地时仍然使用了这种结构。

(图片来自:九州天空城3D)
(图片来自:剑网3)
但是,各位也都看到了,这种方式虽然简单,但是却并不自然,从上方俯视的时候各个面片也能看到清清楚楚,因此这种方式并不是我想要的。
0x02 更真实的草叶
我想要的效果是能够大规模实时渲染,并且每一颗草的叶片都能够随风摇曳的更真实自然的效果。在这方面,业内早有一些探索,例如Siggraph2006上的《Rendering Grass Terrains in
Real-Time with Dynamic Lighting》,以及Edward Lee的论文《REALISTIC REAL-TIME GRASS RENDERING》。
本文主要按照Edward Lee的论文方式在Unity中实现GPU生成无尽草地随风摇曳的效果。
这里,我主要用到了Direct3D 10之后新引入的Geometry Shader.aspx)来实现在GPU上创建单独草体叶片的逻辑。每个叶片根据LOD有3种组成方式,分别需要1个quad、3个quad以及5个quad。

(图片来自:Edward Lee)
而每颗草的位置则由CPU来随机决定,由于GS的输入是一个图元(point、line或triangle)而非顶点,所以我们在CPU中需要根据随机的位置创建point类型的图元作为这棵草的根位置。
ok,接下来就在GPU上通过一个根位置来制作草的叶子。
[AppleScript] 纯文本查看 复制代码
    [maxvertexcount(40)]
    void geom(point v2g points[1], inout TriangleStream<g2f> triStream)
    {

        float4 root = points[0].pos;
虽然位置是随机的,但是我们显然也希望叶子本身的高度和宽度也存在一些随机。
[AppleScript] 纯文本查看 复制代码
        float random = sin(UNITY_HALF_PI * frac(root.x) + UNITY_HALF_PI * frac(root.z));
        _Width = _Width + (random / 50);
        _Height = _Height +(random / 5);
设置好叶子的属性之后,我们就可以根据这些属性来创建新的顶点模拟叶子的样子了。
画一个简图各位可以看到,组成一颗草的叶子需要12个不同的顶点,但是由于这里没有用index,所以最后总共要输出30个顶点来组成5个quad。
而根据这幅简图,我们还可以很方便的根据根的位置计算各个顶点的位置
同时,还能发现偶数顶点对应的uv坐标是(0,v),而奇数顶点对应的uv坐标都是(1,v)——这里的v是uv坐标中的v——因此,我们又能很轻松的计算出各个顶点对应的uv坐标了。
最后,如果我们要计算实时光,则还需要获取顶点的法线信息,这里简单起见统一为(0, 0, 1)。
[AppleScript] 纯文本查看 复制代码
        for (uint i = 0; i < vertexCount; i++)
        {
            v[i].norm = float3(0, 0, 1);

            if (fmod(i , 2) == 0)
            { 
                v[i].pos = float4(root.x - _Width , root.y + currentVertexHeight, root.z, 1);
                v[i].uv = float2(0, currentV);
            }
            else
            { 
                v[i].pos = float4(root.x + _Width , root.y + currentVertexHeight, root.z, 1);
                v[i].uv = float2(1, currentV);

                currentV += offsetV;
                currentVertexHeight = currentV * _Height;
            }

            v[i].pos = UnityObjectToClipPos(v[i].pos);

        }

这样,一个叶片的网格就在GPU上创建完成了。
接下来,我们需要处理一下草叶的纹理来渲染出符合我们预期的叶片。这里我用到了GPU Gem那篇文章中的草丛纹理的处理方法:
即叶片的颜色可以只用一个张单独表示叶片颜色的纹理来处理,比如我用的这张纹理:

而草体的具体轮廓则靠另一张纹理提供。但是这里没有使用alpha blend,而是使用了alpha to coverage,因为在处理重重叠叠的草叶时blend会有一些显示顺序上的问题,至于如何使用alpha to coverage各位可以参考SL-Blend。
[AppleScript] 纯文本查看 复制代码
        SubShader
            Tags{ "Queue" = "AlphaTest" "RenderType" = "TransparentCutout" "IgnoreProjector" = "True" }

            Pass
                AlphaToMask On
所以,现在我们只需要在fs内简单的取样输出就可以了。
[AppleScript] 纯文本查看 复制代码
    half4 frag(g2f IN) : COLOR
    {
        fixed4 color = tex2D(_MainTex, IN.uv);
        fixed4 alpha = tex2D(_AlphaTex, (IN.uv));

        return float4(color.rgb, alpha.g);
    }
0x03 生成覆盖地面的无尽草地
有了叶子之后,我们就可以考虑如何生成地形以及地面上覆盖的草了。为了地面的起伏轮廓自然真实,我们可以根据一张高度图来动态创建地面的网格。
由于Unity的网格顶点上限是65000,因此我决定让地面网格的尺寸为250 * 250:
[AppleScript] 纯文本查看 复制代码
    for (int i = 0; i < 250; i++)
    {
        for (int j = 0; j < 250; j++)
        {
            verts.Add(new Vector3(i, heightMap.GetPixel(i, j).grayscale * 5 , j));
            if (i == 0 || j == 0) continue;
            tris.Add(250 * i + j); 
            tris.Add(250 * i + j - 1);
            tris.Add(250 * (i - 1) + j - 1);
            tris.Add(250 * (i - 1) + j - 1);
            tris.Add(250 * (i - 1) + j);
            tris.Add(250 * i + j);
        }
    }        
    ...
    Mesh m = new Mesh();
    m.vertices = verts.ToArray(); 
    m.uv = uvs;
    m.triangles = tris.ToArray();
这样,一个自然而真实地面网格就创建好了。

之后就来生铺草吧。所谓的铺草无非就是我们需要生成一些顶点,作为草叶的根位置传入之前完成的GS。需要说明的是,由于草的密度要足够大,因此不止需要一个草地的mesh,例如我们要种200,000棵草的话就需要3个草地mesh。另外还要说明的一点,也是要吐槽Unity的地方就在于Unity的mesh实现默认是triangle,而非point(参考Invoking Geometry Shader for every vertex of a mesh)。因此创建记录草根位置的mesh的方法和之前创建地面稍有不同。
[AppleScript] 纯文本查看 复制代码
        m.vertices = verts.ToArray();
        m.SetIndices(indices,MeshTopology.Points, 0);

        grassLayer = new GameObject("grassLayer"); 
        mf = grassLayer.AddComponent<MeshFilter>();
        grassLayer.AddComponent<MeshRenderer>();
创建好之后,可以看到草根的位置随机的分布在地面上,数量有上百万个。

把我们的shader应用于记录草根位置的mesh上。
wow,我们的草地出现了。
0x03 风的模拟
呆立的草虽然看上去比之前的纸片草好看了很多,但是静止而整齐的叶子毕竟还是很不自然。因此,我们要让草动起来也就是模拟风的效果。
思路仍然是利用三角函数来让草叶摇摆起来,同时根据草的根位置为三角函数提供初始相位然后再增加一些随机性在里面让效果更自然。
[AppleScript] 纯文本查看 复制代码
        ...伪代码
        wind.x += sin(_Time.x + root.x);
        wind *= random;
        ...
但是针对目前每一颗草都有独立的叶片网格,为了更加逼真的模拟风的效果,显然不同的叶片的不同部位受到风的影响是不同的。
距离叶子的顶端越近,则受到风的影响就越大。

因此在GS生成新顶点的逻辑中,增加风对顶点位置的影响,越高的顶点被影响的程度越大,这样一个更真实的无尽草地效果就实现了。

这个demo的代码各位可以在这里获取:
游客,如果您要查看本帖隐藏内容请回复
当然,这不是手机上使用的技术,并且作为一个演示demo我并没有做过多的优化(不过在我的本子上跑起来还是很流畅)。
而且和我文章中的演示相比,要简化一些。
ref:
【1】《Chapter 7. Rendering Countless Blades of Waving Grass》
【2】《Rendering Grass Terrains in
Real-Time with Dynamic Lighting》
【3】《REALISTIC REAL-TIME GRASS RENDERING》
【4】《Programming Guide for Direct3D 11》.aspx)
-EOF-
最后打个广告,欢迎支持我的书《Unity 3D脚本编程》
https://item.jd.com/12035114.html



评分

参与人数 2鲜花 +10 收起 理由
alexander_ly + 5 很给力!
mmuu1987 + 5 很给力!

查看全部评分


回复

使用道具 举报

6蛮牛粉丝
1145/1500
排名
3410
昨日变化
16

8

主题

224

帖子

1145

积分

Rank: 6Rank: 6Rank: 6

UID
66029
好友
0
蛮牛币
419
威望
0
注册时间
2015-1-5
在线时间
569 小时
最后登录
2017-12-12
发表于 2017-10-9 10:10:00 | 显示全部楼层
楼主厉害啊

回复

使用道具 举报

5熟悉之中
884/1000
排名
2546
昨日变化

0

主题

54

帖子

884

积分

Rank: 5Rank: 5

UID
90059
好友
0
蛮牛币
803
威望
0
注册时间
2015-4-7
在线时间
373 小时
最后登录
2017-12-9
发表于 2017-10-9 10:27:08 | 显示全部楼层
谢谢分享!!!!!
[发帖际遇]: smj823 捡了钱没交公 蛮牛币 降了 1 . 幸运榜 / 衰神榜

回复

使用道具 举报

5熟悉之中
559/1000
排名
7359
昨日变化
80

1

主题

132

帖子

559

积分

Rank: 5Rank: 5

UID
56504
好友
0
蛮牛币
412
威望
0
注册时间
2014-11-19
在线时间
304 小时
最后登录
2017-12-11
发表于 2017-10-9 10:29:57 | 显示全部楼层
楼主简直6到飞起

回复 支持 反对

使用道具 举报

4四处流浪
356/500
排名
10371
昨日变化
12

0

主题

135

帖子

356

积分

Rank: 4

UID
98962
好友
0
蛮牛币
259
威望
0
注册时间
2015-5-9
在线时间
135 小时
最后登录
2017-12-12
发表于 2017-10-9 10:36:56 | 显示全部楼层
谢谢分享

回复

使用道具 举报

7日久生情
2868/5000
排名
410
昨日变化

33

主题

323

帖子

2868

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
115929
好友
12
蛮牛币
9648
威望
0
注册时间
2015-7-30
在线时间
1150 小时
最后登录
2017-12-12
发表于 2017-10-9 10:38:19 | 显示全部楼层
好不错不错
[发帖际遇]: hh1551229943 发帖时在路边捡到 1 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复

使用道具 举报

7日久生情
1571/5000
排名
1205
昨日变化
2

1

主题

222

帖子

1571

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
27754
好友
0
蛮牛币
1981
威望
0
注册时间
2014-6-2
在线时间
544 小时
最后登录
2017-12-12
发表于 2017-10-9 10:40:20 | 显示全部楼层
功能太cool了。学习

回复 支持 反对

使用道具 举报

7日久生情
1542/5000
排名
754
昨日变化
4

0

主题

95

帖子

1542

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
45780
好友
0
蛮牛币
1387
威望
0
注册时间
2014-9-19
在线时间
397 小时
最后登录
2017-12-11
发表于 2017-10-9 10:57:01 | 显示全部楼层
可惜无法在手机上使用

回复 支持 反对

使用道具 举报

3偶尔光临
221/300
排名
8096
昨日变化
5

0

主题

33

帖子

221

积分

Rank: 3Rank: 3Rank: 3

UID
130351
好友
0
蛮牛币
442
威望
0
注册时间
2015-11-29
在线时间
72 小时
最后登录
2017-12-11
发表于 2017-10-9 10:59:04 | 显示全部楼层
厉害,先收藏了
[发帖际遇]: 一个袋子砸在了 bjay90 头上,bjay90 赚了 1 蛮牛币. 幸运榜 / 衰神榜

回复

使用道具 举报

5熟悉之中
779/1000
排名
3475
昨日变化
19

1

主题

251

帖子

779

积分

Rank: 5Rank: 5

UID
206337
好友
2
蛮牛币
2783
威望
0
注册时间
2017-6-5
在线时间
189 小时
最后登录
2017-12-12
发表于 2017-10-9 11:18:26 | 显示全部楼层
看见shader代码就头疼...

回复 支持 反对

使用道具 举报

5熟悉之中
660/1000
排名
5037
昨日变化
43

5

主题

163

帖子

660

积分

Rank: 5Rank: 5

UID
123828
好友
3
蛮牛币
952
威望
0
注册时间
2015-9-24
在线时间
244 小时
最后登录
2017-12-12
发表于 2017-10-9 12:22:57 | 显示全部楼层
利用GPU实现无尽草地的实时渲染 学习
[发帖际遇]: ll4080333 发帖时在路边捡到 2 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

回复 支持 反对

使用道具 举报

5熟悉之中
733/1000
排名
3756
昨日变化
4

12

主题

69

帖子

733

积分

Rank: 5Rank: 5

UID
97961
好友
3
蛮牛币
819
威望
0
注册时间
2015-5-6
在线时间
337 小时
最后登录
2017-12-8
发表于 2017-10-9 12:24:28 | 显示全部楼层
谢谢大神
[发帖际遇]: 一个袋子砸在了 泗阳该歇大锅 头上,泗阳该歇大锅 赚了 1 蛮牛币. 幸运榜 / 衰神榜

回复

使用道具 举报

2初来乍到
138/150
排名
17940
昨日变化
18

0

主题

23

帖子

138

积分

Rank: 2Rank: 2

UID
37967
好友
0
蛮牛币
272
威望
0
注册时间
2014-8-4
在线时间
91 小时
最后登录
2017-12-11
发表于 2017-10-9 12:59:41 | 显示全部楼层
这个也提供了一种与众不同的思路

回复 支持 反对

使用道具 举报

0

主题

5

帖子

5

积分

Rank: 1

UID
247608
好友
0
蛮牛币
6
威望
0
注册时间
2017-10-9
在线时间
3 小时
最后登录
2017-10-17
发表于 2017-10-9 13:12:17 | 显示全部楼层
6666666666

回复

使用道具 举报

7日久生情
2917/5000
排名
111
昨日变化

0

主题

83

帖子

2917

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
23497
好友
6
蛮牛币
6239
威望
0
注册时间
2014-4-30
在线时间
800 小时
最后登录
2017-12-12
发表于 2017-10-9 13:21:59 | 显示全部楼层
谢谢分享,太棒啦!!!

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

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