Shader - 水体(保姆级)
最终效果预览水体的深度水的深度 : 越靠近斜坡的地方深度越浅,颜色越淡越透.反之则相反利用深度值计算的话,要先开启深度值在shader中加入深度纹理的声明,屏幕坐标的采样UV我们把采样深度图的纹理叫做depthTex,把采样深度纹理的UV叫做screenUV,用screenUV采样深度纹理.在下图中,screenUV的含义是利用屏幕空间下像素坐标除以屏幕的宽和高的像素值(裁剪空间的坐标经过除以w分量齐次化之后又乘以了屏幕像素变换到了屏幕坐标中,除以 _ScreenParams.xy是为了做归一化的处理)此时的depthTex不是线性的深度值,原因是渲染水面时必须在 Shader 标签里设置QueueTransparent并且关闭深度写入通常是ZWrite Off。原理解析渲染引擎画东西是有顺序的。如果你把水面当成“不透明的砖块”画出来它就会把自己的深度记录在“深度图”里把水底下的石头完全挡住覆盖。把水设置成半透明且不写入深度是为了给水底的物体让路。因为水面没有写入自己的深度所以此时全局的深度图_CameraDepthTexture里干干净净地只保存了场景里那些不透明物体比如河床、石头、岸边泥土离摄像机有多远。代码对应就是你在 Shader 里写的SAMPLE_TEXTURE2D(_CameraDepthTexture, ...)你采样出来的这个值其实是水底石头的深度而不是水的深度。现在我们有了水底石头的深度那“水面”本身的深度去哪找呢因为水面没进深度图所以我们只能在代码里“当场计算”。原理解析我们可以把水面面片的顶点坐标通过矩阵乘法转换到“观察空间”以摄像机为原点的空间。此时它的 Z 轴数值Z值就是此时此刻水面这个像素距离摄像机的物理距离。拿刚才得到的两个深度值做减法就能算出水的实际深度。原理解析*公式水的绝对深度 水底石头距离摄像机的深度 - 水面距离摄像机的深度。如果两个深度相减结果接近于 0说明水面和石头几乎挨在一起这里就是交界处岸边或露出水面的礁石可以在这里画白色的泡沫。如果两个深度相减结果非常大说明石头离水面很远这里是深水区应该渲染成深蓝色并且水花要变弱。在subshader中写入下面的语句:因为要在frag中利用vert计算好传过来的观察空间下的值,所以在varying中声明:在vert中将模型空间的顶点转换到观察空间中:水的绝对深度 水底石头距离摄像机的深度 - 水面距离摄像机的深度。利用该公式在frag中写上:返回waterDepth查看效果水的颜色定义两个颜色,一个颜色用于靠近物体的边缘部分,一个用于控制大面积的白色部分在CBUFFER_START中声明对应的变量:在frag中利用lerp函数进行颜色的差值,再返回查看效果水的泡沫因为水的泡沫相对独立一些,仅依靠深度值,再引入一些纹理和参数就可以调节水的泡沫了,所以先写水的泡沫部分.水的泡沫部分采用采样纹理的方法来表现调整纹理的缩放大小,得到下图的效果在frag中写下方逻辑,并返回查看效果现在要控制waterDepth的范围,声明一个范围参数(0~2)来控制它,如果该参数小于1,那么白色范围就会减少,对应浅滩面积增大,反之则相反为了减少文章长度,在properties中声明的参数和在SRPBATCHER中的参数非必要不再书写在frag中加入此range为0~2的数值,返回之后查看效果再返回FAndWCol查看效果为了给水面增加流动效果,可以利用_Time.y对采样的uv进行偏移,再叠加一个速度在vert中写下方逻辑即可,此时的uv就流动起来了为了让泡沫呈现出具体的形状在frag中写下面的逻辑,再返回查看效果可以看到因为step的函数的原因,因为waterdepth远离浅滩的值几乎都为1,所以foamtex和它对比后返回的值都是0,即黑色通过调整foamtex的缩放可以改变水花的形状为了避免后续出现问题,将foamtex的通道改为单通道来匹配waterdepth,同时取出采样出来的四维通道中的一个通道,在这里rgb都可以将foamcolor和watercolor叠加后输出,注意变量的之间的修改现在的foamcolor有一个问题,当模型被拉伸时,它也会被跟着拉伸为了解决这个问题,将引入世界空间下的坐标来采样纹理传统模型 UV 记录的是相对比例0 到 1。当模型被拉伸时UV 空间的映射范围被强行拉扯从而导致纹理产生明显的拉伸与模糊。而世界空间平面映射采用的是绝对物理尺寸真实的米。模型拉伸时世界坐标并不会发生形变仅仅是使更大范围的世界坐标参与到了当前像素的采样计算中因此纹理的物理大小和分辨率始终保持稳定。但是这样引出了一个新的问题,水花不会流动了,因为原来的流动是在uv上的,世界坐标并没有流动所以要在世界坐标上添加流动,将uv的名字修改为foamUV,代表这个UV是专门用来采样水花纹理的UV,再叠加上世界空间顶点坐标在frag中将i.positionWS.xz修改为i.foamUV但是问题又来了,我想让它随着我改变tilling的值而改变,但是又不想它因为模型的缩放而变化,怎么办?顺着这个问题,如果让tilling *世界空间的顶点会怎么样这样问题就解决了在调试时发现了一个bug,当拖动waterdepth的时候,水花会和水深一起移动,所以waterdepth * _WaterDepth是错误的,应该新建一个变量来接收这个值添加下面的一句代码,作用是:利用幂函数调整泡沫噪声纹理的对比度进而控制水面泡沫的密集程度稀疏度和边缘硬度。 其中_FoamArea是新增的range变量解析:水面颜色BUG调整刚刚调试又发现一个bug,调整水面颜色的时候出现了乱七八糟的情况,这种情况是因为插值因子出现了问题,我们的插值因子是waterDepth,它的来源是:float waterDepth linearDepth i.positionVS.z;这个值并不是0到1的值,而是0到很大的值,所以它作为插值因子的时候,计算公式是这样的:总之,原因是waterdepth的值太大解决办法:我们让它限定在0到1的范围,并且让它除以一个自定义的变量值水下的扭曲为了能够看到水下的物体,首先水体要设置为透明水体,在subshader中写下Blend SrcAlpha OneMinusSrcAlpha并添加如下代码:即可看到水下的物体,没有阴影是因为忘记打开平行光了如果想要实现水下扭曲的方法,可通过采样屏幕抓取纹理再扭曲屏幕抓取纹理也可以使用这种方式:总共采样两张纹理,一张纹理采样过后当作另一张采样纹理的UV坐标,就是把A纹理采样出来的纹理值A当作uv坐标,去采样纹理B,这样就达到了一个扭曲的效果(有省略);打开如下设置将渲染队列设置为透明队列加入抓屏纹理的声明和寄值器的声明我的老天,发现了一个问题,我忘记说之前采样的foamTex其实是一张噪声纹理了.OMG将采样的噪声纹理的uv(foamTex)和screenUV进行插值,插值因子是_WaterDistort,这是一个新声明的范围0到1的变量,当它为0时,lerp函数计算出的就是foamTex的值,并赋值给distortUV再用distortUV作为抓屏纹理的采样UV值,就实现了扭动的效果return一下看看效果,后续还会对该效果进行改进,现在的问题是,foamTex的tilling是需要调整的,调整它的同时也会影响到扭动效果,所以需要单独用一张纹理的纹理值对抓屏纹理采样这里采用一张法线纹理贴图在frag中写下面的逻辑由于需要经常用到_Time.ySpeed,所以将它用一个speed变量储存起来,以后直接用speed即可在vert中对waterDisUV进行缩放和旋转的应用之后加上速度,这样法线纹理就可以产生效果了在frag中写下面的逻辑,将watercolor * opaqueTex的目的是混合抓屏纹理和水面的颜色,此时颜色值可能会出现过曝的情况,为了解决这种情况可以将waterColor * 0.5来解决,或者加上saturate,或者通过乘法而不是加法的方式混合颜色(指的是fianlcolor和opaqueTex)现在的效果观察下图,会发现虽然物体进行了扭曲,但这个扭曲就好像是复制了一个物体进行扭曲,因为它不仅扭曲了,还保持物体原有的形状,这是为什么?通过寻找发现,是watercolor的问题,因为opaqueTex没毛病,foamcolor也没毛病,那只能是watercolor的问题了,先确定下来,因为后面要通过C#脚本写渐变的颜色替代watercolor;现在的主要问题是,物体不仅在水下进行扭曲,在水上也进行了扭曲,因为之前是对抓屏纹理的整个纹理进行了扭曲但是这张抓屏纹理背后是有物体的,这个物体不会扭曲,而且显现了出来深度图重建扭曲效果我们先对深度图进行扭曲看看效果在frag中写下面的逻辑,用前面的lerp函数计算出的distortUV对深度图进行采样得到的效果是发现不仅下面的相交处产生了扭曲,上方的边缘也产生了扭曲,这是为什么?其实原因出在也就是说深度值的相加不仅发生在模型的表面,还发生在看不见的区域那么让小于0的扭曲深度值 之前不扭曲的深度值,在深度图中水面之上的物体就不会进行扭曲了有了这个值之后,把它作为抓屏纹理的采样UV,水面之上的物体就不会进行扭曲了但是抓屏纹理的采样UV是可以在screenUV和DisNormal中切换的,所以也应该对深度值进行插值在frag中写下面的逻辑让opaqueUV 扰动的UV,如果深度图中的值小于0,对应的就是水面之上的物体,那么就让它等于screenUV但是if语句开销大,我们换成别的函数来代替亦或是用三目运算符水面的高光加入以下变量注意对向量进行归一化.返回之后查看效果但我们想要的高光其实是波光粼粼的感觉,前面在对水下扭曲时,它的效果是这样的扭曲高光能否利用这张法线纹理对高光进行偏移呢?可以试一试,将N替换为DisNormal得到的效果,发现是可以的虽然水面的效果是波光粼粼的,但是现在你会发现,这种效果的流向是单一的,这是不对的,应该是不确定的流向,我们要让法线的流动从单一流动变为对着互相流动,即需要两个流动的值此时之前定义的float4 waterDisUV里的zw分量就派上了用场在frag中写下面的逻辑,让两个法线相乘,返回查看效果将DisNormal作为高光里的法线项和半程向量点积返回查看效果将高光和水面进行叠加,一般来说如果想把高光叠加,都是采取加法的操作水的反射水的反射是不能做实时反射的,因为开销很大应该采用使用反射向量采样cubeMap的方法来实现.我们要自己先准备好一张cubeMap,声明该cubeMap纹理并计算出反射向量采样cubeMap由于我们的法线向量已经是经过扰动后的向量了,用它计算出来的烦反射向量五花八门,跟漫反射似的,所以没有太大意义办法是用一个lerp函数在原始法线和扰动法线之间插值,插值因子是新声明的_Reflection计算出反射向量后,代入采样函数中,返回CubeMap中观察效果菲涅耳水是有很强的菲涅耳效应的,当视线与水体呈掠视时,看到的就是很强的波光粼粼的效果(反射效果强)当视线垂直入射水体时,看到的就是清澈的水体(反射效果弱)在frag中写下面的逻辑,其中_FresnelScale是新声明的float变量,返回fresnel查看效果我们想要的是垂直入射时是黑色的效果,也就是水波不可见,掠视时是白色的效果,也就是可见用1减去原式进行效果反转返回查看效果将反射乘在高光上,返回并查看效果水的焦散采样一张纹理,让纹理值贴通过该面片贴在面片底下的物体上这里需要用到深度贴花的原理来制作的水的焦散效果首先需要获取到贴着地表的一个坐标在frag中写下面的逻辑首先构建一个深度值,该深度值的值是从摄像机到该顶点的距离让该深度值的w值等于1,利用三角形相似的性质求出该深度值的xy让深度值的z值 lineardepth(之前求得的线性深度值)返回查看效果先采样纹理,输出查看一下,采样纹理的时候用的uv坐标是世界空间下的刚刚构建的深度值depthWS的xz值,为什么是xz值?,因为我们只想让它在xoz平面上(o是原点)流动,不需要在y轴上进行偏移查看效果刚刚声明了transform_Tex,所以现在可以控制这张纹理的tilling来缩放纹理的大小现在来调整纹理的流动修改后的代码查看效果,但是这个流动的截图出来也是静止的,所以这里就不放图了只要你的纹理不是流动的,那就是错误的现在和之前水面的流动是同一个问题,焦散的流动是单一的,比较死板,我们要让它随机的流动起来是时候学习unity的UGUI了,明日再更