博客
关于我
【实时渲染】Unity Shader实现视差贴图 法线贴图
阅读量:321 次
发布时间:2019-03-04

本文共 4685 字,大约阅读时间需要 15 分钟。

为了更好的理解本文所介绍的内容,建议您阅读:

    • 法线贴图 
    • 视差贴图
  • 《UnityShader入门精要》
    • 7.2凹凸纹理

 

所需图片     

 

Unity自带的着色器可以帮助我们实现视差贴图的效果

现在我们来了解一下其背后的原理

目录


视差贴图(Parallax Mapping)

视差贴图可以使物体表面更具有立体感,相对于法线贴图,视差贴图更加真实。

原理

向量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"}

陡峭视差贴图(Steep Parallax Mapping)

陡峭视差贴图和视差贴图原理基本一致,但是效果更好。

我们从上到下遍历深度层,我们把每个深度层和储存在深度贴图中的它的深度值进行对比。如果这个层的深度值小于深度贴图的值,就意味着这一层的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次,否则会报错。具体原因可参考

  • 《Unity Shader 入门精要》
    • 5.7.4慎用分支和循环语句
    • 16章 Unity中的渲染优化次数

 

 

 

转载地址:http://kduh.baihongyu.com/

你可能感兴趣的文章
银河麒麟系统配置apt网络源
查看>>
ArduPilot源码极速下载手册(一文告别github慢速问题)
查看>>
聊一聊那些应该了解的大佬(飞控,人工智能方向)
查看>>
px4调试bug--添加mavlink_log_info信息
查看>>
redis向数组中添加值并查看数组长度
查看>>
python3基础梳理11python中模块和包
查看>>
JS编写一个函数,计算三个不同数字的大小,按从小到大顺序打印(穷举法)
查看>>
mybatis中like的注意
查看>>
sqlplus的基本使用
查看>>
oracle删除表重复数据
查看>>
EditText获取焦点并显示软键盘,Textview字间距,EditText输入监听判断不大于,处理倒计时
查看>>
Oracle删除主表数据
查看>>
js中两种定时器,setTimeout和setInterval实现验证码发送
查看>>
Oracle常用SQL
查看>>
技术美术面试问题整理
查看>>
Redis分布式锁原理
查看>>
C++学习记录 五、C++提高编程(2)
查看>>
自学linux毕业shell面试题
查看>>
4 Java 访问控制符号的范围
查看>>
第9章 - 有没有替代原因(检验证据)
查看>>