簡體   English   中英

如何手動包裝紋理坐標?

[英]How to wrap texture coordinates manually?

我正在使用C ++和HLSL,並且需要包裝我的紋理坐標,以便將紋理平鋪在一個三角形上。

在將坐標“包裝”到0-1范圍內之后,它們將被旋轉,因此我不能簡單地使用紋理采樣器的AddressU和AddressV屬性設置為wrap,因為它們需要被包裝然后旋轉,所以不能旋轉在采樣器內完成。

這里的解決方案很簡單,只需使用紋理坐標的小數部分,它將進行包裝。

這是一個像素着色器的示例,它將對紋理進行36次平鋪( 6 * 6 ):

input.tex *= 6.0f; //number of times to tile ^ 2
input.tex = frac(input.tex); //wraps texCoords to 0-1 range
return shaderTexture.Sample(SampleType, input.tex);

這會平鋪紋理,但會在包裹紋理的邊界上產生問題。 瓷磚必須均勻地划分為要在其上顯示的空間,否則會在邊沿匯合處形成接縫。 繪制紋理的正方形是800x600像素,因此按5進行平鋪將平分,但按6進行平鋪將不會,並且會沿Y軸產生接縫。

我嘗試使用模數運算符input.tex = input.tex % 1來包裝坐標,但得到的結果完全相同。 我還嘗試過更改紋理過濾方法以及AddressU和AddressV屬性,以及無數種不同的調試方法。

我很幸運使用此代碼。 如果x坐標太高,則將其設置為0,如果x坐標太低,則將其設置為1。

input.tex *= 6.0f;
input.tex = frac(input.tex);
if (input.tex.x > 0.999f) input.tex.x = 0;
if (input.tex.x < 0.001f) input.tex.x = 1;
return shaderTexture.Sample(SampleType, input.tex);

不過,這只能解決某些問題,因此絕對不是解決方案。

這是一張顯示紋理的圖片(左)和手動包裹時的外觀(右)。 您會看到並非所有寄宿生都碰到此錯誤。

我也嘗試過不將紋理坐標更改為0-1范圍,而是圍繞每個圖塊的中心而不是(0.5,0.5)旋轉它們,但是得到的結果是相同的。 同樣,我的紋理坐標完全獨立於頂點,並在像素着色器內部進行計算。

我所看到的與該問題有關的任何事情都與在一個像素處具有較高的值,然后在下一個像素處具有較低的值有關,例如u = 0.95和下一個像素u = 0.03,這導致它在紋理上向后內插。 但是,當我旋轉紋理坐標時,根本沒有任何改變。 即使每個圖塊都應用了隨機旋轉。 在這種情況下,邊緣具有各種彼此相鄰的不同值,不僅是左側的高值和右側的低值,而且接縫發生的區域從未改變。

正如MuertoExcobito所說,主要問題是在邊界處,紋理坐標在單個像素中從1跳到0。 從語義上說,整個紋理在此像素中平均是正確的,但這並不是由於紋理在此像素中從1插值到0引起的。 真正的原因是mipmapping。

對於您的紋理,在加載時會生成mipmap。 這意味着紋理將獲得多個mipmap級別,這些級別都相對於以前的大小為一半。

在此處輸入圖片說明

如果紋理變形,則最高級別的采樣將導致過度采樣(像偽像一樣進行點過濾)。 為了防止紋理查找過采樣,請根據屏幕空間中紋理坐標的變化選擇適當的mipmaplevel。 在您的情況下,邊框在一個很小的地方變化很大,這導致使用了盡可能低的Mipmap(這就是您看到的小紅點,這是紅色邊框的原因)。

回到您的問題,您應該使用紋理查找方法SampleGradDocs )來控制SampleGrad 要獲取像素紋理坐標的當前變化,可以使用內在方法ddxddy 它們返回您的着色器中的任意變量,該變量如何在本地更改為相鄰像素(有關該主題的正確解釋將深入了解)。 因此,使用以下代碼不應更改任何內容,因為它在語義上應相同:

input.tex *= 6.0f; //number of times to tile ^ 2
input.tex = frac(input.tex); //wraps texCoords to 0-1 range
float2 xchange = ddx(input.tex);
float2 ychange = ddy(input.tex);
return shaderTexture.SampleGrad(SampleType, input.tex, xchange, ychange);

現在,您可以應用代碼來防止xchange和ychange發生較大變化,從而迫使圖形設備使用更高的mipmap。 這應該刪除您的工件。

如果不需要紋理映射,因為您正在對齊紋理屏幕,並且紋理尺寸不會導致過采樣,則可以使用更簡單的替代sampleLevelDocs )。您可以傳遞參數,該參數選擇特定的mipmap ,您可以自己確定。

此處的代碼導致對單個像素范圍內的整個紋理進行采樣。 例如,對於接縫處的兩個相鄰像素,一個'u'樣本可能是1.0-eps ,下一個樣本將是0.0+eps ,其中eps是小於紋理像素寬度的數字。 對輸出像素進行插值后,將從1.0 .. 0.0進行插值,對這兩個樣本之間的整個紋理進行采樣。 整個紋理的平均會導致“灰色”,即使您輸入的紋理實際上沒有包含任何完全灰色的像素也是如此。

如果您需要在每個范圍內旋轉紋理坐標(例如,分別旋轉0..1、1..2),則有幾種方法可以解決。 首先,您可以將插值從線性更改為點,這將避免紋理像素之間的插值。 但是,如果需要雙線性插值,則可能無法接受。 在這種情況下,您可以構造一個“網格”網格,並在每個圖塊上映射輸入紋理0..1,而圖塊紋理坐標在着色器中獨立旋轉。

另一種可能的解決方案是將坐標轉換為0..1空間,執行旋轉,然后將其轉換回其原始空間。 例如,您可以這樣做:

// pseudo-code:
int2 whole;
input.tex = modf(input.tex, whole);
input.tex = Rotate(input.tex); // do whatever rotation is needed
input.tex += whole;

這將確保包裝沒有任何間斷。 或者,您可以讓您的旋轉代碼考慮非統一空間紋理坐標。

Gnietschow發布了該問題的答案,但我將添加一個答案,以准確顯示我如何使用該答案。 實際上,我什至不知道為什么這行得通,我什至知道即使使用其他多個平鋪,各種紋理和隨機旋轉也能做到這一點。

input.tex *= 6.0f; //number of times to tile ^ 2
input.tex = frac(input.tex); //wraps texCoords to 0-1 range
float2 xchange = ddx(input.tex);
float2 ychange = ddy(input.tex);
if ((xchange.x < 0)) xchange = float2(0, 0);
if ((ychange.y < 0)) ychange = float2(0, 0);
return shaderTexture.SampleGrad(SampleType, input.tex, xchange, ychange);

如果您根本不需要進行映射,則Gniet提到的另一種方法也很好

input.tex *= 6.0f; //number of times to tile ^ 2
input.tex = frac(input.tex); //wraps texCoords to 0-1 range
return shaderTexture.SampleLevel(SampleType, input.tex, 0);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM