博客
关于我
【实时渲染】Unity Shader实现视差贴图 法线贴图
阅读量:322 次
发布时间: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/

你可能感兴趣的文章
vuex modules
查看>>
sleep、wait、yield、join——简介
查看>>
web项目配置
查看>>
基于单片机可控音乐流水灯控制设计-全套资料
查看>>
基于单片机简易信号误差分析设计-全套资料
查看>>
基于单片机简易脉搏测量仪系统设计-毕设课设资料
查看>>
Javascript中String支持使用正则表达式的四种方法
查看>>
【Tool】如何使用 Uniflash 烧写 WIFI 芯片 CC3200
查看>>
纯客户端页面关键字搜索高亮jQuery插件
查看>>
Java温故而知新-反射机制
查看>>
eclipse引用sun.misc开头的类
查看>>
Servlet2.5的增删改查功能分析与实现------删除功能(四)
查看>>
Session验证码的实现(2018-7-3)
查看>>
spring启动错误:Could not resolve placeholder
查看>>
选择性估算器绕过行安全策略漏洞
查看>>
对PostgreSQL数据库结构的宏观理解
查看>>
查询某表格上次进行vacuum的时间
查看>>
invalid byte sequence for encoding
查看>>
聊一聊那些应该了解的大佬(飞控,人工智能方向)
查看>>
redis向数组中添加值并查看数组长度
查看>>