关于raymarching 原理的的部分,这里就不赘述了,这里有一个系列原理的链接,分别介绍了基础原理,采样不同规则体的计算方法,还有变换操作.
直接进入主题,先说下总体的制作方案:
1,用CustomNode的HLSL来实现Raymarching的计算输出是采样的alpha和hit点的normal.
2,利用之前传出的normal进行fresnel计算出半透明,高光.
一,Raymarching部分
新建一个material并设置材质属性如下:只有translucent模式才能获取到深度.
新建CustomNode,这里您可以在code栏中直接输入代码,或者在其中include自己的usf文件,这样就可以在IDE中编写代码,更加方便.这里我用的是后者.可以在code部分键入:
#include "/Engine/1.usf"
return 0;
这样我们就能在usf中愉快的编写了.
usf的存放路径:
在CustomNode的inputs中创建raymarch计算需要的变量.
MaxSteps: 最大步进次数.
WorldPosition:像素的世界空间坐标.
ObjectPosition:材质对象的世界空间坐标.
SceneDepth:场景深度(用于排序的剔除).
CameraPosition:摄像机的世界空间坐标.
Smooth:进行融合操作的的指数
LightDir:灯光的方向
Time:时间
其中的场景深度需要处理一下,SceneDepth是非线性的深度,我们需要把它转成线性深度才能正确的在raymarch用来处理渲染顺序.关于线性深度和非线性深度的区别这里我画了一张图,也就是原始采样的深度采样点之间的距离是一定的,线性的.但是在转移到投影空间之后采样点变得近处比较稀疏远处比较密集了.
刚好线性深度在顶点经过投影变换之后的长度为非线性深度,那么我们可以直接用SceneDepth/(dot(摄像机方向,摄像机朝向)),摄像机方向是指每个像素的摄像机方向矢量,而摄像机朝向只是单纯的表示这个摄像机是怎么放的,如下图.
在output项里我们设置为float4,也就是output.xyz作为normal的输出,output.w作为采样mask的输出.
有了这些元素之后我们可以在usf中进行raymarching计算了.
1,定义最大距离 MaxDist.摄像机的位置ro,摄像机的方向,初始化颜色col.(最后我们就是return这个col,也就是col.xyz为normal输出,col.w为alpha输出).
2,创建所有函数的结构体RMfunction(在usf文件中我们没办法直接定义函数来调用,我们需要把所有的函数都封装在一个结构体RM里,然后在主循环中调用实例化后RM里的函数.)
struct RMfunction{};
3,创建气泡球的距离方程 RMSphere().
4,创建采样的操作smin(),也就对两个球的融合,想了解这个方程的可以直接上IQ大佬的主页查看.
5,创建GetDist(),调用3中的RMsphere().在这里我们可以重复调用它来创建多个球,然后调用smin()融合它们的距离方程,通过Smooth来控制他们的融合程度,还能用time,frac,cos,sin来偏移hit点p,来做气泡徐徐升起的动画.
6,创建RayMarching主循环,调用之前的距离方程GetDist(),唯一和之前教程不同的是我们需要在每一次步进之前检查下该点有没场景内的对象.
if(SceneDepth < length(p- CameraPosition)) break;也就是当该点的深度比场景深度还深的话直接跳出循环剔除这个像素点.
7,创建normal的计算方法GetNormal().关于计算的原理文章开头给的链接也有提及.
8,实例化RM,并且按照顺序调用里头的函数.输出结果 return col;
二,计算透明Opacity和颜色
1,ColorAnimation:如果给RayMarching上贴图的话会比较麻烦,normal本身的颜色就比较像气泡的颜色,可以用HueShift和Time让它更生动一点.
2,Specular:计算高光.
3,Opacity:先用fresnel来让气泡中心透明,然后把计算的好的specular也add进去.
所有的蓝图如下图:
最终效果如下:
小众交流群:672571935