metaball公式_虚幻4渲染编程(RayMarch篇)【第四卷:MetaBall In UnrealEngine4】

MY BLOG DIRECTORY:YivanLee:专题概述及目录​zhuanlan.zhihu.com

INTRODUCTION:

Metaball常常被用在游戏里制作各种魔幻的效果,下面是我在虚幻材质编辑器里实现的MetaBall。https://www.zhihu.com/video/981288051347185664

这一些列我使用了改造过的材质编辑器,能直接在编辑器里写HLSL代码并且压入Shader,实现方法可以看我之前的材质编辑器篇。

目前ray marching大概有几种机制,第一种是SphereHit的方式,这种方式能快速找到表面,这种raymarching算法适合用来绘制不透明的物体,因为我们绘制这类物体只需要知道它的表面即可。代码如下:

这种方式循环次数可能很少,可能几次循环之后就满足break条件然后弹出了。

第二种是绘制那种透明的物体,如烟雾,透明胶体。这种需要对这条光纤上的浓度进行积分。所以需要用segment hit的方式。这种方式可能就需要在一条光线上执行积分操作了,代码例子如下:

然后第三种就是光追的方式了,这种方式我这里就不多说了,暂时先跳过去。

MAIN CONTENT:

【1】2D MetaBall

Metaball的关键在于距离场组合函数上,iq给出了一种叫Polynomial smooth minimum的方法,代码如下:

// Polynomial smooth minimum by iq

float smin(float a, float b, float k)

{

float h = clamp(0.5 + 0.5 * (a - b) / k, 0.0f, 1.0f);

return lerp(a, b, h) - k * h * (1.0 - h);

}

可是这个理论的原理是什么呢,下面进行推导:

首先要对Metaball这个现象进行数学描述:对两个距离场进行平滑插值,使其在一个范围内的部分融合在一起,且仅对这个范围内的距离场进行平滑处理,对于这个范围以外的距离场还是保持它的原值。完成了数学表述以后就可以列函数了,很显然这时一个分段函数

定义:

距离场

,距离场

都是基于距离

的函数

距离场的平滑函数

平滑插值的范围

故可得分段函数

.....................................(1)

可以是很多种方法,不过这里使用线性插值

.......................(2)

其中

是线性插值的权重值,范围是0到1

因为

是一个混合的范围,把

缩放到0到1区间

...............................(3)

求关于

的导数

..................(4)

分段函数

时由

.................................(5)

为了使分段函数连续,当

时,

...............................(6)

时,

,分段函数

.............................(7)

同理当

,分段函数

.........................(8)

函数图形如下:

为了使分段函数连续,则要使分段函数分段部分的左右导数相等即(5)=(8)(6)=(7)

所以首先对(5)进行变化,加

故(4)变为

此式中

.......(9)

(5)变为

此式子中

...........................(10)

(6)变为

此式中

......................(11)

对(6)进行变换,加

故(11)式变为

由于此时

此时(10)为

由于

故此时(9)为

故连续的

函数图象为:

翻译成HLSL代码即

// Polynomial smooth minimum by iq

float smin(float a, float b, float k)

{

float h = clamp(0.5 + 0.5 * (a - b) / k, 0.0f, 1.0f);

return lerp(a, b, h) - k * h * (1.0 - h);

}

这两行代码却包含了巨量的数学原理,再次拜倒在iq大佬脚下。

【2】3D MetaBall

下面我们开始正式制作meatball吧。raymarching篇的第二篇文章我有做过一个很简单的示例:

其实这个再稍微变一下就是我们的meatball了。meatball和这个示例的区别在于距离场的构造:

我们只要让两个球衔接的地方顺滑平滑即可。

第一个sphere函数是距离场元素公式,是我们最基本的距离场模型。第二个就blob5函数就是处理球与球之间距离场的顺滑过度的。第三个函数就是构建我们距离场的函数啦。

下面是我的完整代码:

float sphere(float3 pos)

{

return length(pos) - 50.0;

}

float blob5(float d1, float d2, float d3, float d4, float d5)

{

float k = 0.1;

return -log(exp(-k*d1)+exp(-k*d2)+exp(-k*d3)+exp(-k*d4)+exp(-k*d5))/k;

}

float scene(float3 pos, float iTime)

{

float t = iTime;

float ec = 1.5;

float s1 = sphere(pos - ec * float3(cos(t*1.1),cos(t*1.3),cos(t*1.7)));

float s2 = sphere(pos + ec * float3(cos(t*0.7),cos(t*1.9),cos(t*2.3)));

float s3 = sphere(pos + ec * float3(cos(t*0.3),cos(t*2.9),sin(t*1.1)));

float s4 = sphere(pos + ec * float3(sin(t*1.3),sin(t*1.7),sin(t*0.7)));

float s5 = sphere(pos + ec * float3(sin(t*2.3),sin(t*1.9),sin(t*2.9)));

return blob5(s1, s2, s3, s4, s5);

}

float4 raymarching( in float3 ro, in float3 rd ,float iTime)

{

float4 ret = float4(0,0,0,1);

for( int i=0; i < 90; i++ )

{

float d = scene(ro, iTime);

if( d < 0.001)

{

ret.xyz = ro;

ret.w = 1;

return ret;

}

ro = ro + d * rd;

}

ret = float4(1, 1, 1, 0);

return ret;

}

float3 calcNormal( in float3 pos , float iTime)

{

const float eps = 0.002;

const float3 v1 = float3( 1.0,-1.0,-1.0);

const float3 v2 = float3(-1.0,-1.0, 1.0);

const float3 v3 = float3(-1.0, 1.0,-1.0);

const float3 v4 = float3( 1.0, 1.0, 1.0);

return normalize( v1*scene( pos + v1*eps ,iTime) +

v2*scene( pos + v2*eps ,iTime) +

v3*scene( pos + v3*eps ,iTime) +

v4*scene( pos + v4*eps ,iTime) );

}

float4 render(float3 ro, float3 rd, float time)

{

float4 outcolor = float4(1, 1, 1, 1);

float4 ret = raymarching(ro, rd, time);

float3 N = calcNormal(ret.xyz, time);

outcolor.rgb = N;

outcolor.a = ret.w;

return outcolor;

}

SUMMARY AND OUTLOOK:

再来梳理一下Raymarching的框架。

//距离场构建的基础元素,就像我们建模用的box sphere是一样的道理

float sphere(float3 pos)

{

}

//对距离场构建的基础元素进行操作,类似3Dmax建模里的布尔操作,把模型塌陷到一起

float blob5(float d1, float d2, float d3, float d4, float d5)

{

}

//构建我们的场景

float scene(float3 pos, float iTime)

{

}

//渲染距离场

float4 raymarching( in float3 ro, in float3 rd ,float iTime)

{

}

//渲染法线

float3 calcNormal( in float3 pos , float iTime)

{

}

//渲染我们最终的图像

float4 render(float3 ro, float3 rd, float time)

{

}

Enjoy it!

NEXT:第五卷:3D体纹理云​zhuanlan.zhihu.com

Reference:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值