在Unity中实现基于粒子的水模拟(三:混合屏幕)

在Unity中实现基于粒子的水模拟(三:混合屏幕)



前言

经过了前面的纹理,我们就到了最后的混合到屏幕阶段了,这个阶段的逻辑不是很难,本来是要基于这篇文章的公式来往上套的,不过由于本人水平问题并没有看懂作者的具体含义,于是就大概的实现了一下,估计出入挺大的。
效果看图:
在这里插入图片描述

项目链接:注意是在其中的master分支中

一、着色算法介绍

我们将颜色分为两部分,也就是折射反射

1.折射

折射要有最好的效果应该是要根据当前像素执行SSR算法,也就是计算出折射法线后,在通过深度重建的世界坐标中,用最接近折射位置的那个像素的颜色值作为折射的颜色值,但是这种方式太昂贵,所以没有采用这种方式。

本文用的方式是最简单的折射采样方式,根据法线以及液体宽度指进行uv偏移来获取折射的颜色值(因为实在看不懂作者到底什么意思,所以就这样简单实现了 )。

2.反射

反射的计算比较简单,直接传递一个Cube贴图,然后根据反射方向进行纹理采样即可。

最后将两个颜色根据透光度进行混合就计算完成了。

二、准备纹理

1.获取纹理

在SRP中可以通过设置CommandBuffer的SetRenderTarget将宽度、法线、深度图专门用特定的ShaderTagId指定,这样可以渲染两次就得到所有3张纹理,因为法线可以和深度一次得到。

但是在Build-in中我没有找到指定深度图的方法,导致后面模糊时不能很好的将深度值提取出来模糊,不过因为我们这个计算方式只使用了xy的法线值,可以将深度值存储在法线纹理的b通道中。
裁剪空间获取深度的公式:
Z= ( f * z - f * n ) / ( ( f - n ) * z)

其中f是远裁剪面、n是近裁剪面、z是视角空间z值。
代码表示:

//获取深度的公式,这个深度值就是深度图中的深度值
//_ProjectionParams是Unity提供的数据,y是摄像机的近平面、z是远平面
//i.pos是裁剪空间坐标,其中的w值存储着视角空间的z值
depth = (_ProjectionParams.z * i.pos.w - _ProjectionParams.z * _ProjectionParams.y)/
	((_ProjectionParams.z - _ProjectionParams.y) * i.pos.w)
//Unity会将深度图值转化,让近平面的精度更高,远平面精度可以低一点,
//因为浮点数在接近0时精度会上升
depth = 1- depth;

2.模糊纹理

模糊纹理使用的算法是双边滤波,这个算法可以让球体的边界混合起来,同时不会像高斯模糊一样让颜色值与周围混合,导致颜色不能准确对应该像素。

核心代码:

float CompareColor(float4 col1, float4 col2)
{
	float l1 = LinearRgbToLuminance(col1.rgb);
	float l2 = LinearRgbToLuminance(col2.rgb);
	return smoothstep(_BilaterFilterFactor, 1.0, 1.0 - abs(l1 - l2));
}

float4 BilateralFilterFragment (Varyings input) : SV_TARGET{
	float2 delta = _PostFxEffectSource_TexelSize.xy * _BlurRadius.xy;
	//采集Normal的颜色值
	float4 col =   SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV);
	float4 col0a = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV - delta);
	float4 col0b = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV + delta);
	float4 col1a = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV - 2.0 * delta);
	float4 col1b = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV + 2.0 * delta);
	float4 col2a = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV - 3.0 * delta);
	float4 col2b = SAMPLE_TEXTURE2D(_PostFxEffectSource, sampler_linear_clamp, input.screenUV + 3.0 * delta);
	
	float w = 0.37004405286;
	float w0a = CompareColor(col, col0a) * 0.31718061674;
	float w0b = CompareColor(col, col0b) * 0.31718061674;
	float w1a = CompareColor(col, col1a) * 0.19823788546;
	float w1b = CompareColor(col, col1b) * 0.19823788546;
	float w2a = CompareColor(col, col2a) * 0.11453744493;
	float w2b = CompareColor(col, col2b) * 0.11453744493;
	
	float3 result;
	result = w * col.rgb;
	result += w0a * col0a.rgb;
	result += w0b * col0b.rgb;
	result += w1a * col1a.rgb;
	result += w1b * col1b.rgb;
	result += w2a * col2a.rgb;
	result += w2b * col2b.rgb;
	
	result /= w + w0a + w0b + w1a + w1b + w2a + w2b;

	return float4(result, 1);
}

双边滤波也需要像高斯模糊一样执行两次,对垂直以及水平进行模糊,具体实现可以参考这篇文章

2.混合主纹理上

这部分就和正常的后处理流程一样了,具体后处理如何实现就不赘述了,这里只描写着色的核心代码。

首先第一步,进行深度对比,判断该像素是否需要进行液体着色,也就是是否被遮挡。

	if(currentDepth >= waterDepth)
		return float4(currentColor, 1);		//返回原本像素颜色

获得折射以及反射的颜色值:

	float3 viewDirection = normalize( _WorldSpaceCameraPos - worldPos );
	float3 reflectDir = normalize(-viewDirection + 2 * waterNormal);	//反射方向
	//反射颜色
	float3 specular = 
		SAMPLE_TEXTURECUBE_LOD( _WaterReflectCube, 
			sampler_WaterReflectCube, reflectDir, 0 ).rgb;
	float2 ofssetUV = (-viewDirection - 
		0.2 * waterNormal).xy * waterWidth * 0.2 + input.screenUV;
	//折射颜色
	float3 refrColor = 
		SAMPLE_TEXTURE2D_LOD(_PostFxEffectSource, 
			sampler_linear_clamp, ofssetUV, 0).rgb;
	//混合液体颜色,transLight是透光度
	refrColor = lerp(refrColor * _WaterColor.rgb, refrColor, transLight);

按照大佬文章的公式:
在这里插入图片描述
其中R1和R2分别是水和空气的折射率。
代码:

	float n_0 = pow( (_WaterData.y - _WaterData.x) / (_WaterData.y + _WaterData.x), 2 );

	float fresnel = 
		( n_0 + (1 - n_0) * pow( 1 - dot(viewDirection, waterNormal), 5 ) )
		 * waterWidth;

	float3 finalCol = refrColor * (1 - fresnel) + fresnel * specular;

总结

导致这个粒子水系列就正式完成了,我觉得这个实现的水不适合作为“水”,不过如果拿来做流体模拟的话还是可以的,因为有深度、法线图,用来实现牛奶等BSDF等渲染效果是很不错的,毕竟BSDF的一个难点是宽度计算,有了宽度其他计算就和BRDF没什么区别了。

1.现有问题

目前本场景的粒子并没有进行软粒子处理,如果有必要的话可以在渲染宽度时将深度图传入,在深度相近时进行透明,而且可以根据深度值进行Hi-z剔除,优化效果。

本项目的根据都是基于Unity的物理检测的,这个部分是损耗最大的部分,也是最容易出bug的部分,如果之后场景需要的话建议直接设置固定的流动方向,不进行真正的时时物理检测,刷新流动方向,这样CPU占用太大了。

理论上这些粒子着色都是要用ComputeShader写的,但是我在写这个系列时还不懂这些,在最近研究SRP时才知道有这个东西。
不过由于模拟时数据量太大了,这种直接将数据传递到顶点的方式说不定更适合物理模拟,不过之前实现的粒子系统可能就真的需要更新了,之后应该会更新Compute Shader进行剔除的粒子系统,更加全面的实现Unity新版粒子系统。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值