Unity通过深度图做有交互效果的水泡沫

news2025/2/27 5:04:39

通过深度图做交互水泡沫

大家好,我是阿赵。
这里做一个有交互效果的水面,物体浸入水面时,会根据物体的形状,有一圈水泡沫的效果,并且水泡沫的形状会跟随这物体变化。由于想做得稍微完整一点,又不想其他效果太强泡沫的风头,所以简单加了一个水面纹理流动的效果,却没有加水的反射高光之类的效果,主要是突出泡沫。
而这篇文章主要想说明的就是怎样获取深度图,和怎样求出模型重叠的部分和重叠的边缘。
在这里插入图片描述

一、场景的搭建。

在这里插入图片描述

这个场景很简单,只是一个用Cube围起来的水池,然后有一个水面的面片,水里面有一个球和一个Cube立方体。
为了能让最后的泡沫形状变化看得更容易,所以我给球和立方体简单的做了一个动画,球是在水中浮沉,然后立方体是在水面移动。

二、深度图计算

在这里插入图片描述

在Unity渲染的过程中,是会产生一张深度图信息的。
通过在Shader里面声明深度图,可以获取得到
//声明获取摄像机深度图
UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
uniform float4 _CameraDepthTexture_TexelSize;
然后因为需要和屏幕渲染的图形深度匹配,所以还需要用屏幕坐标的齐次坐标去采样这张深度图
//计算齐次坐标
float4 screenPosNorm = screenPos / screenPos.w;
screenPosNorm.z = (UNITY_NEAR_CLIP_VALUE >= 0) ? screenPosNorm.z : screenPosNorm.z * 0.5 + 0.5;
//采样深度贴图,由于深度图并不是线性渐变过渡的,所以用LinearEyeDepth转换成线性变化
float screenDepthVal = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenPosNorm.xy).r);

由于深度图只需要r通道的信息,所以看起来是红色深浅渐变的一种效果。

忽略了颜色,只看黑白作为深度,会是这样。
在这里插入图片描述
说明一下,对于眼睛观察的角度来说,这个深度图是非线性效果,所以不会出现很明显的线性渐变,这里需要使用LinearEyeDepth去把这个深度值转换成线性。

接下来,如果我们用屏幕的齐次坐标和深度相减然后去绝对值,可以求出模型边缘相交的部分
float distanceDepthVal = abs((screenDepthVal - LinearEyeDepth(screenPosNorm.z));
在这里插入图片描述

这时候看到黑色的部分就是模型穿插的部分,范围比较大,把整个球和立方体插入水中的部分都看到了。
我们可以通过一个距离值来控制一下黑色的范围:
float distanceDepthVal = abs((screenDepthVal - LinearEyeDepth(screenPosNorm.z)) / (distance));
在这里插入图片描述

这时候,就还有刚刚开始相交的小部分地方会有黑色。
由于我们想在边缘叠加东西,所以一般来说会用取反的方法,把黑白翻转,变成这样
在这里插入图片描述

然后再给白色的部分叠加泡沫的贴图。我这里就随便给一张噪声图来模拟了泡沫了:
在这里插入图片描述

再加上衬托用的水基础波纹的颜色,结果会变成这样:

在这里插入图片描述

上面计算模型相交的深度渐变的过程,一般成为DepthFade,我把它独立成一个方法,以后有需要就可以复用。其实DepthFade是一种比较常用的技术,所以在ASE之类的编辑器里面,直接就有现成的节点可以用
在这里插入图片描述

三、完整Shader:

Shader "azhao/WaterDepth"
{
	Properties
	{
		_distance("distance", Range( 0 , 2)) = 0
		_WaterColor("WaterColor", Color) = (0.5471698,0.5471698,0.5471698,0)
		_noiseMap("noiseMap", 2D) = "white" {}
		_speed("speed", Vector) = (0,0,0,0)
		_speed2("speed2", Vector) = (0,0,0,0)
		_depthMul("DepthMul", Range( 0 , 2)) = 1
		_WaterTex("WaterTex", 2D) = "white" {}
		_water1Mul("water1Mul", Range( 0 , 1)) = 0
		_water2Mul("water2Mul", Range( 0 , 1)) = 0
		_alpha("alpha", Range( 0 , 1)) = 1

	}
	
	SubShader
	{
		
		
		Tags { "Queue"="Transparent" }
	LOD 100


		Blend SrcAlpha OneMinusSrcAlpha
		Cull Back
		ZWrite Off
		Pass
		{
			Name "Unlit"
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
	
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"



			struct appdata
			{
				float4 vertex : POSITION;
				float4 color : COLOR;
				float2 uv : TEXCOORD0;

			};
			
			struct v2f
			{
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				float4 screenUV : TEXCOORD1;

			};

			uniform float4 _WaterColor;
			uniform sampler2D _WaterTex;
			uniform float4 _WaterTex_ST;
			uniform float2 _speed;
			uniform float _water1Mul;
			uniform float2 _speed2;
			uniform float _water2Mul;
			uniform sampler2D _noiseMap;
			uniform float4 _noiseMap_ST;
			
			uniform float _distance;
			uniform float _depthMul;
			uniform float _alpha;
			//声明获取摄像机深度图
			UNITY_DECLARE_DEPTH_TEXTURE(_CameraDepthTexture);
			uniform float4 _CameraDepthTexture_TexelSize;

			//计算相交深度渐变的值
			float DepthFade(float4 screenPos, float distance)
			{
				//计算齐次坐标
				float4 screenPosNorm = screenPos / screenPos.w;
				screenPosNorm.z = (UNITY_NEAR_CLIP_VALUE >= 0) ? screenPosNorm.z : screenPosNorm.z * 0.5 + 0.5;
				//采样深度贴图,由于深度图并不是线性渐变过渡的,所以用LinearEyeDepth转换成线性变化
				float screenDepthVal = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, screenPosNorm.xy).r);
				//关键的一步,屏幕的齐次坐标的z轴同样转换成线性,然后和深度图的值相减
				float distanceDepthVal = abs((screenDepthVal - LinearEyeDepth(screenPosNorm.z)) / (distance));
				return distanceDepthVal;
			}

			v2f vert ( appdata v )
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				//ComputeScreenPos一定要在顶点程序计算
				float4 screenPos = ComputeScreenPos(o.pos);
				o.screenUV = screenPos;				
				o.uv = v.uv;
				return o;
			}
			
			half4 frag (v2f i ) : SV_Target
			{
				float2 uv_WaterTex = i.uv.xy * _WaterTex_ST.xy + _WaterTex_ST.zw;
				float2 uv_noiseMap = i.uv.xy * _noiseMap_ST.xy + _noiseMap_ST.zw;
				//输入屏幕UV坐标和距离,计算需要显示的深度值
				float distanceDepthVal = DepthFade(i.screenUV, _distance);
				//通过深度值计算泡沫显示的范围
				float foamVal = (1-saturate(distanceDepthVal)) * _depthMul;
				//为了让边缘产生不规则,所以读取了一张噪声图,再乘以上面的泡沫深度值
				float4 foamCol = tex2D(_noiseMap, uv_noiseMap) * foamVal;

				//为了demo看起来稍微完整而加的2个水波纹叠加,不是重点,不用在意
				float4 waveCol1 = tex2D(_WaterTex, (uv_WaterTex + (_speed * _Time.y))) * _water1Mul;
				float4 waveCol2 = tex2D(_WaterTex, (uv_WaterTex + (_speed2 * _Time.y)))* _water2Mul;
				//最终颜色是水波纹+泡沫
				float3 finalRGB = (_WaterColor*(waveCol1 + waveCol2) + foamCol).rgb;
				finalRGB = clamp(finalRGB, float3( 0,0,0 ) , float3( 1,1,1 ) );
				half4 finalCol =half4( finalRGB, _alpha);
				return finalCol;
			}
			ENDCG
		}
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/520622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ipad可以用别的品牌的手写笔吗?便宜的ipad电容笔

而对于那些把ipad当做学习工具的人而言,苹果Pencil就成了必备品。但因为苹果Pencil太贵了,学生们买不起。因此,最好的选择还是平替电容笔。作为一个ipad的忠实用户,同时也是一个数字热爱着,这两年来,我一直…

谈谈ChatGPT对中国教育的影响与挑战,我们该怎么办?

ChatGPT对中国教育的影响 1. 个性化教学 通过分析学生的学习习惯和问题,AI可以为每个学生提供个性化的学习路径。例如,如果一个学生在数学上表现出困难,AI可以提供更多的数学练习和教学资源。 2. 在线教育 AI可以作为在线课程的一部分提供帮助…

verilog手撕代码5——计数器(置位、加减、环形、扭环形、格雷码计数器实现)

文章目录 前言一、二进制计数器(n位 2^n状态)1.1 可置位计数器1.2 加减计数器 二、环形计数器(n位 n状态)2.1 移位寄存器首尾相连构成环形计数器 三、扭环形计数器/Johnson计数器(n位 2*n状态)3.1 移位寄存…

路径规划算法:基于海鸥优化的路径规划算法- 附代码

路径规划算法:基于海鸥优化的路径规划算法- 附代码 文章目录 路径规划算法:基于海鸥优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要:本文主要介绍利用智能优化算法海鸥…

带头双向循环链表原来这么简单?

☃️个人主页:fighting小泽 🌸作者简介:目前正在学习C语言和数据结构 🌼博客专栏:数据结构 🏵️欢迎关注:评论👊🏻点赞👍🏻留言💪&…

win/mac电脑最好用的录屏软件Camtasia 2023官方中文版

Camtasia 2023专业的屏幕录制和视频剪辑软件 3000多万专业人士在全球范围内使用Camtasia展示产品,教授课程,培训他人,以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和Mac上进行录屏和剪辑创作专业外观的视频变得更为简单。…

Kali-linux使用Maltego收集信息

Maltego是一个开源的漏洞评估工具,它主要用于论证一个网络内单点故障的复杂性和严重性。该工具能够聚集来自内部和外部资源的信息,并且提供一个清晰的漏洞分析界面。本节将使用Kali Linux操作系统中的Maltego,演示该工具如何帮助用户收集信息…

【LeetCode】312. 戳气球

312. 戳气球(困难) 解法一:动态规划 首先看一个区间: 区间(i,j) 是一个开区间,因为我们只能戳爆 i 和 j 之间的气球,不能戳爆索引为 i 和 j 的气球。 我们不妨考虑该区间内被戳爆的最后一个气球&#xff…

UNIAPP框架中获取当前定位信息

概述 准备生成自己的Android证书。高德地图生成自己的key并配置mainfest.json。设置左上角图标并完成配置。页面中获取经纬度坐标。依据经纬度坐标获取位置信息。更新图标信息完成展示。 第一步:生成Android证书 参考资料 Android平台云端打包证书使用说明 https…

java类和对象之认识1

文章目录 一、Java类二、Java成员变量三、Java方法四、Java类的构造方法五、Java创建对象六、Java使用对象七、Java对象的引用和实体八、Java类与程序的基本结构九、Java参数传值总结 一、Java类 类是组成Java程序的基本要素,一个Java应用程序就是由若干个类所构成…

永磁同步电机(PMSM)无传感器控制基于龙伯格观测器Matlab/Simulink仿真分析

文章目录 前言一、龙伯格观测器1.1.龙伯格观测器的原理2.2.龙伯格观测器的误差2.3.PMSM龙伯格观测器的建立 二、Matlab/Simulink仿真分析2.1.仿真电路分析2.1.1.电机控制模式切换10ms任务2.1.2.速度环控制2ms任务2.1.3. 电流环控制50us任务2.1.4.电机主电路 2.2.仿真结果分析 总…

单调栈模板总结及应用

文章和代码已经归档至【Github仓库:https://github.com/timerring/algorithms-notes 】或者公众号【AIShareLab】回复 算法笔记 也可获取。 文章目录 单调栈模板栈算法模板例题:单调栈基本思路code 单调栈模板 栈:先进后出。 队列&#xff…

shell数组(包含排序算法)

目录 一:数组定义方法 1、方法一 2、方法二 ​3、方法三 ​4、方法四 5、判断数组是否完整 (1)方法一 (2)方法二:通过脚本 二:获取数组值 1、获取数组长度 2、获取数组数据列表 3、获…

ipad专用笔和其他笔有什么区别?第三方电容笔了解

要是ipad仅仅用来玩游戏,看电视的话,也太浪费ipad平板的作用了。ipad这个产品,用途还是很广的,既能用于职业绘画,又能用于学习笔记。许多人都觉得苹果的电容笔非常好,但与普通的电容笔相比,它的…

Python常用的开发工具合集

​ Python是一种功能强大且易于学习的编程语言,被广泛应用于数据科学、机器学习、Web开发等领域。随着Python在各个领域的应用越来越广泛,越来越多的Python开发工具也涌现出来。但是,对于新手来说,选择一款合适的Python开发工具可…

美本统计学基础笔记

美本统计学基础笔记 1.基础2.概率3.离散概率分布Discrete Probability Distributions4.The Normal Probability Distribution正态概率分布5.Sampling Distributions采样分布6.Large-Sample Estimation大样本估计7.Large-Sample Tests of Hypotheses假设的大样本检验 1.基础 左…

颜值打分代码实例讲解(paddle框架)

数据集介绍 训练数据集为华南理工大学实验室公布的数据集 数据中包含500张女生图片,分别由70人进行打分,最终取平均值即为该图片的打分情况。 我们在实践中将图片分值设定为1-5。 500张图片中,450张用于训练,50张用于验证。 任…

快速落地基于“AIGC+数字人”的数字化内容生产

谁不想有一个可爱的数字人形象呢?在日常的工作和娱乐中,越来越多的数字人虚拟形象与大家见面,他们可以是主播,也可以是语音助手,还可以是你自己的虚拟宠物。只有更快更精准的生成数字人,才能让数字人更加普…

【JS】1680- 重学 JavaScript API - Beacon API

❝ 前期回顾:1.Page Visibility API 2.Broadcast Channel API ❞ 1. 什么是 Beacon API 1.1 概念介绍 Beacon API 是 HTML5 提供的一种新的浏览器 API,可以用于在浏览器后台异步地发送数据,而不影响当前页面的加载和性能。通过 Beacon API&am…

FE_Vue学习笔记 常用指令的学习【v-model filters v-text v-html v-cloak v-once v-pre 自定义指令】

1 收集表单数据 v-model 收集表单数据&#xff1a; 若&#xff1a;<input type"text">&#xff0c;则v-model收集的是value的值&#xff0c;用户输入的就是value值。 若&#xff1a;<input type"radio">&#xff0c;则v-modle收集的是value的…