使用raymarching实现体积雾

前段时间在学习rayMarching技术,看到许多大佬搞的体积雾。我也做了一个简单的。

RayMarching原理

RayMarching(光线步进)严格上来说应该是后期的一种。
一个简单的实现是在屏幕上绘制球体:从光线原点射出光线,先以原点为中心调用一次距离函数,计算出此点到球形表面的最短距离。如果小于设置好的误差值,就视为接触到物体,返回距离值。如果判断没接触到,则沿着光线向量前进刚刚计算出的距离值得到新的圆心继续求距离,直到判定为接触物体。如果前进步数过多,超出设定最高步数或者前进距离超出最远距离,就视为无法接触物体。
使用raymarching实现体积雾_第1张图片
如果要像上图那样检测多个物体,就可以分别计算原点到多个物体表面的距离,然后取最小值,就能同时检测多个物体。如果取最大值则是将图形取并集,再合理地对距离值取负值,就能完美地完成几何体的布尔运算。你也可以在这个网站中找到许多几何体的距离函数和一些有趣的融合算法。
如果我们对每个像素都进行上述的步进操作,就能得到物体的深度值,也就能开始着色了。法线的计算是根据计算附近像素点的深度值得出。如果想要仅仅显示几何体,做到这一步就能按照传统的光照方案计算出图案,值得注意的是,由于RayMarching技术是后期处理,因此要考虑到如何与背景图像进行融合。

float3 getNormal(float3 p)//近似计算出法线
{
float2 offset = float2(0.001f,0.0f);
//float d = getDist(p);
float3 n= float3(
    getDist(p+offset.xyy)-getDist(p-offset.xyy),
    getDist(p+offset.yxy)-getDist(p-offset.yxy),
    getDist(p+offset.yyx)-getDist(p-offset.yyx)
);
return normalize(n);
}

简单体积雾运算

接下来就可以按照乐乐老师的shadertoy项目的体积雾运算再次进行颜色绘制。这是一个简单的体积雾颜色绘制。基本原理就是在计算好的几何体内,按照几乎相同的步长进行步进操作,按照一定的噪声算法对像素增加不透明度,并且按照当前点到球心的距离进行着色。很显然。这并不是一个基于物理的体积雾着色,但是效果非常不错。我在引入着色代码后进行了一定的更改:

float3 raymarchingCloud(float3 ro, float3 rd, float t,float3 backCol,float depth)
	{
	float4 sum = float4(0.0,0.0,0.0,0.0);
	float3 pos = ro + rd * t;
	for (int i = 0; i < 30; i++) {
		float4 col;
		float radiusSum = 0;
		for(int i=0; i<_SphereNum; i++)
		{
			radiusSum += _SpherePositions[i].w;
		}
		radiusSum /= _SphereNum;
		float dist = radiusSum+_Fog*getDist(pos);
		if ( sum.a > 0.99 || t>depth ) continue;
		float den = density(pos, dist);
		col = float4(color(den, dist), den*_FogDensity);
		col.rgb *= col.a;
		sum = sum + col*(1.0 - sum.a); 
		t += max(0.05, 0.02 * t);
		pos = ro + rd * t;
	}
	sum = clamp(sum, 0.0, 1.0);
	backCol = lerp(backCol, sum.xyz, sum.a);
	return backCol;
}

我将原算法中对当前点到球心的距离算法改为了到几何体表明的距离。这样就能兼容不同几何形状的烟雾,甚至多个烟雾效果。同时我也增加了两个能够管理烟雾浓度的参数,方便使用。
使用raymarching实现体积雾_第2张图片
上面就算体积雾的效果。效果优点:
1简单快速,占用较少
2深度检测,能较好的与环境融合
3支持多个烟雾
4效果优秀
5使用Object和脚本进行烟雾管理
缺点:
1并非基于物理,并不能很好的与光照进行交互
2z轴的烟雾融合并不算很好
为了解决以上两个问题,还准备找个时间做个基于物理体积雾系统。

你可能感兴趣的:(shader,unity)