【UnityShader】云海效果模拟与视差映射
之前在知乎上看到有大佬模拟了云海效果,正好之前项目里要用,就仔细研究一下,发现确实挺有意思的。
主要原理就是视差映射ParallaxMapping,先主要介绍一下视差映射的原理。
视差映射ParallaxMapping
说起视差映射,首先就要说起大家都不陌生的法线贴图技术。
法线贴图把法线储存在贴图的RGB通道中,在片元着色器里采样后,再计算光照,就可以在物体表面模拟凹凸的细节,让原本平滑、没什么细节的表面,可以模拟丰富细节的表面上的光照效果和反射效果等。
但是,在视线离物体很近的时候,法线贴图模拟出的凹凸效果往往就会不那么真实了。
如果配合上一张高度图,再加上视差映射技术,就可以让细节的真实感更进一步
原理
- 如上图所示,0.0的平面即为真实的模型平面,我们需要在其上模拟出起伏凹凸的高度,即下方的凹凸。
- 如果要模拟这样的凹凸,也就是正常视线看到的T0处,需要采样的得到,却是视线V通过T0的延长线交于所需模拟凹凸平面上的那一点在MainTex上的对应采样点信息
- 也就是需要求得每个点的采样uv,关于视角方向viewDir和该点高度信息height的一个偏差offset,让其对MainTex的采样满足模拟凹凸
- 那么,在法线贴图之外,还需要一张高度贴图,从0到1表示高度从最低到最高,只需要一个通道,所以可以写入其他贴图的不用的a通道
- 首先,使用viewDir.xy除以viewDir.z可以得到uv的所需偏移方向
- 注意:这里使用的viewDir是切线空间的视角方向,这样才能正好对应uv和垂直方向上的偏移
- 然后采样高度图,得到T0处的高度H(T0),H(T0) * viewDir.xy/viewDir.z即可得到uv的偏移offset
- 如果不除以视角方向viewDir的z分量,就叫带偏移上限的视差映射,而除以z分量,就是原始视差映射,原始视差映射在视角偏向掠射角时会产生错误的效果
- 这种方法性能很好,因为只用采样一次heightmap,就可以得出结果。但求得的采样点与实际交点偏差较大,因此效果一般
- 下面介绍三种可以求得较精确交点,也就是效果更好,但性能也相应下降的两种视差映射优化方法
陡峭视差映射(Steep Parallax Mapping,SPM)
- 如上图所示,把从0到1的高度平均分为若干层
- 在T0处采样高度,并逐次把当前层高度步进一个layerHeight(图中是0.125),把uv增加offset为layerHeight * (viewDir.xy/viewDir.z)
- 如果此次采样得到的高度值高于当前层的高度值,说明凹凸的平面依然在层之上,继续步进
- 如果此次采样得到的高度值低于当前层的高度值,说明凹凸的平面已经在当前层之下了,而上次的采样所得的结果还是凹凸平面在当前层之上,所以交点一定在上次采样点与此次采样点之间
- 最基础的方法就是一直循环到结束,输出当前uv作为最终使用的uv
- 三种优化方法中,这种方法性能最好,但效果最差
浮雕视差映射(Relief Parallax Mapping,RPM)
- 在SPM的基础上,更精准的寻找交点
- 也就是在最后两次的采样结果之上,使用二分法,依次逼近实际交点
- 即在T3时停止循环,并向T2步进层高的一半高度,并采样得到当前层高度与高度图采样高度的关系
- 如果层高度大于高度图高度,说明该点在T3和T2的中点与T3之间,就继续向T2步进一半的一半高度,
- 如果层高度小于高度图高度,说明该点在T3和T2的中点与T2之间,就返回T3处,再向T2步进一半的一半高度
- 周而复始,依次循环,直到层高度等于高度图高度,或者达到设置的循环最多次停止
- 三种优化方法中,这种方法效果最好,但由于循环次数过多,性能最差
视差遮蔽映射(Parallax Occlusion Mapping,POM)
这种方法是基于SPM的基础上的另一个优化版本
如图所示,POM只是对最后两次的采样结果进行简单的插值计算,没有像RPM一样进行二分搜索
nextHeight = H(T3)- currentLayerHeight
prevHeight = H(T2)-(currentLayerHeight - layerHeight)
weight = nextHeight/(nextHeight - prevHeight)
Tp = T(T2)weight + T(T3)(1.0 - weight)
POM会比RPM更容易漏掉一些小细节,在短距离内发生高度的大幅度变化的情况,使用POM也会得到错误的结果
三种优化方案中,这种方法效果适中,性能也较为优良
所以最后选中POM进行云海效果模拟的方法
POM代码
float3 ParallaxMapping(in float3 viewDir, in float2 texcoord, in float height) {viewDir.z = abs(viewDir.z) + 0.42;const float numLayers = 10;float layerHeight = height/numLayers;float3 offsetStep = layerHeight * viewDir/viewDir.z;offsetStep.z /= height;//xy记录当前uv,z记录当前LayerHeightfloat3 curTexcoord = float3(texcoord, 0); float3 prevTexcoord = curTexcoord;float curTexHeight = tex2D(_CloudTex, curTexcoord).a;float prevTexHeight = curTexHeight;//当前层高度高于高度图高度时停止循环while(curTexHeight > curTexcoord.z){prevTexcoord = curTexcoord;curTexcoord += offsetStep;prevTexHeight = curTexHeight;curTexHeight = tex2Dlod(_CloudTex, float4(curTexcoord.xy,0,0)).a;}//当高度为0的时候,直接不会进入循环,导致分母为0//所以要加一个极小值让分母不为0float w = (curTexHeight - curTexcoord.z)/(abs((curTexHeight - curTexcoord.z) - (prevTexHeight - prevTexcoord.z))+1e-7f);curTexcoord = curTexcoord * (1-w) + prevTexcoord * w;curTexcoord.z = curTexHeight * (1-w) + prevTexHeight * w;//输出一个float3类型的变量,xy为偏差后的uv,z为高度return curTexcoord; }
模拟自阴影
自阴影,即为模型自身的一部分阻挡住了光线射向另一部分,导致另一部分产生阴影的现象
而一个平面显然是不会有自阴影,但现在使用视差映射在平面表面模拟了凹凸,那么自阴影现象也是需要存在的
同样可以使用视差映射接近的算法,确定一个点是否在阴影中
首先使用刚刚视差映射得到的最终uv和最终高度h,依次向光源方向步进
如果层高度小于采样点高度,就说明该点在表面之下,光线被阻挡,如果是计算硬阴影,直接设置为阴影;如果是计算软阴影,增加阴影系数,继续步进
如果层高度大于采样点高度,就说明该点在表面之上,光线没有被阻挡
软阴影需要计算从起始点到最终不阻挡光线的那个点,而阴影系数根据当前层深度和当前高度图深度之间的差异计算,计算软阴影系数的公式如下
代码如下
float ParallaxSoftShadow(in float3 lightDir, in float2 texcoord, in float height) {float shadowMultiplier = 1;const float minLayers = 25;const float maxLayers = 50;lightDir.z = abs(lightDir.z) + 0.42;if(dot(float3(0,0,1), lightDir) > 0){float numSamplesUnderSurface = 0;shadowMultiplier = 0;//光线靠近垂直方向时,减少分层,光线偏离垂直方向时(更为倾斜),增加分层,在保证效果的前提下节约性能float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0,0,1), lightDir)));float layerHeight = height/numLayers;half2 offsetStep = lightDir.xy/lightDir.z/numLayers;float curLayerHeight = height - layerHeight;float2 curTexcoord = texcoord + offsetStep;float heightFromTexture = tex2D(_CloudTex, curTexcoord).a;int stepIndex = 1;while(curLayerHeight > 0){if(heightFromTexture < curLayerHeight){numSamplesUnderSurface +=1;shadowMultiplier = max(shadowMultiplier, (curLayerHeight - heightFromTexture)*(1.0 - stepIndex/numLayers));}stepIndex += 1;curLayerHeight -= layerHeight;curTexcoord += offsetStep;heightFromTexture = tex2Dlod(_CloudTex, float4(curTexcoord, 0,0)).a;}shadowMultiplier = numSamplesUnderSurface < 1 ? 1 : 1-shadowMultiplier;}return shadowMultiplier; }
模拟云海效果,只用高度图即可,把高度图写入主图的a通道,使用视差映射的算法,得到偏差后的坐标,采样MainTex,最后再乘上阴影系数即可。
完整代码如下
Shader "Custom/Scene/Cloud"
{Properties{_CloudTex ("Cloud Texture", 2D) = "white"{}_CloudColor ("Cloud Color", Color) = (1, 1, 1, 1)_CloudSpeed ("Cloud Speed", Vector) = (2, 1, 0, 0)_Height ("Height", Range(0, 1)) = 0.5}SubShader{Tags { "Queue"="Transparent" "RenderType"="Opaque" }Pass{Blend SrcAlpha OneMinusSrcAlphaCGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"#include "Lighting.cginc"sampler2D _CloudTex;float4 _CloudTex_ST;fixed4 _CloudColor;float4 _CloudSpeed;float _Height;struct v2f{float4 vertex : SV_POSITION;float2 uv : TEXCOORD0;float3 tanViewDir : TEXCOORD1;float3 tanLightDir : TEXCOORD2;};v2f vert (appdata_tan v){v2f o;o.vertex = UnityObjectToClipPos(v.vertex);o.uv = TRANSFORM_TEX(v.texcoord, _CloudTex) + frac(_Time.x * _CloudSpeed.xy);TANGENT_SPACE_ROTATION;o.tanViewDir = mul(rotation, ObjSpaceViewDir(v.vertex));o.tanLightDir = mul(rotation, ObjSpaceLightDir(v.vertex));return o;}float3 ParallaxMapping(in float3 viewDir, in float2 texcoord, in float height){viewDir.z = abs(viewDir.z) + 0.42;const float numLayers = 10;float layerHeight = height/numLayers;float3 offsetStep = layerHeight * viewDir/viewDir.z;offsetStep.z /= height;//xy记录当前uv,z记录当前LayerHeightfloat3 curTexcoord = float3(texcoord, 0); float3 prevTexcoord = curTexcoord;float curTexHeight = tex2D(_CloudTex, curTexcoord).a;float prevTexHeight = curTexHeight;//当前层高度高于高度图高度时停止循环while(curTexHeight > curTexcoord.z){prevTexcoord = curTexcoord;curTexcoord += offsetStep;prevTexHeight = curTexHeight;curTexHeight = tex2Dlod(_CloudTex, float4(curTexcoord.xy,0,0)).a;}float w = (curTexHeight - curTexcoord.z)/(abs((curTexHeight - curTexcoord.z) - (prevTexHeight - prevTexcoord.z))+1e-7f);curTexcoord = curTexcoord * (1-w) + prevTexcoord * w;curTexcoord.z = curTexHeight * (1-w) + prevTexHeight * w;return curTexcoord;}float ParallaxSoftShadow(in float3 lightDir, in float2 texcoord, in float height){float shadowMultiplier = 1;const float minLayers = 25;const float maxLayers = 50;lightDir.z = abs(lightDir.z) + 0.42;if(dot(float3(0,0,1), lightDir) > 0){float numSamplesUnderSurface = 0;shadowMultiplier = 0;//光线靠近垂直方向时,减少分层,光线偏离垂直方向时(更为倾斜),增加分层,在保证效果的前提下节约性能float numLayers = lerp(maxLayers, minLayers, abs(dot(float3(0,0,1), lightDir)));float layerHeight = height/numLayers;half2 offsetStep = lightDir.xy/lightDir.z/numLayers;float curLayerHeight = height - layerHeight;float2 curTexcoord = texcoord + offsetStep;float heightFromTexture = tex2D(_CloudTex, curTexcoord).a;int stepIndex = 1;while(curLayerHeight > 0){if(heightFromTexture < curLayerHeight){numSamplesUnderSurface +=1;shadowMultiplier = max(shadowMultiplier, (curLayerHeight - heightFromTexture)*(1.0 - stepIndex/numLayers));}stepIndex += 1;curLayerHeight -= layerHeight;curTexcoord += offsetStep;heightFromTexture = tex2Dlod(_CloudTex, float4(curTexcoord, 0,0)).a;}shadowMultiplier = numSamplesUnderSurface < 1 ? 1 : 1-shadowMultiplier;}return shadowMultiplier;}fixed4 frag (v2f i) : SV_Target{float3 uv = ParallaxMapping(normalize(i.tanViewDir), i.uv, _Height);float shadowMultiplier = ParallaxSoftShadow(normalize(i.tanLightDir), uv.xy, uv.z);half4 c = tex2D(_CloudTex, uv.xy) * _CloudColor;c.rgb *= _LightColor0.rgb * (shadowMultiplier);return c; }ENDCG}}
}
【UnityShader】云海效果模拟与视差映射相关推荐
- 视差图转为深度图_Parallax Mapping视差映射:模拟冰块
继续练习一个模拟冰块的效果,模拟的是一个不透明内部有杂质的冰块,内部杂质用视差映射来实现,表面就是简单的法线贴图+Cubemap反射采样,也可以直接只计算高光不反射图案.文章会把视差映射讲一下,算是对 ...
- 视差映射:更逼真的纹理细节表现(上):为什么要使用视差映射
前言 视差贴图技术是相对于传统的法线贴图技术所做的一项改进 视察贴图技术可以给场景里要渲染的游戏物体增加物体表面的凹凸细节,在过去为了表现物体的表面细节,通常会使用法线贴图,那么为什么又要使用视差贴图 ...
- 网页视差滚动效果html,CSS视差滚动效果
一.效果Demo先行~视差滚动效果大家可能都听过,基本上都是JS实现的,有对应插件 - Parallax.js . 实际上,如果你对兼容性要求不是很高,比方说忽略IE浏览器,则我们使用简单的几行CSS ...
- OpenGL parallax mapping视差映射的实例
OpenGL parallax mapping视差映射 先上图,再解答. 完整主要的源代码 源代码剖析 先上图,再解答. 完整主要的源代码 #include <glad/glad.h> # ...
- Unity Shader 视差映射
老规矩先上图: 视差映射是立体表现中比较常用的手段,但在具体理解的时候需要一定的抽象能力,下面分享一段非常简易的案例讲清什么是视察映射. 如下图所示,在未偏移以前假设取的是点A,这时需要在高度图中取得 ...
- cocos shader(云海效果)
用shader 实现云海效果 文章学习:https://forum.cocos.org/t/topic/128595 1.用一张噪声图片 2.噪声图片是灰度的.所以像素的颜色r=g=b 3.利用噪声贴 ...
- jQuery实现的简单文字提示效果模拟title
模拟title实现效果,可以修改文字的样式,换行等. 文件下载: 先看下效果截图: 代码分析: <!-- 引用jQuery --><script src="jquery.j ...
- 塑料保鲜膜效果模拟:ABC文件用BlendShape导出到mayaUnity
将ABC文件用BlendShape导出FBX 一.应用场景: 此项目为高中化学实验案例,为实现保鲜膜 模拟动画,这里通过使用MarvelousDesigner来结算出动画效果,导出ABC格式带动画后, ...
- UnityShader——流光效果
声明:本文为个人笔记,用于学习研究使用非商用,内容为个人研究及综合整理所得,若有违规,请联系,违规必改. UnityShader学习目录 文章目录 UnityShader学习目录 前言 一.实现原理 ...
最新文章
- 用python解算法谜题_编程的乐趣 用Python解算法谜题
- 以某个字符开始_小白从零开始数据分析01—Excel常用公式汇总(数据清洗)
- 【Qt】QModbusRtuSerialMaster类
- 使用IntelliJ IDEA11创建Java Web程序
- 多线程多进程解析:Python、os、sys、Queue、multiprocessing、threading
- 深入浅出JVM-内存模型
- sql怎样删除重复值
- cn域名保护隐私_为什么域名隐私保护如此重要
- c语言程序设计的常用算法,《C语言程序设计的常用算法.doc
- Faster R-CNN源码中RPN的解析(自用)
- ubuntu14.04-安装flash
- 魔板(康托展开去重)
- QOS端口限速EMAIL流量限速
- data在python_Fake data的使用和产生 - Python篇
- 小牛电动Q2财报:国外不乐观,国内狂下沉
- 撸了一次 Js 代码
- 【Leetcode】精选算法top200道(二)
- bat脚本--android adb一键截图
- python用双重循环输出菱形图案_使用循环创建菱形图案
- pressOn在线制作流程图、思维导图、架构图等
热门文章
- python求最大公约数
- Centos7安装部署免费confluence wiki
- 特斯拉Model Y 标准续航实测
- codeVS 3116 高精度练习之加法
- server接收dtu透传代码_深入ASP.NET Core源代码之 - Web Server Kestrel
- app版本更新 下载apk后没有跳转到安装页面
- 操作分区表对global和local索引的影响
- Small Object Detection using Context and Attention(论文阅读笔记)
- Java 方法(重载,递归)
- QIIME 2教程. 09数据导入Importing data(2021.2)