世界杯积分榜_世界杯几年一届 - fjmzsy.com

【UnityShader】云海效果模拟与视差映射

9725

之前在知乎上看到有大佬模拟了云海效果,正好之前项目里要用,就仔细研究一下,发现确实挺有意思的。 主要原理就是视差映射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记录当前LayerHeight

float3 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

//所以要加一个极小值让分母不为0

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;

//输出一个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 OneMinusSrcAlpha

CGPROGRAM

#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记录当前LayerHeight

float3 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

}

}

}

身份证5开头的是哪个省的,以及身份证数字含义
工业现场总线技术,Profinet、EtherCAT与Modbus的通信协议对比