Height Map Terrain

我在做飞行模拟游戏的时候,有这样的需求:飞机在高空中俯瞰地表。

这时我遇到两个问题:

1.飞机飞十分钟会飞出上百公里,那么这个地表模型谁来做?需要多长时间?

2.这个地表模型有多少面,GPU能扛得住吗?

对,如果我们按照传统思维实现这个功能的话,一定会遇到这两个问题,第二个问题也许还好点,毕竟我们有lod系统,但是第一个问题- -#

那么今天我给大家提供一个新的思路:用shader实现基于高度图的地形系统,我给它命名为“Height Map Terrain”。

先上个效果图:

基本原理如下:

1.输入一张高度图和一张地表贴图。

2.在fragment shader中根据uv映射取地表贴图的颜色,并调整成自己想要的整体色调。

3.在fragment shader中根据uv映射取高度图中的高度值,并经过光照计算出软影子。

4.将2和3混合。

5.输出到屏幕或者Quad。

这个方法有如下几个优点:

1.支持无限大地图

2.不需要人工制作复杂的地形,只要用ps生成高度图和地表贴图即可。

3.没有vertex的计算开销,最多只渲染两个三角形。

实现方法:

1.在场景中创建一个Quad。

2.创建一个空的shader再把附件中的代码考进去。

3.把附件中的水贴图和噪音图考到项目中,并指定给上面创建的shader。

4.把shader拖到上面创建的Quad中。

5.运行。

附件:

Water Effect – Swimming Pool

水面效果是一种常见的特效,在游戏场景中有大量的应用。我们常用的编辑器(Unity、UE)都自带水特效shader。通常来说自带的效果已经做够好了,应该可以直接拿来用。但是我们做移动应用时,受到了gpu和耗电量的限制,自带特效往往不能达到性能平衡点,因此各种五花八门的简化特效出现了。
这里我提供一个水特效,shader计算量非常小,适合做泳池等在固定容器内的水效果。

游泳池特效

这个特效是用Fragment Animation实现的,一般水特效多少都会有sin cos这样的操作以实现波的效果,而这特效利用噪声图来实现,有效的降低了计算量。具体说明见代码注释。

实现方法:

1.在场景中创建一个Quad。

2.创建一个空的shader再把附件中的代码考进去。

3.把附件中的水贴图和噪音图考到项目中,并指定给上面创建的shader。

4.把shader拖到上面创建的Quad中。

5.运行。

附件:

 

水贴图
噪音图

UnityShader之假体积光

1.什么是体积光?

体积光就是白天在你昏暗的房间里打开窗户,一道光顺着窗口照到你的房间。就像这个样子:

“volume lighting”的图片搜索结果

2.体积光如何实现?

常见的体积光实现方法往往会用到深度缓冲和阴影贴图。

比如我通过基本光照公式计算好场景中的阴影区域,那么我就知道光线可以打到阴影以外的任何位置。由于光线会随着距离不断衰减,那么利用深度缓冲我们就可以让体积光看起来更真实。那么我就可以通过后处理方式在非阴影区域混合一个白雾效果,再利用深度缓冲控制雾的强弱,那么最终效果看起来就是上图那样。

3.假体积光

由于体积光的实现会消耗大量的gpu资源,很多设备无法承受这个开销,特别是手机。所以有人用了一些tricky的手法,用相对简单的计算实现近似的体积光效果。这种方法就叫做假体积光。

下面我提供一个假体积光算法:

把FVLController.cs绑在摄像机上就可以运行了。在运行过程中你会发现一些瑕疵,对这就是假体积光,希望有大神能把近似做成完美。

下面放两张效果图

UnityShader之简单描边效果

经常玩3d游戏的小朋友们经常会看到下图这样的效果:一个被选中的人物轮廓被不同颜色的线条包围来表示敌友关系。

“cs go character outline”的图片搜索结果

明明是3d模型,程序怎么知道我从眼睛看到的边缘在哪?程序怎么探测到模型最外面那个像素的?

现在我带大家一起一步步实现这个从传统三维知识的角度看起来无从下手的效果。

解决这个问题确实用的不是什么复杂的技术^^而是一种视觉欺骗,而且只需要2步就能完成的简单效果:

1.先画一个比原始尺寸略大的模型,涂成和描边同样颜色的色块。

2.再把真正的模型盖在上面,因为色块比真正的模型大一点,所以看起来就是模型外面描了一圈边。

先上代码,后面慢慢解释:

 

代码说明:

1.为了适配多个硬件平台,我把逻辑写在了2个subshader中,下面我们仅拿第一个解说。

2.为了让vert再可以在多个subshader中重用,我用CGINCLUDE把它提取出来。

3.如何绘制比实际模型大一点的色块?

答:假设我们有个这样的场景,一个球体和一个相机。

如果要绘制一个大一点的色块,把球scale大一点再上色就行了。但是直接执行下scale就行么?你会发现你看到的不是描边,而是一颗麦丽素。那我们该怎么做?

请看下camera preview。我们需要的只是在xy轴扩大一点就行。模型扩大原理就是顶点沿法线正方向移动。camera preview对应的是View Space。

那么就有

然后在subshader中连续执行两个pass:

第一个pass画色块:

第二个pass绘制实体,并叠加在色块上:

最终效果:

描边有锯齿的锅在第28行。试试其它算子也许效果会更好,但是计算量也会更大。把描边宽度减小能减轻锯齿。

图形学有一句话:看起来是对的就是对的,背后可能完全没有任何数学、物理等科学依据。

UnityShader之顶点动画

我们在游戏中会经常看到这样的效果:翻滚的海浪、飘动的旗子。那么这些效果怎么实现?你的第一想法会是贴图动画?骨骼动画?物理引擎?粒子?

这里我给大家提供一种方案:不依赖于美术,在较低的计算量下实现波动效果效果。这个方案叫做顶点动画。

下面是代码:

拖一个plane到场景里然后绑定一下Material和Shader,再把input1/2/3分别调到0.3/1/1即可。如果你再加个旗子的贴图效果就更好了。

友情提示:顶点动画没有处理顶点变化造成的法线变化问题,所以如果要加光效还需要自己实时修正法线。如果顶点动画太复杂自己写不出对应的法线着色器,那么请调用mesh.RecalculateNormals()。

最终效果

 

UnityShader之滚动贴图

滚动贴图是一种常见的游戏技巧,无限滚动背景、传送带、广告牌和光束武器等都会用到这个技巧。实现这个效果的代码非常简单。
在场景里创建个UI Image再把下面这个shader拖上去就行了。

最终效果

要点说明:

1.什么是_Time?

答:_Time是float4类型,xyzw分别代表:

_Time.x = time / 20
_Time.y = time
_Time.z = time * 2
_Time.w = time * 3

2.什么是frac?

答:内置函数frac表示取float的小数部分。因为贴图的uv范围在0-1之间。这个例子不用frac也能正常运行,但是为了避免出现不可预期的结果,还是建议大家这么用。记住_Time和frac是一对好基友就行拉。

附贴图一张

 

前向渲染和延迟渲染的区别

前向渲染和延迟渲染是两种光照渲染模式。
假设有1个光源和1000个具有光照反射的三角形在view coordinate沿着z轴正方形延伸摆放,法线与z轴平行,即所有三角形xy全相同,只有z不同,但是这里增加一个条件:摆放顺序是无序的。
从屏幕上其实你只能看到一个带光照的三角形,其他的都被挡住了。

那么前向渲染会这样做:
1.遍历1000个三角形片元
2.进行深度检测,没通过的忽略
3.通过检测的进行光照计算
4.更新帧缓冲区
5.返回1继续直到遍历结束

由于上面的要求是无序摆放,那么如果运气差一点 1000次深度检测全部都能通过,那么光照会计算1000次,可是因为只能看见最上面的,那么999次光照计算都是多余的。如果光源越多第三步的重复次数越多,整体复杂度也会越高。

延迟渲染引入了GBuffer,它会这样做:
1.遍历1000个三角形片元
2.进行深度检测,没通过的忽略
3.通过的将坐标、光照等信息写入GBuffer
4.返回1继续直到遍历结束
5.遍历Gbuffer
6.利用Gbuffer中的数据进行光照计算
7.更新帧缓冲区
8.返回5继续直到遍历结束

延迟渲染先把可以显示在屏幕上的像素点的相关参数保存下来,然后只进行了一次光照计算就实现了最终效果。这样大大节约了光照计算复杂度。每增加一个光源,只会增加一次整体的光照计算。所以延迟渲染的好处显而易见了。

然而,世间无完美之事,GBuffer只能给屏幕上的每一个点保存一份光照数据,但是如果这些三角形都是半透明的怎么办?无解–# Blend已废。
由于Gbuffer存的都是像素值,无法体现出每个像素对应的原始模型,那么多重采样抗锯齿功能也无法实现。三角形可能还好点,画圆就悲剧了。

所以如果各位大哥对Blend混合和抗锯齿有要求,那么Gbuffer可能就不太适合了。

Unity Shader之贴图基础

最基本的贴图结构,因为比较简单,直接上代码,注意一下注释。至于什么是贴图,简单说就是给一个模型的表面覆盖上真实世界的图片,让它看起来就是真实世界的物体,而不仅仅是只能看到形状的纯色模型。

操作方法:

1.创建名为TextureBasicShader的shader,并将下面的代码覆盖进去。

2.创建名为TextureBasicMat的Material,然后在它的Inspector面板里选择TextureBasicShader。

3.把本文最下面的贴图拷贝到项目Asset目录,并拖拽进步骤2面板的None(Texture)区域。注意如果是自己找的图片尺寸最好长宽都是2的幂(n次方)大小。

4.随意创建几个3d物体,然后把TextureBasicMat拖拽到物体上即可。

 

最终效果

要点说明:

问:什么是TRANSFORM_TEX?

答:在UnityCG.cginc中有如下定义:

#define TRANSFORM_TEX(tex,name) (tex.xy * name##_ST.xy + name##_ST.zw)

xy控制贴图缩放(材质面板中的Tiling)

zw控制贴图平移(材质面板中的offset)

如果修改材质面板中的tiling或者offset你会发现模型会发生变化。如果把30和31行的代码注释互换,你会发现修改tiling和offset完全没有效果了。

附贴图一张

Unity Shader之环境光

环境光是指光源没有固定位置,没有方向,没有距离衰减,场景中所有物体的所有表面光线强度完全相同的光模型。

原理:

实现方法:

1.向场景中添加一个胶囊。

2.创建一个名为AmbientLightShader的Shader和AmbientLightMat的Material,并将Shader指定给Material。

3.将AmbientLightMat拖到胶囊上。

4.打开AmbientLightShader并填写如下代码

5.编译,可看到效果如下,通体X色,只能看到整体轮廓:

Unity Shader之漫反射光

漫反射是来自表面的光的反射,使得入射光线以多个角度反射而不是仅在一个角度反射。漫反射光和环境光的主要区别漫反射依赖于光线的方向,而环境光与光线方向无关。当打开环境光时,场景中的所有物体都会有同样的亮度。

环境光效果:

漫反射光效果:

原理:

在漫反射光中,光撞击几何表面的角度决定了该表面的亮度。依据朗伯特定律反射光的强度与(观察者的视线和表面法线质检的角度的余弦)成正比。

在上图中,绿箭头代表表面法线,黑线表示表面。ABCD为四个角度的光线。其中A和法线的角度为0,余弦为1,说明A的强度最大。同理B的强度小于A。而CD并非在正空间,肯定无法让平面反射,因此对表面亮度没有影响,此时余弦小于等于0。

表面光强度公式I_{D}=\mathbf {L} \cdot \mathbf {N} CI_{L},来自于朗伯特定律

实现:

1.向场景中添加一个胶囊。

2.创建一个名为DiffuseLightShader的Shader和DiffuseLightMat的Material,并将Shader指定给Material。

3.将DiffuseLightMat拖到胶囊上。

4.打开DiffuseLightShader并填写如下代码

5.编译,查看效果如下图