写在前面
因为各种事务繁杂原因忙了一段时间别的事情,近期回来继续学习Unity Shader及其背后原理。
在浏览过程中看到了 这个。
因而自己也设法实现了一下
原博客实现非常简短,实际上也清晰明了,但是其中包含的许多原理知识都没有涉及到,因此这里也把原理和相关知识尽量写下来。
但是在研究细节的过程中还是发现每个部分都有很多值得挖掘的地方,接下来慢慢的把乐乐大佬的书抄一遍这些原理写出来吧。
这里只介绍一下需要用到的部分,但是比较简单且方便说明的原理我会尽量提及解释。
实现原理
水面起伏部分
这部分很简单,我们读取一张噪声,然后为顶点加上一个正弦波动画,并且使用上噪声提供的数据即可。这部分的实现比较简单,一会直接上源代码。这次的重点主要放在深度图部分。
需要注意一点,这里由于需要在顶点着色器内对纹理采样,但是不能使用tex2D函数,原因是uv坐标这个阶段还没出来?(Unity官方文档这么写的:Using tex2D in the Vertex Shader. This is not valid, because UV derivatives don’t exist in the vertex Shader. You need to sample an explicit mip level instead; for example, use tex2Dlod (tex, float4(uv,0,0)). You also need to add #pragma target 3.0 as tex2Dlod is a Shader model 3.0 feature.)
浪花部分
水面的波纹,需要在它触碰到别的物体的时候产生效果,但是如何检测到其他物体边缘的存在呢?难道写个脚本搜索吗?(我以前真这么觉得。。。。)
当然不是,想象一下,三维空间内每个物体的坐标又三个参数构成,分别是xyz坐标,转换到NDC空间时,屏幕上每个点可以对应多个物体(因为有许多物体处在同一个视角的不同距离处嘛),因此很自然地,如果我们遍历屏幕上的每个点,这些点实际上已经为我们固定好了三个坐标中的两个:(x,y)坐标。
因此在判断当前绘制的物体是否距离其他物体很近的时候,我们并不需要计算当前点和周围点的距离然后判断哪些点是近的,而只需要在绘制某个点时,看看现在需要绘制的物体的z深度和已存在物体的z深度,做个比较即可。
这两个值比较接近的,就意味着当前绘制的物体与屏幕内其他物体非常接近。而使用这个值作为一个遮罩或者采样器,就能够实现各种跟“交互”相关的效果了。
那么我们怎么得到这两个值呢?
当前绘制物体的z深度:
当前绘制的物体的z深度,即是每次绘制顶点时顶点在观察空间下的z坐标。
已经存在的物体的z深度:
Unity中为我们保存了depth Texture,储存着摄像机看到的空间的深度值。
下面我们分别来看
使用深度纹理以及屏幕空间坐标采样实现浪花
这里为了使用屏幕空间坐标对Unity生成的深度纹理进行采样,我花了不少功夫搞明白Unity内究竟是如何转换各种坐标的,尤其是ComputeScreenPos()函数,究竟做了什么。这些函数的代码都非常短,但是背后蕴藏的数学知识会稍微麻烦一点
在Unity内使用深度纹理
首先在脚本里把主摄像机的depthTextureMode打开:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode]
public class depthtexturemode : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GetComponent<Camera>().depthTextureMode = DepthTextureMode.Depth;
}
// Update is called once per frame
void Update()
{
}
}
_CameraDepthTexture内置纹理变量
这里的depthTextureMode.depth会在Shader里面提供一个内置变量sampler2D _CameraDepthTexture;
我们在shader内可以使用它:
需要明确的是,在_CameraDepthTexture中存储的实际上只有一个float有效数据,因此使用float就可以了,原博客中使用了float4来获取,最终也只使用了r分量,让人非常困惑。。。。
而深度值可以有多种表述方式,我们注意到,在观察空间,剪裁空间,NDC都是有Z轴的,这些深度量化程度不同,但本质上其值都对应着一个唯一的深度,并且可以通过数学公式互相转换。
那么这里的深度值具体是什么值呢?
这里的深度是由NDC下的深度来的,但不是NDC深度
从_CameraDepthTexture中获取的值 d = 0.5 ∗ Z N D C + 0.5 d=0.5*Z_{NDC}+0.5 d=0.5∗ZNDC+0.5,因此一般情况下有 d ∈ ( 0 , 1 ) d\in(0,1) d∈(0,1)。
如何使用深度纹理中的值呢?
由于NDC是齐次除法的产物,齐次除法是非线性的,因此d值也是非线性的,使用起来十分诡异,为此需要将d转换为线性空间下的深度值。
而观察空间就是显然的线性空间,因此很自然我们希望通过d值回溯到观察空间下的值,可以么?当然可以。
z c l i p = − F a r + N e a r F a r − N e a r ∗ z v i e w − 2 ∗ F a r ∗ N e a r F a r − N e a r z_{clip}=-\frac{Far+Near}{Far-Near}*z_{view}-\frac{2*Far*Near}{Far-Near} zclip=−Far−NearFar+Near∗zview