前言

  • 今天要给大家分享的主题是现在游戏开发中必备的一项游戏效果:游戏中的云渲染
  • 现在大部分的游戏,不管是主机、PC还是手机游戏都在往3A的方向发展,所以现在的游戏在云的渲染上很少会采用传统的渲染方式
  • 大家可以想一想:平时在玩游戏时是否看到过一些传统的渲染方式、它用了什么样的技术来实现?知道的同学,可以把你的答案发在评论区
  • 今天要带给大家的是一种不太一样的渲染方式,是现在很多游戏大作中必备的体积云渲染方式,比如地平线系列、大表哥二,都采用了这样的渲染技术,可以说体积云是主机游戏标配的一项渲染技术
  • 下面先从最早年代的云渲染技术开始,来看一下传统的云渲染方式是什么样的,现在的渲染方式又是什么样的

版权声明

  • 本文为“优梦创客”原创文章,您可以自由转载,但必须加入完整的版权声明
  • 更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
  • 点赞、关注、分享可免费获得配套学习资源
  • 点击观看完整视频

文章目录

  • 前言
  • 版权声明
  • 机构介绍
  • 云的渲染方式
    • 天空盒
    • 公告板云
    • 片面穿插云
    • 视差云
    • 模型云
    • Ray March 体积云
  • 体积云的特点
  • 议题
  • 体积云渲染时的七个步骤
    • 云的噪点图生成
    • 屏幕后处理
    • 光线追踪
    • 光线步进
    • 大气光线散射
    • 云的形态控制
    • 最终效果演示
  • 小结
  • 思考
  • 写在最后

机构介绍

  • 优梦创客:我们的目标是帮助各位同学创作一款大家爱玩的游戏
  • 我是今天课程的主讲老师雷蒙德。在行业内有14年的从业经验,做过程序员,技术经理,教学经理,目前的是我们优梦创客的创始人
  • 下面是我曾经参与制作过的一些成功的项目作品,包括有《传奇世界》,《神迹I/II》,《疯狂赛车》,《Infinity3D引擎》,《剑网三》等

云的渲染方式

天空盒

  • 天空盒只能算是滥竽充数的云渲染方式,它也是当打开Unity以后默认会采用的性能最节省但效果也是最差的渲染方式
  • 其实在网络游戏还没有流行的年代就已经有天空盒技术了,那么天空盒是什么呢?
    • 它就是把一个摄像机放在整个场景中间,然后往上、下、左、右、前、后六个方向拍六张图片,来构成一个盒子形状
    • 整个游戏的天空都是被包围在这个盒子里的,这就叫天空盒技术
  • 它的技术实现很简单,只要拍六张图片,然后把贴图渲染出来就可以了,这种方式是性能最节省的方式,可能有同学会感到很奇怪,但它就是这么简单,只要六张图片就可以构成这样的天空盒

公告板云

  • 左边的图片是游戏画面,可以看到游戏画面里的天空看上去比较平淡,比如云,真实的云在天气晴朗时,会有比较亮的地方,也有比较暗的地方,而且云本身也是带阴影的
  • 但这个云看上去就比较平淡,因为这种方式也比较简单,就是在整个场景上做了一个穹顶(如右图),它可能就是用几个面片把天空罩起来,然后在穹顶上贴一张天空的云层图片

片面穿插云

  • 第三种方式叫做面片穿插云,面片穿插云的特点也是简单,但它比使用前面的这些方式做出来的效果看上去要真实一点
  • 如右图,面片穿插云不再是一个单一平面,而是由很多个平面来组合成云的形状,然后在不同的面片上绘制不同的云贴图,由于这个贴图是半透明的,所以面片穿插云看上去还是会有一定的立体感
  • 这种方式在早期的游戏,以及一些手机游戏里会用的比较多,因为手机游戏对性能的要求还是比较高的,所以采用面片穿插云的情况会多一些

视差云

  • 这也是我们课程里本身就有的效果,叫做云海效果,它是基于视差贴图的技术,在视角移动时可以看到云的体积感,因为视差贴图就是用一张图片来表示视差,然后渲染出来
  • 虽然是一个平面,但它能渲染出让你感觉是高低不同的感觉,如果你玩过《光遇》这样的游戏,就会发现:这样的云海只适合出现在我们的脚底,可以用它做山下的云海效果,但不太适合做天空上的云

模型云

  • 模型云就相对要更真实一些,它是把真实的模型放在你的头顶上来表示云的形状,它的实现方式有几种:

    • 模型沿着法线外扩,来形成多个云的云层
    • 采用曲面细分的技术
    • 上图中最上面的图就是使用法线外扩来实现的云,下面的图使用的是曲面细分
  • 这种方式的优点是:云的形状比较简单方便
  • 缺点:跟前面的技术一样,由于它本质上并不是真正的带有体积的云,所以是不能在云里穿梭的

Ray March 体积云

  • 接下来就是今天课程的主题了:体积云的实现,下面先来看看体积云应该如何实现的,另外今天还会讲到一种大家平时不太会接触到,或者比较难理解的一种TA进阶的技术:Ray March体积云技术,Ray March的意思是光线步进,它跟光线追踪是比较像的
  • 上图中的游戏画面,这应该是地平线系列的游戏,虽然我这边清晰度有限,但仍然可以清晰地看到云的层次感

体积云的特点

  • 在学习体积云技术之前需要先了解一下体积云有什么样的特点
  • 上图中的左图是体积云,右图也是体积云,但右边的云平时叫它火烧云,体积云最明显的特点就像它的名字所描述的,它是有体积的,而不是平面的
  • 当拿到云渲染任务时,首先就要思考云有什么特点:
    • 云并不是简单的匀速运动,云是分层的,其中会有快速运动的分层和慢速运动的分层
    • 在渲染时要注意云的形状和云内部的细节
  • 这是渲染时要考虑的两个点,所谓的形状就是云的外轮廓是什么样的,所谓的细节就是说云内部的浓度在不同的位置是不一样的,要有所区分
  • 另外由于云渲染的开销是比较大的,所以会在不同的部位去对云采用不同的分辨率来进行渲染
  • 以上就是体积云包括它的技术实现上的一些特点,当然这样讲可能是比较概括的,具体如何实现会在下面讲到

议题

  • 体积云的噪点图生成

    • 体积云里的每朵云的形状都是不一样的,那么怎样生成不同形状的体积云呢?这就需要依赖于随机生成的一些图形噪点
    • 之后也会讲到如何编写噪点图生成工具
  • 屏幕后处理
    • 在项目中会使用到光线追踪和光线步进技术,光线追踪和光线步进并不是传统的渲染方式:“放置一个模型,然后把材质效果赋予给这个模型,这时模型就会按照材质效果来渲染”,它不是这样的
    • 光线追踪和光线步进是依赖于屏幕后处理技术的,也就是说它实际上是在一个四边形面片上来进行云的绘制,这种技术还是非常特别的,所以今天会先给大家简单介绍一下什么是屏幕后处理
  • 光线追踪一个云盒
    • 在掌握了屏幕后处理以后就可以用光线追踪的技术来渲染一个云盒了
    • 什么叫云盒呢?就是说我的云是有一定的体积,一定的范围,我们会在屏幕后处理阶段把云渲染在盒子范围内
  • 光线步进云的形状
    • 这里会讲到使用Ray Marching(光线步进)技术,这种技术的文献比较少,而且比较晦涩难懂,所以今天我会用比较通俗易懂的方式来给大家介绍如何用光线步进技术来把云的形状渲染出来
  • 大气光线散射原理
    • 由于云是在空气当中的,而且离地面也比较远,所以它会受到大气的影响而产生光线散射的效果,所以应该怎样把光线散射表现在云的渲染中呢?
    • 有同学可能不太清楚实现光线散射究竟能得到什么样的效果?
      • 由于光线散射的原因,靠近太阳的地方,云是比较亮的;而远离太阳的地方,云是比较暗的
      • 这就是大气光线散射,通俗来说就是在最终效果上产生了影响
  • 云的形态控制
    • 体积云与传统云最大的一个区别就是:”体积云的形态是完全可控的“,这也是为什么很多的3A游戏都会采用体积云的原因
    • 这里会讲解体积云的形态控制,包括形态的编辑
  • 最终效果演示
    • 最后会展示最终实现出的体积云效果

体积云渲染时的七个步骤

云的噪点图生成

  • 在第一个步骤里需要生成云的噪点图,很多同学在网上找的资料只会告诉你:

    • 第一步生成噪点图,第二步干什么、第三步干什么,第四步干什么,但他并没有说明噪点图生成是为了什么
  • 在这里就说明一下为什么要生成噪点图:生成噪点图就是为了表示云的形状

  • 噪点图生成的第一个步会生成一张平面的纹理图(如左图),噪点图上比较亮的部分就是云层上云比较集中的部分,比较暗的部分就是云层没有那么丰富的位置

  • 那么噪点图要怎么样生成呢?(如右图)

    • 首先我会在图片上随机生成一些小红点的位置,这对于学过一点程序的同学来说不是什么难事,就是调用一下C#或者是Unity提供的随机数函数就能生成一些小红点位置
    • 比如图片宽度和高度是512乘512,那么生成一个0到511之间的随机数就可以了
  • 随机数生成完以后,图片上的每一个像素格子都要判断靠它距离最近的那个小红点是谁

  • 比如上图中距离左上角红格子最近的红点是哪一个呢?肉眼看一下应该是右下方的红点,然后再来看一下这个红格子跟红点的距离是多少,距离越近,就把它渲染的越黑,也就是使用距离值来代表它的亮度

  • 假设它们的距离是0.6,比红点右下方的红格子远一点,红点与右下方红格子的距离可以假设为0.2,因为它距离红点较近,所以它在图像上就会渲染的比较暗,而左上角的格子因为距离比较远,所以就渲染的比较亮

  • 这种随机生成若干个随机点,然后每一个像素格子找到靠它最近的随机点,靠随机点的距离来代表这个格子的亮度的算法叫Worley Noise

  • 有同学可能会说:”右边的图跟最终要生成的形态好像不太一样”

    • 可以把中间是黑的,边缘是白的图做一下亮度值的反转
    • 黑色在图形学里用0表示,白色在图形学里用1表示,所以只要用1减去当前像素的颜色,就能把它的黑和白反转了
  • 这张黑色居多的图片经过反转以后,就会形成左边看到的中间是白的,越靠近红点越白,越远离红点越黑的图片

  • 然后再进一步的把这张图像的分辨率提高,

    • 比如说右边这张图它可能是32乘32的,现在我把他提高到1024乘以1024,这样就形成了一张比较精细的Worley Noise噪点图
  • 这样第一个基础目标就达成了,但由于云是在3D空间里的,而现在生成的噪点图是平面的,所以现在的噪点图只适合用于一些比较假的平面云效果,可以用这个噪点图生成一些平面云来渲染,但这个效果并不是我们想要的,所以下面就要生成带体积的噪点图

  • 要生成带体积的噪点图,可以先在平面上生成噪点图,然后再在高度上生成噪点图,这样就会形成一个立体噪点图的形态

  • 你可以这样想象:云是在左图中的立方体盒子范围之内的,然后这个云的每一个噪点图像都是128乘以128的,那么这时的高度也是128,那你就可以把这个云理解成是我在每一个平面上都把它切片,切成了128片

  • 那么怎样形成这样的一个128乘以128乘以128的立体噪点图呢?步骤如下

    • 首先在平面上生成噪点图,而在平面上生成噪点图的开销其实是挺大的,前面也讲过:噪点图的清晰度越高,图片的尺寸就越大,假设图片尺寸是1024乘以1024,那么它的生成开销可能需要一分钟甚至几分钟时间才能生成一张照片
    • 那有没有办法让它的速度变得更快一些呢?是有的办法的,如右图,可以只生成中间这一小块噪点图,然后再把它重复九次,这样它的范围就扩大了,而我的代价只不过是把它复制九次
    • 但是大家应该会发现:复制出的这张平面噪点图是有点问题的,复制出的平面噪点图是不连续的,它的缝隙非常明显,所以接下来我们要解决这个问题
    • 其实只要把这张图片镜像一下就行了,将这张图片进行水平和垂直翻转,就能让平面噪点图变成连续的状态,并且可以把渲染开销降低到原来的1/9
    • 接着再在高度上生成跟平面噪点图分辨率一样的若干个切片,比如平面噪点图是64乘64,那么在高度上再形成64个这样的图片切片就形成了3D噪点图
    • 前面讲过:由于噪点图生成的效率比较低,所以不能一下生成很大的噪点图,但可以用平面生成噪点图的优化方法来优化立体噪点图的生成
    • 只要把其中一个3D噪点图重复26次就可以了,所以只要再重复26次,并进行翻转,就能形成在空间上连续的噪点图,这是第一个步骤,下面进入到具体的代码实现
  • 前面已经讲过了:最基本的步骤是要生成平面噪点图,那么如何生成Worley Noise噪点图呢?其实这在刚才也讲过了

    • 一,随机确定一些中心点
    • 二,计算每个像素到中心点的距离
  • 但由于随便找一个像素点,然后把这个点与其他中心点的距离全部算一遍的效率太低了,并且中心点越多,它的效率就越低,所以要进行优化

  • 现在的问题是中心点太多该怎么办?

    • 可以从算法的层面来优化,答案就在右图中,大家可以思考一下:

      • 怎样优化噪点图生成
    • 如果同学们没有图形学基础,那你可以先大致看一下体积云的生成思路,之后会在系统课程里进一步的讲解,你可以跟着我们的课程系统学习
    • 在学习中思路其实是最重要的,如果你没有思路,那么你在做项目和一些具体的图形效果时就会遇到很多问题
  • 算法层面的优化方法可以参考一下我们的《Untiy小白的TA之路》,这是我们在算法实现上要解决的一个点

  • 第二个点就是:

    • 如果右边的3D噪点图的每一张图片都是64乘64的,那么你能算出来这张噪点图一共有多少个像素点吗?
    • 这个开销是非常惊人的,无论是从存储的开销上,还是从将来要去做的噪点采样上来说,它的性能开销都是非常大的
  • 那么右边噪点图的像素数量是多少呢?就在这张图的正下方,它是64乘64乘64的像素数量,这个存储开销是比较大的,如果现在再开启一下MipMap,那么它的噪点的数量会更多

  • 所以问题就是我们如何在Unity中生成这样的噪点图呢?如何用Worley Noise算法来生成噪点图呢?像素数量太多如何优化呢?

  • 优化刚才讲过了,只要用九宫格算法就可以了,但是哪怕用九宫格去生成其中一个格子,它也是有不少的时间开销的,如果不去优化,那它的性能和时间开销是非常恐怖的,比如优化以后需要一分钟时间去生成,那不优化就可能需要五分钟甚至十分钟来完成噪点图的生成

  • 如果在美术制作的流程中为了预览云的形状好不好,而花费五分钟或者十分钟的等待时间,这样对于整个工作流程影响太大

  • 所以为了解决这些问题我们就可以开启Unity Shader的Compute Shader来进行并行运算,这个Compute Shader可以利用我们的CPU和GPU来高效的调动整个噪点图的生成

  • 具体怎样做才能使效率能够达到更高,大家可以关注我们的《Unity小白的TA之路》

  • 上图是我们的噪点图生成器,在噪点图生成器中可以控制云产生四种细节,分别放在RGBA4个通道里、还可以显示所有通道产生的噪点、调整云产生的噪点数量、把云的黑与白做一个反转、看到每个切片之间是如何过渡的,这就是云的噪点生成器,所以我们的体积云是完全可控的

  • 前面说过云是由快速移动的云层和慢速移动的云层构成的,有外观形状和内部细节,由低分、高分的云细节来叠加而成,并且我们还需要生成的基础的Perlin噪点来跟Worley Noise噪点做一个结合

  • 以上是在做第一个步骤时要实现的功能,这些功能会涉及到一些C#编辑器脚本的开发、以及Unity编辑器拓展开发

  • 在第一个步骤完成以后就已经能够生成云的形状了,那如何按照云的噪点来把云渲染出来呢?这里就需要使用到光线追踪技术来把云渲染到一个盒子范围里

  • 但在光线追踪技术里,我们不是在场景里的一个盒子范围内根据噪点图来渲染云,而是在屏幕后处理阶段虚拟一个盒子,然后在这个盒子里渲染

  • 这种技术在图形学里叫做SDF算法,根据SDF算法来确定包围盒的边界,然后把云渲染在这个包围盒里

    • 那么如何确定盒子范围呢?

      • 需要用到光线追踪
    • 确定了盒子范围以后,如何在盒子里把云画出来呢?
      • 需要用到光线步进技术
  • 接下来会介绍屏幕后处理的基本原理,即使你是小白,没有任何的基础,也可以了解到什么是屏幕后处理,之后会讲到如何基于屏幕后处理去做光线追踪和光线步进

屏幕后处理

  • 当然了,如果展开来讲图形渲染管线、Unity图形引擎的图形处理原理,那它的篇幅就太长了,因为今天的主题是渲染体积云,所以只需要了解屏幕后处理是干什么的就可以了
  • 屏幕后处理就是把整个屏幕看成是一个四边形,然后整个屏幕的内容都已经渲染在一张纹理贴图上面了,这张纹理贴图就是左图代码中的_MianTex,然后再传入这张图片的每一个像素点的uv坐标(也就是代码中的input.uv),它就能够对屏幕四边形进行采样,然后把屏幕四边形画在场景里
  • 可以看到:我在代码里写了一个片元着色器,什么是片元着色器呢?就是说这个着色器程序是作用在每一个像素点上面的,每一个像素点在渲染时都会调用处理fragment的片元着色器来进行着色
  • 在这段程序中如果不写下面的return 1 - col,或者写的时候不写1 - ,那么渲染出来图像就是右上角这幅图像,为什么会这样呢?
    • 因为Unity会把整个屏幕渲染的结果传到主纹理上面,后面的uv坐标可以理解成每一个像素点的坐标,那么在传入每一个像素点的坐标时,图形显卡就能从整个屏幕四边形这张图片上取得指定的坐标位置的颜色值,然后把它输出到颜色缓冲区,这样就能看到上面的图像
  • 屏幕后处理的具体细节今天不涉及,先大致了解它的结构,我可以在屏幕上做任何的后期处理,比如只要在刚才那个语句中加上1 - ,就能把所有的颜色都做一下翻转,得到负片的效果,负片就是手机照相机里一种图片效果,实现起来很简单,就是把照片渲染效果用1 -,把所有的颜色都做一个翻转,就能得到负片的效果
  • 那么为什么要在这里讲解屏幕后处理呢?因为接下来要做光线追踪

  • 如果想要对场景里的物件做一个勾边效果,那么就可以写右边用黄色圈起来的这段代码,这段代码可能比你在网上看到的大部分代码都要简单,它只不过是把像素点坐标做一个偏移,然后采样
  • 打个比方:
    • 如果我采样上图中最左边的红色长方体的顶面中间的红色跟它旁边这块红色,由于颜色比较接近,计算出的颜色差值比较小,那就正常渲染它,但如果是在边界位置
    • 比如这个长方形的最底下的像素点是红色,下面的地面是黑色,那么当你对红色像素点采样时,又采样了旁边的黑色像素点,然后这两个像素点的颜色,发现颜色差比较大,那这个红色像素点的位置就是边界,此时把边界像素点渲染成黑色,就实现了描边效果

光线追踪

  • 接下来就要进入正题了,刚才已经知道了屏幕后处理是什么,怎么写屏幕后处理,屏幕后处理是面向一个像素点,然后执行片元着色程序,就是这么简单的事儿
  • 所以下面来看看如何用光线追踪技术来渲染云盒,什么叫云盒呢?其实这是我发明的一个名词,就是渲染一个盒子,这个盒子的里面是用来画云的,所以简称它叫云盒,否则描述起来很复杂
  • 这一步的目标就是在执行左边的片元着色器程序时能渲染出一个盒子,大家来看上图,右上角图片是正常的场景,在这个场景里,我随便拖出来一个盒子并把它设置成不渲染的状态,就是说我只是拖个形状出来,然后取得这个形状,这个盒子我们可以叫它AABB包围盒
  • 大家注意:任何一个立方体的盒子,它都是有一个左下角点和右上角点的,也就是坐标最小的点和最大的点,那么我们取得这个盒子坐标最小的左下角点,然后再取得坐标最大的右上角点,它们在程序里分别叫做BoundsMin和BoundsMax
  • 然后我们往程序里的rayBoxDst传入包围盒的范围,然后再传入一个射线,这里要注意的是在屏幕后处理阶段里我只是传入屏幕坐标的范围,没有把盒子的模型传进去,就能把盒子、包括盒子范围内的内容画出来,就如同上面右下角的那张图所示
  • 下面我们就来看看它是怎么一步一步实现出来的,我们先来看一下算法的解析
  • 其实这个地方刚才已经大致提过了,首先传入一个AABB包围盒,它代表盒子的范围,然后我们会在渲染每一个像素点时从我的摄像机位置发出一条射线,射线的目标点就是这个像素点的位置,也就是说:射线的起点是摄像机的原点,方向指向屏幕上的一个像素点,然后通过rayBoxDst(快速求交)算法,就能算到这个像素点是否与云盒中的云有相交
  • 大家来看我在右下角画的图,我在里面画了一个四边形盒子,盒子里的就是云,那么我射出的射线到底是跟这个云有相交,还是没有相交,通过rayBoxDst就能算出来,这个rayBoxDst会返回一个二维向量,它表示射线与云盒的碰撞信息,如果返回的是零,就表示射线没有碰撞到云盒
sampler2D _MainTex;
float3 BoundsMin;
float3 BoundsMax;
Texture3D<float4> ShapeNoise;
Texture3D<float4> DetailNoise;float4 frag(v2f input) : SV_Target
{float4 col = tex2D(_Miantex, input.uv);float3 rayOrigin = _WorldSpaceCameraPos;float3 rayDir = normalize(input.viewVector);float2 rayBoxInfo = rayBoxDst(BoundsMin, BoundsMax, rayOrigin, rayDir);float dstToBox = rayBoxInfo.x;float dstInsideBox = rayBoxInfo.y;bool rayHitBox = dstInsideBox > 0;if(!rayHitBox){col = 0;}return col;
}
  • 大家来看上面的代码
  • 在通过rayBoxDst获取到碰撞信息后,我通过返回的X坐标来记录射线到盒子的距离并用变量保存,叫做dstToBox,然后再通过返回的y值记录盒子内部的长度,同样是用变量保存叫做dstInsideBox
  • 前面我们也讲过:如果射线没有碰到盒子,那么它返回的就是零,所以我就用盒子内部的距离值(dstInsideBox)来判断是不是碰到这个盒子了,如果返回的是True,就表示碰到了,如果是False,就表示没有碰到
  • 下面的if语句就是判断如果没有碰到,就把颜色设置为黑色,碰到了就会把显示颜色,这样就能把盒子画出来了,今天我们不去追求精确的理解,但希望大家能大致上理解如何在屏幕后处理的空间里,在明明没有模型的情况下,把我们的立方体盒子渲染出来
  • 这种技术叫做SDF算法,其实我们不仅可以在屏幕四边形上画盒子,还可以画各种各样的形状,大家如果想要了解如何绘制各种SDF形状,那么大家也可以在文末扫一下爱丽丝老师的二维码,来领取我们的课程资料
  • 所以要实现这样的功能,其实也就这几行代码,关键就是你能不能理解,理解了,那么这段代码对你来说就不难,但如果你不理解呢,那虽然只有十几行代码,但对你来说就跟天书一样,这和客户端开发不大一样,如果你是客户端程序员,那么你可能每天都会写上几百行代码,但是这几百行代码的技术含量可能没有这么高,关键在于理解
  • 现在我们可以反过来把盒子外面绘制出来,把盒子里面绘制成黑色,我们要做的事很简单,就是把上图代码中的if语句中的感叹号去掉,就可以可以实现这样的效果了
  • 我们接着往下看,现在假设我的盒子是隐藏在前面红色盒子的后面(如右上角图),但我们的黑色盒子并没有被红色盒子遮挡住,这又是什么原因呢?
    • 这是因为我们在屏幕后处理阶段写的Shader代码没有办法在一个平面上区分每一个像素点的前后关系的,就比如黑色盒子的像素点明显是被藏红色立方体的后面的,但我没有办法得到它们的深度关系
  • 那该怎么办呢?
    • 我们可以利用Unity URP渲染管线的一个深度采样功能(内置管线也有,不过方法不一样),就能取得每一个像素点的深度值,然后再通过图形学里的深度重建的技巧,来得到每一个像素的深度值
  • 就比如说在屏幕中间有一个像素点,那么现在它的深度值是记录在深度缓冲区里的,然后这个深度值通过深度重建,就能得到这个像素点从摄像机里看过去的深度
  • 然后我们先假设盒子在这个点的后面,接着我只要通过光线追踪来追踪一下,就能得到这个点到立方体盒子的距离值,现在这个点的深度值是明显比盒子的深度值小的,那么就能证明盒子是被隐藏在深度缓冲区后面的,这样的话,盒子中被遮挡的像素点不会被渲染出来
  • 上面的代码中有一个判断
bool rayHitBox = dstInsideBox > && dstToBox < depth;
if(rayHitBox)
{col = 0;
}
return col;
  • 如果摄像机到盒子的距离小于场景的深度值,也就是盒子在前面,那么才会把盒子的区域渲染成黑色
  • 关于深度重建的原理和推导,我们会在课程里详细介绍

光线步进

  • 体积云并不是盒子范围内全部都要渲染,为什么呢?因为前面生成的噪点图代表云是否生成,而噪点图是一个灰度图,所以当它的灰度值小于一定的值时就不会把它渲染成云
  • 这里要写一个算法来采样当前位置里云的浓度
float sampleDensity(float3 position)
{float3 uvw = position * CloudScale * 0.001 + CloudOffset * 0.01;float4 shape = ShapeNoise.SampleLevel(samplerShapeNoise, uvw, 0);float density = max(0, sharp.r - DensityThreshold) * DensityMultiplier;return density;
}
  • 在这个算法的第一行传入一个position(位置)(如下)
float sampleDensity(float3 position)
  • 然后通过当前位置取到当前位置的uv坐标(如下)
float3 uvw = position * CloudScale * 0.001 + CloudOffset * 0.01;
  • 大家可以想象一下:上面的立方体盒子里面装的就是立体的噪点图,装进去以后,然后进行采样(如下)
float4 shape = ShapeNoise.SampleLevel(samplerShapeNoise, uvw, 0);
  • 因为噪点图的RGBA通道内全都是相同的颜色,所以采样到的是一个黑白图,直接取红色通道里的一个噪点的灰度值减去云浓度的预值(如下)
float density = max(0, shape.r - DensityThreshold) * DensityMultiolier;
  • 这个减法是为了判断这个位置到底有没有云
  • 比如当前取出的噪点值是0.7,而云的阈值浓度是0.5,也就是说噪点的值如果低于0.5就不作为云的一部分
  • 你还可以把云的浓度系数做一个放大,这就是上面代码中DensityMultiplier的作用
  • 放大以后,就能得到云的浓度信息了,这里需要注意的一点是:由于云在立方体盒子里是立体的,所以这个3D噪点图会有很多层,所以并不是一次步进完,而是进行多次步进(如下图)
  • 这也是为什么这个技术叫做光线步进的原因,每次步进一点,都要进行采样,然后判断
  • 这个立方体盒子里被红色线条包围的地方是云,图中画了箭头的点是有云的,它们的浓度值是0.1、0.01、0.5,因为不同位置上的浓度是不一样
  • 将它们进行求和,算出来的总值就作为最开始看到的那个点的浓度值
  • 这是下一步的算法,也许有些细节你看不懂,但没关系,接下来讲解被黄框包围的代码,详细的细节会在系统课程里进行解释
  • 下面这段代码是设置每次光线步进的距离
float stepSize = dstInsideBox / NumSteps;
  • 然后写一个while循环:只要没有离开云盒的范围,就不停的采样云的浓度,然后把它加到总浓度里
while(dstTravelied < dstLimit)
{float3 rayPos = rayOrigin + rayDir * (dstToBox + dstTravelled);totalDensity += sampleDensity(rayPos) * stepSize;dstTravelled += stepSize;
}
  • 最后把总浓度跟整个场景渲染出的颜色做乘法混合,这样云浓度比较高的部分在渲染时就没有场景里本身的颜色,而云浓度比较低的部分渲染时就有场景本身的颜色
float transmittance = exp(-totalDensity);
return col * transmittance;
  • 这里有一个细节点是需要注意:云的浓度越高,它采样出的结果就越大,那么怎样在云的浓度越高时,场景里渲染的内容越少呢?

    • 这里需要用到exp函数,大家中学应该也学过exp函数:e是一个常数值,它的值等于2.718,X值越小,也就是云的浓度越小,最后算出的e^-x值就越大
    • 也就是说:云的浓度越小,场景显示的比例就越大,云的浓度值越大,场景显示的亮度系数就越少
    • 这个地方虽然是一个数学细节,但如果不注意、或者平时在网上看文章时不理解的话就会错过这个点,使用其他方法处理得到的结果也没这么好
  • 在这里向大家提一个问题:
return col * transmittance;
  • 为什么没有用1 - transmittance的方式?这样能实现效果吗?为什么不能这样做?或者为什么没有首选这样做?大家可以思考一下

  • 这是经过上面一系列步骤之后实现的效果,虽然这不是最终效果,但是和我们想要的效果已经比较接近了,可以看到这个云本身是立体的,而且可以调节它的浓度系数、范围系数、云的偏移,通过调整云的偏移就可以让云动起来
  • 虽然这是在屏幕后处理阶段实现的,但我们还是会用一个立方体盒子来承载这个效果,并且可以向其中传入想要渲染的范围,以做到动态控制

大气光线散射

  • 刚才已经将云的效果实现了八、九分,下面还需要增加一个考量,就是大气的光线散射,这里采用了一种叫做LightMarch技术
  • 它的原理是这样的:从摄像机的位置射出一条光线,这条射线会穿越云层,每当它穿越一点,就判断一下它到太阳光方向的浓度值,浓度值越大,说明它产生的散射就越多,也就是光线从太阳的位置射过来以后会产生更多的偏折
  • 云里的每一个点都要判断一下,最后把LightMarch的结果加起来,就可以得到光线散射的系数值,然后用这个系数值作为最终渲染云效果的系数,就能实现:”云越靠近光线的地方,就越透明;越远离光线的地方,就能更多的展现它本身的颜色”的效果

云的形态控制

  • 前面讲过:体积云最大的好处就是可以控制云的形状
  • 所以我们也通过Unity实现了编辑器扩展,可以编辑、调整云生成时的形状,包括实时渲染出的形状
  • 上图就是编辑器所实现的效果,它可以进行云的偏移、形状变化、改变云的浓度值、受光线的影响程度、光线的反射、还可以把云的范围调大调宽
  • 还有刚才讲到的大气散射效果,可以看到靠近太阳的地方是比较通透的,而且可以调整散射的值,这跟之前讲的背光原理课程是有点接近的,大家如果想要学习我们的公开课,可以在文末扫一下爱丽丝老师
  • 另外这个效果在调整灯光角度时,云会受到灯光角度的影响,所以体积云的另外一个好处就是它是全动态,也就是说你所看到的效果都是随着场景光照、位置、观察的角度而实时变化,它是真实的

最终效果演示

  • 上图是体积云效果在游戏场景中的使用,可以看到体积云是可以穿进去的,而且可以在云上、云下飞行,它和之前讲的云海效果不太一样,云海效果一般是用在角色脚底位置,而体积云是用在天空的位置
  • 比如你的游戏里有武侠轻功,那么你在飞越时就可以穿到云层里去,这样可以更好的提升游戏的品质

小结

  • 接下来对今天课程内容做一个小结:

    • 体积云的噪点图生成

      • 课程最开始讲解了云渲染的第一个步骤:噪点图的生成
      • 讲到了噪点图生成的原理和步骤,还有它的实现的算法,以及可以对噪点图做的一些优化
    • 屏幕后处理简介
      • 在进行光线追踪和光线步进时需要用到屏幕后处理技术
    • Ray Tracing光线追踪一个云盒
      • 今天通过比较短的介绍来让大家对屏幕后处理的具体含义有了基本的了解,这样就能进一步学习如何使用光线追踪来渲染云盒,它的核心就是光线与AABB包围盒的碰撞判定
      • 深度重建和SDF算法会在VIP课程里给大家详细讲解
    • Ray Marching光线步进云的形状
      • 这里利用Ray Marching技术做了光线步进
      • 如果你自己去看光线步进的论文的话,我估计你可能看上五分钟、十分钟就会犯困,因为它太学术了,所以我们在课程里会尽量用通俗易懂的语言来帮助大家了解到光线步进的原理
      • 其实这也是为什么系统学习会更好的原因,你遇到问题时可以直接来问指导老师,我也会把我这十几年的工作经验、图形效果的开发经验教给大家
    • 大气光线散射的原理
      • 今天因为篇幅原因所以没有详细地讲解大气光线散射的原理,但在我们的VIP课程里,我们会给大家详细的讲解它从原理到实现的步骤
    • 云的形态控制
      • 最后就是我们需要编写云的编辑器拓展,这也是TA同学必须掌握的技能
      • 我们通过这个编辑器可以控制云的形态,这样能方便美术同学去调整云的效果
    • 最终效果演示
      • 在最后看了一下最终效果演示
  • 有同学可能会问:我掌握体积云以后是不是只会体积云呢?这堂课能让我学到什么东西呢?
    • 除了今天学的体积云以外,体积雾、体积光其实都可以利用今天的课程技术实现
  • 当然今天学的内容其实也还不仅仅只是这么一点内容,接下我给大家总结一下课程里的一些可以思考点,大家可以在课后去想想

思考

  • 一、噪点图生成算法如何进行优化?

    • 我们讲到了高层的优化原理,那么最基本的优化原理又是什么样的呢?
    • 当噪点的中心点比较多的时候,有什么方法可以快速的优化噪点图的生成呢?
  • 二、Compute Shader的原理是什么?
    • Compute Shader可以帮助我们快速的生成噪点图,特别是在3D噪点图数据量比较大的情况下,那么我们应该如何掌握、并用好Compute Shader呢?
    • Compute Shader不仅仅可以用在噪点图的生成上,它的用处非常广泛,比如说做二次元卡渲时,我们需要对角色的顶点做法线平均化,那如果你能够用Compute Shader那就会比直接写的脚本运算快速,这也是我们的课程会教给大家的
    • Compute Shader还能用来做实时大规模的动画运算
  • 三、如何有Compute Shader加速运算
  • 四、Ray-Box快速求交算法
    • 刚才讲过,这里就不再赘述了
  • 五、RayMarch性能是否还有优化空间
    • 由于RayMarch在包围盒范围内是一步一步往前走的,所以它的执行指令数量、流水线、还有for循环可能会造成打断都会对它的效率造成影响,也就是说:它是有优化空间的
    • 想要优化它,我们需要掌握两个知识点
      • 需要理解图形渲染管线的工作流程
      • 深入理解RayMarch的算法原理
    • 掌握它们以后我们就可以对RayMarch进行优化,大家也可以关注一下我们课程,后面我们还会去开一些RayMarch的性能优化、RayMarch的技术实现专题
  • 六、手游上的RayMarch如何优化
    • 这在我们的VIP课程里会讲到
  • 七、什么是深度重建
    • 这是TA的基本功
  • 八、如何利用URP环境下的深度缓冲
    • 因为URP的性能比较高,所以现在很多公司都会迁移到URP环境,所以你需要掌握URP环境下的深度缓冲、它的用法、它的编程、包括在URP环境下怎么写屏幕后处理,因为URP中的效果实现方法跟传统的内置管线是有些不一样的,比如URP环境下的屏幕后处理跟传统的内置管线就是不一样的
  • 九、大气的光线散射如何深入理解并实现
    • 这个大家自己可以思考一下
  • 十、体积雾怎么做
  • 十一、体积光怎么做
  • 十二、如何利用Unity的编辑拓展实现TA工具链
    • 大家可以看到我们在生成的噪点图、云的实时效果的参数更新都是依赖于编辑拓展的,所以这也是TA必备的工作技能

写在最后

  • 更多学习资源请加QQ:1517069595或WX:alice17173获取(企业级性能优化/热更新/Shader特效/服务器/商业项目实战/每周直播/一对一指导)
  • 点赞、关注、分享可免费获得配套学习资源
  • 点击观看完整视频

初学者也能看懂的Ray March体积云相关推荐

  1. 初学者也能看懂的 Vue2 源码中那些实用的基础工具函数

    1. 前言 大家好,我是若川.最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 想学源码,极力推荐之前我写的<学习源码整体架构系列>jQuery.underscore.l ...

  2. 初学者也能看懂的 Vue3 源码中那些实用的基础工具函数

    1. 前言 大家好,我是若川.最近组织了源码共读活动.每周读 200 行左右的源码.很多第一次读源码的小伙伴都感觉很有收获,感兴趣可以加我微信ruochuan12,拉你进群学习. 写相对很难的源码,耗 ...

  3. Super Jumping! Jumping! Jumping!(初学者也能看懂的dp)

    Nowadays, a kind of chess game called "Super Jumping! Jumping! Jumping!" is very popular i ...

  4. 学校里学不到的调试技巧(初学者也可以看懂)

    实用调试技巧 1. 什么是bug? 2.调试是什么?有多重要? 2.1 调试是什么? 2.2 调试的基本步骤 2.3 Debug和Release的介绍. 3. Windows环境调试介绍 3.1 调试 ...

  5. C语言打印出心形表白,520神器,初学者也能看懂!!

    C语言实现打印出心形,初学者的表白神器. 解题思路:这道例题我分了4部分,前3行一部分,4-6行一部分,7-13行一部分,最后一行一部分,读者请仔细阅读注释,小林写的很详细了. 前三行输出,为了让初学 ...

  6. 初学者都能看懂的 Spring 源码之依赖注入(DI)源码分析

    前言 在面试中,经常被问到 Spring 的 IOC 和 DI (依赖注入),很多人会觉得其实 IOC 就是 DI ,但是严格上来说这两个其实并不等价,因为 IOC 注重的是存,而依赖注入注重的是取, ...

  7. Linux都有什么版本呢?各发行版分类与说明 初学者也能看懂

    程序员们对优秀的代码永远充满着好奇心理,过往 windows 或 Mac的代码是不对外透露的,程序员们没有深入接触操作系统的机会. 然而,Linux代码因为其开源特性大家都能从网上获取.这一点可以说具 ...

  8. 详解 matplotlib.pyplot ,Python 初学者真能看懂

    Matplotlib 是一个 Python 中的 2D 绘图库, pyplot 模块是一个方便使用 Matplotlib 的接口. 下面是 pyplot 模块中的五个重要的知识点: [创建图形]: p ...

  9. 初学者也能看懂的DPDK解析

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由Willko发表于云+社区专栏 一.网络IO的处境和趋势 从我们用户的使用就可以感受到网速一直在提升,而网络技术的发展也从1GE/10 ...

最新文章

  1. 【学术相关】科技论文写作:grammerly润色工具
  2. Mysql用navicat查看建表语句
  3. docker centos node nginx
  4. Windows环境下基于python3 + selenium构建网络爬虫
  5. 定位于定位优化(iOS)
  6. 【Kafka】A broker is already registered on the path /brokers/ids/0. This probably indicates that you
  7. CCF NOI1007 计算余数
  8. Android IOS WebRTC 音视频开发总结(二二)-- 多人视频架构模式
  9. 全球机场三字码,对应的城市三字码
  10. 王者荣耀android换ios,王者荣耀安卓转ios教程攻略
  11. 批量查排名的工具有哪些?网站关键词可以优化?
  12. Kubernetes之Secrets
  13. C#窗体Winform,如何嵌入图片添加图片,使用图片资源?
  14. 疫情下的科技内卷:租房被卷进“网购”时代
  15. JS的正则表达式及详解
  16. Java计算当前应用的tps_Java TPS实现
  17. 上地服务器托管机房的现状
  18. 高斯定理下的电场和磁场(详细案例模型分析)
  19. Cisco3905话机一直停留在‘image downloading fail’界面
  20. bomb二进制炸弹拆除实验(MIPS)

热门文章

  1. 狗都能看懂的CenterNet讲解及代码复现
  2. 服务器xp系统网页打不开网页,ie浏览器打不开网页,xp系统ie打不开网页-
  3. Android实现一键开启自由窗口、分屏、画中画模式——分屏模式
  4. java 事务管理 子父线程_java父线程子线程(转)
  5. TweenMax介绍
  6. 人造肉在中国还有未来吗?
  7. Qt播放音乐报错DirectShowPlayerService::doSetUrlSource: Unresolved error code 0x80070002 ()
  8. Gartner:2017年中国新兴技术成熟度曲线
  9. 魔兽世界怀旧服哪个服务器人最多,魔兽世界怀旧服8个服务器人口普查 部落/联盟阵营最新比例...
  10. 某金融企业核心存储POC测试及选型经验