本文共 4685 字,大约阅读时间需要 15 分钟。
为了更好的理解本文所介绍的内容,建议您阅读:
所需图片
Unity自带的着色器可以帮助我们实现视差贴图的效果
现在我们来了解一下其背后的原理
目录
视差贴图可以使物体表面更具有立体感,相对于法线贴图,视差贴图更加真实。
向量V代表观察者观察方向,红色曲线代表高度图所表示的数值,底部黑色平面代表纹理贴图。
假设平面位移到高度曲线所表示的位置,那么观察者会看到B点(此处的B点不是高度图上的B点,而是B点对应纹理贴图的采样点,上图只是为了使读者更好的理解,下文同理)。可是实际上平面并没有位移,所以观察者所看到的是A点,而不是B点。那我们如何通过高度图寻找到一个适合的采样点呢?
这就是视差贴图的用处。我们要用V减去P,P的方向和V一致,大小与H(A)相等。此时观察者看到的是H(P)在贴图上对应的点,即(V-P)向量与H(P)处虚线交点处在贴图上对应的点,而此处和B点相距较近。
如果某点的高度越高,那么对其纹理坐标的偏移也就越大,记住,视差贴图并不能使表面看起来和高度图完全一致,因为它只是根据高度图来欺骗观察者视觉的一个trick。
在真正实现时我们会采用深度图(即(1-height)所得)。
P的方向和V一致,大小与H(A)相等。A点纹理坐标就会偏移到H(P)点,和我们想要获得的P点相距较近。
float height = tex2D(_DepthMap, i.uv.zw);float2 p = viewDir.xy / viewDir.z * (height * _HeightScale);i.uv.zw = i.uv.zw - p;
这里的操作均是在切线空间中进行的。
附上代码
Shader "Custom/ParallaxMapping"{ Properties { _Diffuse("Diffuse",Color) = (1,1,1,1) _MainTex("Main Tex",2D)="White"{} _Specular("Specular",Color) = (1,1,1,1) _Gloss("Gloss",Range(8.0,256)) = 20 _Color("Color Tint",Color) = (1,1,1,1) _BumpMap("Normal Map",2D)="bump"{} _BumpScale("Bump Scale",Float)=1.0 //********************************** _HeightMap("_DepthMap",2D)="white"{} _HeightScale("HeightScale",Range(0.005,0.08)) = 1 //******************************* } SubShader{ pass { Tags{"LightMode" = "ForwardBase"} CGPROGRAM#pragma vertex vert#pragma fragment frag#include"Lighting.cginc" fixed4 _Diffuse; sampler2D _MainTex; fixed4 _MainTex_ST; sampler2D _BumpMap; fixed4 _BumpMap_ST; float _BumpScale; fixed4 _Specular; float _Gloss; fixed4 _Color; //********************* sampler2D _DepthMap; float _HeightScale; //*********************** struct a2f { float4 vertex:POSITION; float3 normal:NORMAL; float4 texcoord:TEXCOORD0; float4 tangent:TANGENT; }; struct v2f { float4 pos:SV_POSITION; float4 uv:TEXCOORD0; float4 TtoW0:TEXCOORD1; float4 TtoW1:TEXCOORD2; float4 TtoW2:TEXCOORD3; }; v2f vert(a2f v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv.xy = v.texcoord.xy*_MainTex_ST.xy + _MainTex_ST.zw; o.uv.zw = v.texcoord.xy*_BumpMap_ST.xy + _BumpMap_ST.zw; float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent)*v.tangent.w; o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); return o; } fixed4 frag(v2f i) :SV_Target{ float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w); fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos)); fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); //************************************** float height = tex2D(_DepthMap, i.uv.zw); float2 p = viewDir.xy / viewDir.z * (height * _HeightScale); i.uv.zw = i.uv.zw - p; //************************************** fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); bump.xy *= _BumpScale; bump.z = sqrt(1 - saturate(dot(bump.xy, bump.xy))); bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump))); fixed3 albedo = tex2D(_MainTex, i.uv).rgb*_Color.rgb; fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo; fixed3 diffuse = _LightColor0.rgb*_Diffuse.rbg*albedo*max(dot(bump, lightDir),0); fixed3 halfDir = normalize(lightDir + lightDir); fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(bump, halfDir)), _Gloss); return fixed4(diffuse + ambient+ specular, 1.0); } ENDCG } } FallBack "Specular"}
陡峭视差贴图和视差贴图原理基本一致,但是效果更好。
我们从上到下遍历深度层,我们把每个深度层和储存在深度贴图中的它的深度值进行对比。如果这个层的深度值小于深度贴图的值,就意味着这一层的P向量部分在表面之下。我们继续这个处理过程直到有一层的深度高于储存在深度贴图中的值:这个点就在(经过位移的)表面下方。
这个例子中我们可以看到第二层(D(2) = 0.73)的深度贴图的值仍低于第二层的深度值0.4,所以我们继续。下一次迭代,这一层的深度值0.6大于深度贴图中采样的深度值(D(3) = 0.37)。我们便可以假设第三层向量P是可用的位移几何位置。我们可以用从向量P3的纹理坐标偏移T3来对fragment的纹理坐标进行位移。你可以看到随着深度曾的增加精确度也在提高。
最好的点是视线与高度图的交点,而最终我们获得了T3这个点,两者相差极小。
float numLayers = 10;float layerDepth = 1.0 / numLayers;float currentLayerDepth = 0.0;float2 p = viewDir.xy *_HeightScale;float deltaTexCoords = p / numLayers;float2 currentTexCoords = i.uv.zw;float currentDepthMapValue = tex2D(_DepthMap, currentTexCoords).r; while(currentLayerDepth < currentDepthMapValue){ currentTexCoords -= deltaTexCoords; currentDepthMapValue = tex2D(_DepthMap, currentTexCoords).r; currentLayerDepth += layerDepth;}i.uv.zw = currentTexCoords;
但是循环语句中,循环次数不能超过1024次,否则会报错。具体原因可参考
转载地址:http://kduh.baihongyu.com/