主頁 >  其他 > 翻譯20 視差和法線、高度圖回顧

翻譯20 視差和法線、高度圖回顧

2020-09-10 02:36:17 其他

1 視差紋理

    由于視角的原因,當調整攝像機位置時,觀察到的事物的相對位置會發生變化,這種視覺現象稱為視差,在坐火車高速行駛看窗外的景物,附近的物體看起來很大并且移動很快,而遠處的背景看起來很小并且移動較慢,
渲染時,相機使用透視模式時,也會出現視差,

    之前(翻譯6與翻譯9)使用過法線貼圖將表面不規則感添加到平滑表面, 它會影響照明,但不會影響表面的實際形狀, 因此,該效果視差不明顯,通過實作法線貼圖基于視野深度的幻覺有許多限制,這一篇的目的就是解決該限制,

1.1 法線貼圖效果回顧

    下面給出許多albedo map 和 normal map差異對比

image image

albedo map 和 normal map

   如果沒有法線貼圖,表面看起來很平坦, 添加法線貼圖會使它看起來好像具有不規則的表面, 但是,高度海拔差異看起來不明顯, 當從入射視角與表面的夾角越趨于0,高度差越不明顯,如果高程差異較大,則表面特征的相對視覺位置應由于視差而發生很大變化,但不會發生變化, 我們看到的視差是平坦的表面

 image

平坦的視角

    雖然可以增加法線貼圖的強度,但這不會改變視差,同樣,當法線貼圖變得太強時,它會看起來很奇怪,它影響了平坦表面的光線的明暗變換,而視差效果它們確實是平的,所以法線貼圖只適用于小的變化,但不會表現出明顯的視差,

image

法線貼圖的光線變化.

要獲得真正的深度視差感,首先需要確定深度應該是多少,法線貼圖不包含這些資訊,所以我們需要一個高度圖,這樣,我們就可以創建一個基于高度資訊的假視差效果,就像法線貼圖創建一個假斜率一樣,下面的貼圖也稱它是灰度圖,黑色代表最低點,白色代表最高點,因為我們將使用這個貼圖來創建一個視差效果,也稱為視差圖,

image

高度圖

確保在匯入時禁用sRGB(顏色紋理),這樣在使用線性空間渲染時資料就不會被弄亂

1.2 Shader引數

為了能夠使用視差貼圖,我們必須為它添加一個屬性到著色器,也會給它一個強度引數來縮放效果,因為視差效果相當強,我們將其范圍設定為0-0.1,

[NoScaleOffset] _ParallaxMap ("Parallax", 2D) = "black" {}
_ParallaxStrength ("Parallax Strength", Range(0, 0.1)) = 0

[NoScaleOffset] _OcclusionMap ("Occlusion", 2D) = "white" {}
_OcclusionStrength ("Occlusion Strength", Range(0, 1)) = 1

視差貼圖是一個著色器特性,我們將啟用_PARALLAX_MAP關鍵字,將必需的編譯器指令添加到base pass、additive pass和deferred pass,

#pragma shader_feature _NORMAL_MAP
#pragma shader_feature _PARALLAX_MAP
為什么不在ShadowCaster增加視差貼圖?
當使用albedo貼圖的alpha通道的透明度時,視差貼圖只會影響陰影,即使是這樣,在陰影貼圖中的視差效果也很難被注意到,所以它通常不值得額外的計算時間,但是如果愿意,也可以將它添加到陰影施法者通道中,

為了訪問新的屬性,給我的照明添加相應的變數

sampler2D _ParallaxMap;
float _ParallaxStrength;

sampler2D _OcclusionMap;
float _OcclusionStrength;

為了能夠自定義配置材質,在Extend ShaderGUI擴展中增加相應Enable與Disanble key的方法,

void DoParallax () {
	MaterialProperty map = FindProperty("_ParallaxMap");
	Texture tex = map.textureValue;
	EditorGUI.BeginChangeCheck();
	editor.TexturePropertySingleLine
	(
		MakeLabel(map, "Parallax (G)"), map,
		tex ? FindProperty("_ParallaxStrength") : null
	);
	if (EditorGUI.EndChangeCheck() && tex != map.textureValue) {
		SetKeyword("_PARALLAX_MAP", map.textureValue);
	}
}

image

1.3 坐標匹配

通過在fragment程式中調整紋理坐標,讓平坦表面的某些部分看起來高低交錯,創建一個應用視差函式,給它一個inout插值器引數,

void ApplyParallax (inout Interpolators i) {
}

在fragment程式使用插入的資料之前呼叫視差函式,會有點例外是LOD衰落,因為這取決于螢屏位置,先不調整這些坐標,

FragmentOutput MyFragmentProgram (Interpolators i) {
	UNITY_SETUP_INSTANCE_ID(i);
	#if defined(LOD_FADE_CROSSFADE)
		UnityApplyDitherCrossFade(i.vpos);
	#endif

	ApplyParallax(i);

	float alpha = GetAlpha(i);
	#if defined(_RENDERING_CUTOUT)
		clip(alpha - _Cutoff);
	#endif
	…
}

通過簡單地向U坐標添加視差強度來調整紋理坐標,做一次偏移計算

void ApplyParallax (inout Interpolators i) {
	#if defined(_PARALLAX_MAP)
		i.uv.x += _ParallaxStrength;
	#endif
}

改變視差強度會導致紋理偏移,增加U坐標會使紋理向負的U方向移動,V坐標同理,這看起來不是視差效果,因為這是一個與視角無關的均勻位移,

shifting[3]

u坐標移動

1.4 隨視角方向移動

視差是由相對于觀察者的透視投影,所以必須改變紋理坐標,這意味著必須基于視圖的方向來移動坐標,而視圖的方向對于表面上每個片段都是不同的,

 imageView direction varies across a surface.

    紋理坐標存在于切線空間中,為了調整這些坐標,需要知道視圖在切線空間中的方向,這需要矩陣乘法對空間進行轉換,在fragment-程式已經有了一個切線空間矩陣,但是它是用于從切線空間到世界空間的轉換,在本例中,需要從物件空間轉到切線空間

    視圖方向向量定義為從表面到攝像機,需要歸一化,我們可以在vertex程式中確定這個向量,轉換它并將它傳遞給fragment程式,但是為了最終得到正確的方向,需要推遲歸一化,直到插值完成后,添加切線空間視圖方向作為一個新的插值成員變數,

struct InterpolatorsVertex {
	…

	#if defined(_PARALLAX_MAP)
		float3 tangentViewDir : TEXCOORD8;
	#endif
};

struct Interpolators {
	…

	#if defined(_PARALLAX_MAP)
		float3 tangentViewDir : TEXCOORD8;
	#endif
};
暫存器數量限制是多少?
model 1與model 2都只支持8個Texture Coordinate Register ->Texcoord[0-7],當使用model 3時,可以使用TEXCOORD8,若硬體不支持model 3其機能也就不是很強大,所以不要使用視差映射,

首先, 使用mesh網格資料中的原始頂點切向量和法向量,在頂點程式中創建一個從物件空間到切線空間的轉換矩陣,因為我們只用它來變換一個向量而不是一個位置我們用一個3×3矩陣就足夠了

InterpolatorsVertex MyVertexProgram (VertexData v) {
	…

	ComputeVertexLightColor(i);

	#if defined (_PARALLAX_MAP)
		float3x3 objectToTangent = float3x3(
			v.tangent.xyz,
			cross(v.normal, v.tangent.xyz) * v.tangent.w,
			v.normal
		);
	#endif

	return i;
}

然后,可以使用ObjSpaceViewDir函式得到物件空間中頂點位置的視圖方向,再用矩陣變換它我們就得到了我們需要的切線空間下視圖方向,

#if defined (_PARALLAX_MAP)
	float3x3 objectToTangent = float3x3
	(
		v.tangent.xyz,
		cross(v.normal, v.tangent.xyz) * v.tangent.w,
		v.normal
	);
	i.tangentViewDir = mul(objectToTangent, ObjSpaceViewDir(v.vertex));
#endif


ObjSpaceViewDir內部實作?
ObjSpaceViewDir函式是在UnityCG中定義的,它先將攝像機位置轉換到物件空間,然后減去物件空間下頂點位置得到一個從頂點指向攝像機的向量,注意它還沒有標準化.
inline float3 ObjSpaceViewDir (float4 v)
{
    float3 objSpaceCameraPos = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos.xyz, 1)).xyz;
    return objSpaceCameraPos - v.xyz;
}

最后,我們可以在ApplyParallax函式使用切線空間視圖方向了,首先,對它進行規格化normalize,把它變成一個合適的方向向量,然后,添加它的XY組件到紋理坐標,再由視差強度縮放,

void ApplyParallax (inout Interpolators i) {
	#if defined(_PARALLAX_MAP)
		i.tangentViewDir = normalize(i.tangentViewDir);
		i.uv.xy += i.tangentViewDir.xy * _ParallaxStrength;
	#endif
}


這能有效地將視圖方向投影到紋理表面上,當以90度角直視表面時,在切空間中的視圖方向等于表面法線(0,0,1),這不會導致位移,視角越淺,投影越大,位移效果也越大,

 image影視圖方向用作UV偏移

所有這一切的影響是表面似乎被拉向上的切線空間,看起來比它實際上更高,基于視差強度,The effect of all of

shiftinguv

隨投影視角方向移動UV.

1.5 基于高度滑動

    在基于高度這一點上,我們可以讓表面看起來更高,但它仍然是一個均勻位移,下一步是使用視差貼圖來縮放位移,采樣貼圖,使用它的G通道作為高度,應用視差強度,并使用它來調節位移,

i.tangentViewDir = normalize(i.tangentViewDir);
float height = tex2D(_ParallaxMap, i.uv.xy).g;
height *= _ParallaxStrength;
i.uv.xy += i.tangentViewDir.xy * height;

shiftingbyHeight

由高度調整的移動

    低的區域現在保持不變,而高的區域被向上拉,standard shader抵消了這種效果,所以低的區域也向下移動,而在中間的區域保持他們原來的位置,這是通過從原始高度資料中減去差值來實作的,

float height = tex2D(_ParallaxMap, i.uv.xy).g;
height -= 0.5;
height *= _ParallaxStrength;


shiftingbyStrength

視差貼圖效果 由合理到過量

這就產生了我們想要的視差效果,但它只在低強度下有效,不足的是位移位移變換的很快,會撕裂表面,

1.6 偏移視差映射演算法

    我們目前使用的視差映射技術被稱為帶偏移限制的視差映射,我們只是使用了視圖方向的XY部分,它的最大長度是1,因此,紋理偏移量是有限的,這種效果不錯,但不能代表正確的透視投影,

    一個更精確的計算偏移量的物理方法是將高度場視為幾何圖形表面下的體積,并通過它拍攝一個視圖射線,光線從相機發射到表面,從上面進入高度場體積,并持續發射直到它到達由場定義的表面,

    如果高度場均勻為零,那么射線就會一直持續到體積的底部,它與物體的距離取決于光線進入物體時的角度,它沒有限制,角度越淺,越遠,最極端的情況是當視角趨于0時,光線射向無窮大,

 image光線投射到底部,有限且正確

    為了找到合適的偏移量,我們必須縮放視圖方向向量,通過除以它自己的Z分量來使它的Z分量變成1,因為我們以后不需要用Z,我們只需要用X和Y除以Z,

i.tangentViewDir = normalize(i.tangentViewDir);
i.tangentViewDir.xy /= i.tangentViewDir.z;

    雖然這樣可以得到一個更正確的投影,但它確實會使淺視角的視差效果惡化,standard著色器通過增加0.42偏差到Z減輕淺視角的視差效果惡化,所以它永遠不會接近零,這扭曲了透視圖,但使工件更易于管理,我們再加上這個偏差.

i.tangentViewDir.xy /= (i.tangentViewDir.z + 0.42);

image
視差貼影像標準著色器

    通過上述多個步驟修正后, 現在我們的著色器與標準著色器支持同樣的視差效果,視差映射可以應用于任何表面,投影假設切線空間是均勻的,曲面具有彎曲的切線空間,因此會產生物理上不正確的結果,只要視差強度和曲率很小,你就可以擺脫它,

image球面視差貼圖

    同樣,陰影坐標不會受到這個效果的影響,因此,陰影在強烈的視差的組合下看起來很奇怪,好像漂浮在表面上,

image 陰影不受視差影響

1.7 Parallax Configuration

    你不同意Unity使用0.42的偏移值嗎?或者你想使用一個不同的值,還是讓它保持在0?或者你想用偏移限制代替嗎?它是可以配置!

   當你想使用偏移限制,定義PARALLAX_OFFSET_LIMITING在著色器,或者,通過定義PARALLAX_BIAS來設定要使用的偏差,

void ApplyParallax (inout Interpolators i) {
	#if defined(_PARALLAX_MAP)
		i.tangentViewDir = normalize(i.tangentViewDir);
		#if !defined(PARALLAX_OFFSET_LIMITING)
			i.tangentViewDir.xy /= (i.tangentViewDir.z + PARALLAX_BIAS);
		#endif
		…
	#endif
}


    當沒有定義時,假設偏差是0.42,在ApplyParallax 中定義它,注意,宏定義不關心函式作用域,它們總是全域的,

#if !defined(PARALLAX_OFFSET_LIMITING)
	#if !defined(PARALLAX_BIAS)
		#define PARALLAX_BIAS 0.42
	#endif
	i.tangentViewDir.xy /= (i.tangentViewDir.z + PARALLAX_BIAS);
#endif


    現在我們可以通過著色器的CGINCLUDE塊來微調我們的視差效果,添加無偏差和限制偏移的選項,但將它們轉換為注釋,以堅持默認選項,

CGINCLUDE
	#define BINORMAL_PER_FRAGMENT
	#define FOG_DISTANCE

//	#define PARALLAX_BIAS 0
//	#define PARALLAX_OFFSET_LIMITING

ENDCG

1.8 Detail UV

視差貼圖可以在主貼圖上作業,但是我們還沒有注意到副貼圖,我們必須應用紋理坐標偏移到細節UV上,

首先,下面是一個包含網格模式的詳細地圖,它可以很容易地驗證效果是否正確地應用于細節,

image細節網格紋理

使用這個紋理作為材質的細節albedo貼圖,設定二級貼圖的平鋪為10×10,這表明,細節紫外線確實仍然不受影響,

 image image
細節UV不受影響

Standard也簡單地添加了UV偏移到細節UV,這是存盤在UV插值器的ZW組件,

float height = tex2D(_ParallaxMap, i.uv.xy).g;
height -= 0.5;
height *= _ParallaxStrength;
float2 uvOffset = i.tangentViewDir.xy * height;
i.uv.xy += uvOffset;
i.uv.zw += uvOffset;

細節可能有所變化,但是它們肯定還不匹配視差效果, 那是因為我們平鋪了二級紋理, 這樣會將細節UV縮放10倍,使視差偏移量變弱十倍, 我們還必須將細節拼貼應用到偏移量,

i.uv.zw += uvOffset * _DetailTex_ST.xy;

實際上,縮放應該相對于主UV平鋪,以防它被設定為1×1以外的一些東西,

i.uv.zw += uvOffset * (_DetailTex_ST.xy / _MainTex_ST.xy);

image
正確的UV

2 Ray Marching-光線步進

    然而,除了上述的偏移視差映射還有另外的視差演算法:發射射線與高度場體積相交,確定其交點在表面上的位置,然后對該位置采樣, 它通過在射線進入體積時的交點,對高度圖進行一次采樣, 但是,當看向任意一個角度時,這并不能準確告訴射線實際上與高度場相交的高度,

image 實際與預測的高度對比

  先假設入口點的高度與交點的高度相同,但這實際上只有在入口點和交點具有相同的高度時才是正確的,當偏移量不大且高度場變化不大時,它的效果仍然很好,但是,當偏移量太大或高度變化太快時,該演算法就會出現問題,而這很可能是錯誤的,這就會造成表面撕裂,

    如果我們能算出射線實際到達的高度場的位置,那么總能找到真正的可見表面點,這不能通過單個紋理樣本來實作,我們必須沿著視圖射線逐步移動,并每次都采樣高度場,直到射線到達表面,該技術是RayMarching,

image 隨視圖射線前進

    有各種不同的視差貼圖使用raymarching,常見的是陡視差映射Steep Parallax Mapping、地形映射Relief Mapping和視差遮擋映射Parallax Occlusion Mapping,與使用單一紋理樣本相比,它們能通過高度場來創建更好的視差效果,除此之外,它們還可以應用額外的陰影和技術來改進該演算法,當我們做的匹配這些方法時,我會呼叫它,

2.1 自定義視差函式

    標準著色器僅支持簡單的偏移視差映射, 現在,我們要在自己的著色器中添加對視差光線Ray marching的支持, 但是,我們還要繼續支持這種簡單方法, 兩者都需要采樣height欄位,因此將采樣代碼行放在單獨的GetParallaxHeight函式中, 而且,兩種方法的投影視圖方向和偏移量的最終應用都相同, 因此,將偏移量計算也單獨為一個函式, 它僅需要原始UV坐標和已處理的視圖方向作為引數,結果回傳要應用的UV偏移,

float GetParallaxHeight (float2 uv) {
	return tex2D(_ParallaxMap, uv).g;
}

float2 ParallaxOffset (float2 uv, float2 viewDir) {
	float height = GetParallaxHeight(uv);
	height -= 0.5;
	height *= _ParallaxStrength;
	return viewDir * height;
}

void ApplyParallax (inout Interpolators i) {
	#if defined(_PARALLAX_MAP)
		i.tangentViewDir = normalize(i.tangentViewDir);
		#if !defined(PARALLAX_OFFSET_LIMITING)
			#if !defined(PARALLAX_BIAS)
				#define PARALLAX_BIAS 0.42
			#endif
			i.tangentViewDir.xy /= (i.tangentViewDir.z + PARALLAX_BIAS);
		#endif

		float2 uvOffset = ParallaxOffset(i.uv.xy, i.tangentViewDir.xy);
		i.uv.xy += uvOffset;
		i.uv.zw += uvOffset * (_DetailTex_ST.xy / _MainTex_ST.xy);
	#endif
}

現在,我們將應用視差函式宏替換對視差偏移的硬編碼呼叫,從而使視差方法更加靈活,如果沒有定義它,我們將它設定為使用偏移量方法,

void ApplyParallax (inout Interpolators i) {
	#if defined(_PARALLAX_MAP)
		…
		#if !defined(PARALLAX_FUNCTION)
			#define PARALLAX_FUNCTION ParallaxOffset
		#endif
		float2 uvOffset = PARALLAX_FUNCTION(i.uv.xy, i.tangentViewDir.xy);
		i.uv.xy += uvOffset;
		i.uv.zw += uvOffset * (_DetailTex_ST.xy / _MainTex_ST.xy);
	#endif
}


為RayMarching方法創建一個新函式,與ParallaxOffset函式類似的引數和回傳型別,

float2 ParallaxOffset (float2 uv, float2 viewDir) {
    …
}

float2 ParallaxRaymarching (float2 uv, float2 viewDir) {
    float2 uvOffset = 0;
    return uvOffset;
}

現在可以通過定義PARALLAX_FUNCTION來改變著色器中的視差方法,

#define PARALLAX_BIAS 0
//#define PARALLAX_OFFSET_LIMITING
#define PARALLAX_FUNCTION ParallaxRaymarching

2.2 相交計算

    為了找到視圖射線到達高度場的點,我們需要對射線上的多個點進行采樣并計算出在表面下方的位置,第一個采樣點在頂部,我們在這里輸入高度量,就像使用偏移方法一樣,最后一個采樣點就是射線到達體積底部的地方,我們會在這些端點之間均勻地添加額外的采樣點,

    假設每條射線進行10次采樣,這意味著我們將對高度圖采樣10次而不是一次,所以這不是一個便宜計算方法,因為我們用了10個樣本,所以步長是0.1,這是我們沿著視圖射線移動的因子,也就是UV偏移增量,

float2 ParallaxRaymarching (float2 uv, float2 viewDir) {
    float2 uvOffset = 0;
    float stepSize = 0.1;
    float2 uvDelta = viewDir * stepSize;
    return uvOffset;
}


為了應用視差強度,我們可以調整每一步采樣的高度,但是縮放UV delta也有同樣的效果,只需要計算一次,

float2 uvDelta = viewDir * (stepSize * _ParallaxStrength);


    通過這種方式,無論視差強度如何,我們都可以繼續使用0–1作為高度場的范圍, 因此,射線的第一步高度始終為1,低于或高于該高度的表面點的高度由高度場定義,

float stepSize = 0.1;//步長
float2 uvDelta = viewDir * (stepSize * _ParallaxStrength);
float stepHeight = 1;//步高
float surfaceHeight = GetParallaxHeight(uv);

    現在我們要沿著射線迭代,首先,每一步我們都會增加UV偏移量,視圖向量指向攝像機,但我們是在向表面移動,所以我們需要減去UV delta,然后我們用步高來減小步長,然后我們再次對高度圖采樣,使用while回圈重復上述步驟,直到采樣完畢,

float stepHeight = 1;
float surfaceHeight = GetParallaxHeight(uv);

while (stepHeight > surfaceHeight)
{
    uvOffset -= uvDelta;
    stepHeight -= stepSize;
    surfaceHeight = GetParallaxHeight(uv + uvOffset);
}

  當編譯時,會得到一個編譯器警告和錯誤,這個警告告訴我們在回圈中使用了梯度指令,這指的是回圈中的紋理采樣,GPU必須弄清楚使用哪個mipmap級別,它需要比較相鄰片段使用的UV坐標,只有當所有片段執行相同的代碼時,它才能對比,對于回圈來說,這是不可能的,因為它可以提前終止,每個片段都可能不同,因此編譯器將展開回圈,這意味著它將一直執行所有9個步驟,而不管邏輯是否可以提前停止,相反,它隨后使用確定性邏輯選擇最終結果,

    編譯失敗是因為編譯器無法確定回圈的最大迭代次數,它不知道這個最多是9,通過將while回圈轉換為執行限制的for回圈來明確這一點,

for (int i = 1; i < 10 && stepHeight > surfaceHeight; i++)
{
    uvOffset -= uvDelta;
    stepHeight -= stepSize;
    surfaceHeight = GetParallaxHeight(uv + uvOffset);
}

 image

Raymarching 步進10次 無偏差, 無限制.

    與簡單的視差偏移方法相比,視差效果更加明顯,較高的區域現在也正確地阻擋了我們后面較低區域的視野,我們還得到了明顯的圖層,總共10層,

2.3 更多步進

    這個基本的光線行進方法最適合陡峭的視差貼圖,效果的質量是由我們的樣本解析度決定的,一些方法根據視角使用可變的步驟,較淺的角度需要更多的步長,因為光線較長,但我們的樣本量是固定的,所以我們不會這樣做,

提高質量的明顯方法是增加采樣的次數,因此讓其可配置,使用PARALLAX_RAYMARCHING_STEPS,默認值為10,而不是固定的步長和迭代次數,

float2 ParallaxRaymarching (float2 uv, float2 viewDir) {
    #if !defined(PARALLAX_RAYMARCHING_STEPS)
        #define PARALLAX_RAYMARCHING_STEPS 10
    #endif
    float2 uvOffset = 0;
    float stepSize = 1.0 / PARALLAX_RAYMARCHING_STEPS;
    float2 uvDelta = viewDir * (stepSize * _ParallaxStrength);

    float stepHeight = 1;
    float surfaceHeight = GetParallaxHeight(uv);

    for (
        int i = 1;
        i < PARALLAX_RAYMARCHING_STEPS && stepHeight > surfaceHeight;
        i++
    ) {
        uvOffset -= uvDelta;
        stepHeight -= stepSize;
        surfaceHeight = GetParallaxHeight(uv + uvOffset);
    }

    return uvOffset;
}


現在我們可以在著色器中控制步數,對于真正的高質量,將PARALLAX_RAYMARCHING_STEPS定義為100,

#define PARALLAX_BIAS 0
//#define PARALLAX_OFFSET_LIMITING
#define PARALLAX_RAYMARCHING_STEPS 100
#define PARALLAX_FUNCTION ParallaxRaymarching

image

Raymarching 100次采樣

    這讓我們知道了它的效果能有多好,但它計算量太大了,一般不適合手機,所以把樣本數設為10后,我們仍然可以看到視差效果看起來連續和平滑,然而,由視差遮擋引起的輪廓總是鋸齒狀的,MSAA并不能消除這一點,因為它只適用于幾何圖形的邊緣,而不是紋理效果,只要不依賴深度緩沖區,后處理抗鋸齒技術能解決,

不能按片段寫入深度緩沖區嗎?
這在足夠先進的硬體上確實是可能的,使它能夠正確地與高度場相交并應用陰影,不過,它計算量太大,

    我們當前的方法是沿著射線步進,直到到達表面以下的點,或者到達射線末端可能的最低點,然后我們用UV偏移處理那個點,但隱藏在表面之下的這個點,很可能會出現錯誤,這就是導致表面撕裂的原因,

    增加步長數只會減少最大誤差,使用足夠的步驟,錯誤會變得更小,以至于我們無法再看到它,所以當一個表面總是從遠處看,你可以用更少的步驟,距離越近,視角越小,需要的樣本就越多,

image2.4 步長之間插值

    提高質量的一種方法是根據經驗預測光線真正到達表面的位置,比如第一步在表面之上,下一步在表面之下,在這兩步之間的某個點射線一定到達了表面,

    兩個射線點、和兩個射線點到表面最近的點,能定義兩條線段,因為光線和表面碰撞,這兩條線段會相交,所以如果我們跟蹤前面的步驟,我們可以在回圈之后執行直線交叉,我們可以用這個資訊來近似出真正的交點,

image
執行直線交叉

    在for回圈內,我們必須跟蹤之前的UV偏移量、步長高度和表面高度,一般來說,這些等于回圈之前的第一個樣本,

float2 prevUVOffset = uvOffset;
float prevStepHeight = stepHeight;
float prevSurfaceHeight = surfaceHeight;

for (
    …
) {
    prevUVOffset = uvOffset;
    prevStepHeight = stepHeight;
    prevSurfaceHeight = surfaceHeight;
    …
}


    在回圈之后,我們計算這些線的交點,我們可以使用這個插值之間的前點和后點的UV偏移,

forfloat prevDifference = prevStepHeight - prevSurfaceHeight;
float difference = surfaceHeight - stepHeight;
float t = prevDifference / (prevDifference + difference);
uvOffset = lerp(prevUVOffset, uvOffset, t);

return uvOffset;


數學原理:
這兩個線段定義在兩個樣本步驟之間的空間內,我們將這個空間的寬度設定為1,從前一步到最后一步的直線由點(0,a)和點(1,b)定義,其中a是前一步的高度,b是后一步的高
度,因此,可以用線性函式'v(t) = a + (b - a)t'來定義視圖線,同樣地,面線由點(0,c)和(1,d)定義,函式's(t) = c + (d - c)t',

交點存在于s(t) = v(t)'處,那么t的值是多少?
c + (d - c)t` = a + (b - a)t`

(d - c)t` - (b - a)t` = a - c

(a - c + d - b)t` = a - c

`t = (a - c) / (a - c + d - b)
注意:a - c是在t = 0處直線高度的絕對差,d - b是t = 1處的絕對高度差,

image線段交點

    實際上,在這種情況下,我們可以使用插值器來縮放我們要添加到上一點上的UV偏移量,它可以歸結為相同的東西,只是用了更少的數學,

float t = prevDifference / (prevDifference - difference);
uvOffset = prevUVOffset - uvDelta * t;

image

    效果看起來好多了,我們現在假設表面在樣本點之間是線性的,這可以防止最明顯的分層假象,然而,它不能幫助我們檢測我們是否錯過了步驟之間的交集,我們仍然需要很多的樣本來處理小的特征,輪廓和淺角度,

    有了這個技巧,我們的方法類似于視差遮擋映射,雖然這是一個相對便宜的改進,但通過定義PARALLAX_RAYMARCHING_INTERPOLATE,我們讓它成為可選的,

#if defined(PARALLAX_RAYMARCHING_INTERPOLATE)
    float prevDifference = prevStepHeight - prevSurfaceHeight;
    float difference = surfaceHeight - stepHeight;
    float t = prevDifference / (prevDifference + difference);
    uvOffset = prevUVOffset - uvDelta * t;
#endif

在shader內定義PARALLAX_RAYMARCHING_INTERPOLATE,

#define PARALLAX_BIAS 0
//#define PARALLAX_OFFSET_LIMITING
#define PARALLAX_RAYMARCHING_STEPS 10
#define PARALLAX_RAYMARCHING_INTERPOLATE
#define PARALLAX_FUNCTION ParallaxRaymarching

2.5 步長搜索

    通過在兩個步長之間進行線性插值,我們假定表面在兩個步長之間是筆直的, 但是,通常情況并非如此, 為了更好地處理不規則的高度場,我們必須在兩個步長之間搜索實際的交點, 或至少接近它,

    完成回圈后,不要使用最后的偏移量,而是將偏移量調整到最后兩個步長的中間位置,對該點的高度進行采樣,如果我們結束在表面以下,向表面之上方向移動四分之一,并再次采樣,如果我們在表面上結束,向表面之下方向移動四分之,并再次采樣,不斷重復這個程序,

image

越來越接近交點

    上述方法是二分查找的一個應用,它與地形測繪方法最匹配,每走一步,路程減半,直到到達目的地,在我們的例子中,我們將簡單地做固定次數,以達到預期的解決方案,一步,得到0.5,兩步,得到0.25、0.75,三步,是0.125、0.375、0.625、0.875,注意,從第二步開始,每次采樣提升分的辨率將翻倍,

The above approach is an application of binary search. It best matches the Relief Mapping approach. Each step the covered distance halves, until the destination is reached. In our case, we'll simply do this a fixed number of times, arrived at a desired resolution. With one step, we always end up halfway between the last two points, at 0.5. With two steps, we end up at either 0.25 or 0.75. With three steps, it's 0.125, 0.375, 0.625, or 0.875. And so on. Note that, starting at the second step, the effective resolution doubles per sample.

    為了控制是否使用此方法,我們定義PARALLAX_RAYMARCHING_SEARCH_STEPS,默認情況下將其設定為零,這意味著我們根本不進行搜索,如果它被定義為大于0,我們將不得不使用另一個回圈,注意,這種方法與PARALLAX_RAYMARCHING_INTERPOLATE不兼容的,因為我們不能再保證表面是交叉的最后兩個步驟,當我們搜索的時候,禁用插值,

for#if !defined(PARALLAX_RAYMARCHING_SEARCH_STEPS)
    #define PARALLAX_RAYMARCHING_SEARCH_STEPS 0
#endif
#if PARALLAX_RAYMARCHING_SEARCH_STEPS > 0
    for (int i = 0; i < PARALLAX_RAYMARCHING_SEARCH_STEPS; i++) {
    }
#elif defined(PARALLAX_RAYMARCHING_INTERPOLATE)
    float prevDifference = prevStepHeight - prevSurfaceHeight;
    float difference = surfaceHeight - stepHeight;
    float t = prevDifference / (prevDifference + difference);
    uvOffset = prevUVOffset - uvDelta * t;
#endif

    此回圈也執行與原始回圈相同的基本作業,調整偏移量和步高,然后采樣高度欄位,

for (int i = 0; i < PARALLAX_RAYMARCHING_SEARCH_STEPS; i++) {
    uvOffset -= uvDelta;
    stepHeight -= stepSize;
    surfaceHeight = GetParallaxHeight(uv + uvOffset);
}

但每次迭代,UV增量和步長減半,

for (int i = 0; i < PARALLAX_RAYMARCHING_SEARCH_STEPS; i++)
{
    uvDelta *= 0.5;
    stepSize *= 0.5;
    uvOffset -= uvDelta;
    stepHeight -= stepSize;
    surfaceHeight = GetParallaxHeight(uv + uvOffset);
}

同樣,如果點在表面之下,我們必須朝相反的方向移動,

uvDelta *= 0.5;
stepSize *= 0.5;
if (stepHeight < surfaceHeight) {
    uvOffset += uvDelta;
    stepHeight += stepSize;
}
else {
    uvOffset -= uvDelta;
    stepHeight -= stepSize;
}
surfaceHeight = GetParallaxHeight(uv + uvOffset);

調整著色器,所以它使用三個搜索步驟

#define PARALLAX_BIAS 0
//#define PARALLAX_OFFSET_LIMITING
#define PARALLAX_RAYMARCHING_STEPS 10
#define PARALLAX_RAYMARCHING_INTERPOLATE
#define PARALLAX_RAYMARCHING_SEARCH_STEPS 3
#define PARALLAX_FUNCTION ParallaxRaymarching

image

10步長加上3個二分查找的最終效果

    結果看起來相當不錯,但仍不完美,二分法搜索可以比簡單的插值處理較淺的角度,但仍然需要相當多的搜索步驟,以擺脫分層,所以這是一個試驗的問題,找出哪種方法在特定情況下最有效,需要多少步驟,

2.6 縮放物件和動態批處理

    盡管我們的視差映射方法似乎可行,但存在一個隱藏的錯誤, 而且還把錯誤顯示出來了,它顯示了何時使用動態批處理來組合已縮放的物件, 例如,給我們的四邊形一個像(10,10,10)的比例,然后復制它,將副本移到它下面一點, 假設在播放器設定中啟用了此選項,這將觸發Unity動態批處理四邊形,
    批處理開始時,視差效果將扭曲, 旋轉相機時,這一點非常明顯, 但是,這僅發生在游戲視圖和構建中,而不發生在場景視圖中, 請注意,standard著色器也存在此問題,但是當使用弱偏移視差效果時,您可能不會立即注意到它,

image

動態批處理會產生奇怪的結果

    在批處理將它們合并到一個單一的網格中之后,Unity不能標準化處理后的幾何法向量和切向量,因此頂點資料正確的假設不再成立,

    頂點法向量和切向量沒有規范化不是什么大的問題,因為我們在頂點程式中將視圖向量轉換到切線空間,對于其他所有內容,資料在使用之前都要標準化,

   解決方法是在構造物件轉換到切線矩陣之前對向量進行歸一化, 因為只有動態批處理的縮放幾何才需要此選項,所以根據是否定義了PARALLAX_SUPPORT_SCALED_DYNAMIC_BATCHING,將其設為可選,

#if defined (_PARALLAX_MAP)
    #if defined(PARALLAX_SUPPORT_SCALED_DYNAMIC_BATCHING)
        v.tangent.xyz = normalize(v.tangent.xyz);
        v.normal = normalize(v.normal);
    #endif
    float3x3 objectToTangent = float3x3(
        v.tangent.xyz,
        cross(v.normal, v.tangent.xyz) * v.tangent.w,
        v.normal
    );
    i.tangentViewDir = mul(objectToTangent, ObjSpaceViewDir(v.vertex));
#endif
#define PARALLAX_BIAS 0
//#define PARALLAX_OFFSET_LIMITING
#define PARALLAX_RAYMARCHING_STEPS 10
#define PARALLAX_RAYMARCHING_INTERPOLATE
#define PARALLAX_RAYMARCHING_SEARCH_STEPS 3
#define PARALLAX_FUNCTION ParallaxRaymarching
#define PARALLAX_SUPPORT_SCALED_DYNAMIC_BATCHING

動態批量與正確的結果

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/535.html

標籤:其他

上一篇:【翻譯】Quant 應該怎樣寫博客?

下一篇:WordPress網站評論量上漲的10+方式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more