Zbuduję to w kilku warstwach, abyś mógł zobaczyć, jak to się łączy.
Zacznij od utworzenia nowego modułu cieniującego w Unity, wybierając Create --> Shader --> Unlit
albo z menu Zasoby, albo z menu kontekstowego prawym przyciskiem myszy w oknie projektu.
W górnym bloku dodamy _ScrollSpeeds
parametr kontrolujący szybkość ruchu tekstury w każdym kierunku:
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ScrollSpeeds ("Scroll Speeds", vector) = (-5, -20, 0, 0)
}
To ujawnia nową 4-komponentową właściwość float w inspektorze materiałów, z przyjazną nazwą „Prędkości przewijania” (podobnie do dodawania public
lub Serialized
zmiennej do MonoBehaviour
skryptu)
Następnie użyjemy tej zmiennej w module cieniującym wierzchołek do przesunięcia współrzędnych tekstury ( o.uv
), dodając tylko dwa wiersze do domyślnego modułu cieniującego:
sampler2D _MainTex;
float4 _MainTex_ST;
// Declare our new parameter here so it's visible to the CG shader
float4 _ScrollSpeeds;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// Shift the uvs over time.
o.uv += _ScrollSpeeds * _Time.x;
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
Uderz to w quad (z uroczą swobodną teksturą żyrafy autorstwa Kenneya ), a otrzymasz:
Aby uzyskać przewijanie tekstury na zewnątrz w pierścieniu, możemy po prostu użyć siatki podzielonej jak pajęczyna, ze współrzędną UV v zwiększającą się od środka na zewnątrz. Ale to da same artefakty w kształcie piły. Zamiast tego pokażę, jak możemy obliczyć nasze promieniowanie UV na fragment.
Jest to nieco bardziej kosztowne, zarówno z powodu operacji wyzwalania i długości (które są droższe niż podstawowa matematyka) oraz ponieważ nie jest tak wydajne przewidywanie i buforowanie danych tekstur w sprzęcie podczas obliczania texcoordów na fragment, w porównaniu do samej interpolacji między wierzchołkami. Ale taki mały efekt specjalny nie jest nadmierny.
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// Shift the UVs so (0, 0) is in the middle of the quad.
o.uv = v.uv - 0.5f;
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// Convert our texture coordinates to polar form:
float2 polar = float2(
atan2(i.uv.y, i.uv.x)/(2.0f * 3.141592653589f), // angle
length(i.uv) // radius
);
// Apply texture scale
polar *= _MainTex_ST.xy;
// Scroll the texture over time.
polar += _ScrollSpeeds.xy * _Time.x;
// Sample using the polar coordinates, instead of the original uvs.
// Here I multiply by MainTex
fixed4 col = tex2D(_MainTex, polar);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
To daje nam coś takiego (tutaj zwiększyłem parametry kafelków w materiale, aby było wyraźniej, co się dzieje - owinięcie tylko jednego powtórzenia płytki wokół koła wygląda na zniekształcone i dziwne)
Wreszcie, aby zabarwić teksturę przewijanym gradientem, możemy po prostu dodać gradient jako drugą teksturę i pomnożyć je razem.
Najpierw dodajemy nowy parametr tekstury u góry:
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_TintTex("Tint Texture", 2D) = "white" {}
_ScrollSpeeds ("Scroll Speeds", vector) = (-5.0, -20.0, 0, 0)
}
I zadeklaruj to w naszym bloku CGPROGRAM, aby moduł cieniujący CG mógł to zobaczyć:
sampler2D _MainTex;
float4 _MainTex_ST;
// Declare our second texture sampler and its Scale/Translate values
sampler2D _TintTex;
float4 _TintTex_ST;
float4 _ScrollSpeeds;
Następnie zaktualizuj nasz moduł cieniujący fragmenty, aby używał obu tekstur:
fixed4 frag(v2f i) : SV_Target
{
float2 polar = float2(
atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
length(i.uv) // radius
);
// Copy the polar coordinates before we scale & shift them,
// so we can scale & shift the tint texture independently.
float2 tintUVs = polar * _TintTex_ST.xy;
tintUVs += _ScrollSpeeds.zw * _Time.x;
polar *= _MainTex_ST.xy;
polar += _ScrollSpeeds.xy * _Time.x;
fixed4 col = tex2D(_MainTex, polar);
// Tint the colour by our second texture.
col *= tex2D(_TintTex, tintUVs);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
A teraz nasza żyrafa robi się naprawdę trippy:
Przy nieco bardziej artystycznym doborze tekstur i szybkości przewijania może to dać efekt podobny do pokazanego w pytaniu.
W wersji, którą pokazałem powyżej, możesz zauważyć dwa małe artefakty:
Twarze w pobliżu środka koła stają się rozciągnięte / chude / spiczaste, a następnie, gdy poruszają się na zewnątrz, stają się zgniecione / szerokie.
To zniekształcenie występuje, ponieważ mamy stałą liczbę twarzy na obwodzie, ale obwód, który obejmują, powiększa się wraz ze wzrostem promienia, a ich wysokość pozostaje taka sama.
Możemy to naprawić poprzez ponowne mapowanie pionowego komponentu próbki tekstury, aby podążać za krzywą logarytmiczną, więc powtórzenia tekstury są bardziej oddalone od siebie, gdy promień się zwiększa, i bliżej siebie w kierunku środka. (W rzeczywistości daje to nam nieskończony regres coraz mniejszych żyraf ...)
W środkowej lewej części kwadratu znajduje się rząd jednego lub dwóch rozmytych pikseli.
Dzieje się tak, ponieważ GPU patrzy na dwie sąsiednie współrzędne próbki tekstury, aby dowiedzieć się, jakiego filtrowania użyć. Gdy próbki są blisko siebie, tekst jest wyświetlany jako duży / zamknięty i pokazuje najbardziej szczegółowy poziom mip. Kiedy próbki są daleko od siebie, domyślamy się, że musimy pokazywać teksturę z niewielkim powiększeniem lub z dużej odległości, i pobiera próbki z mniejszej / rozmytej mipmapy, aby upewnić się, że nie otrzymamy błyszczących artefaktów aliasingowych.
Problem jest tutaj, jesteśmy w punkcie zawinięcia we współrzędnych biegunowych, od -180 do 180 stopni. Próbujemy więc z bardzo podobnych punktów w naszej powtarzalnej przestrzeni tekstur, nawet jeśli ich współrzędne numeryczne sprawiają, że wyglądają, jakby były daleko od siebie. Możemy więc dostarczyć własne wektory gradientu próbkowania, aby to skorygować.
Oto wersja z następującymi poprawkami:
fixed4 frag(v2f i) : SV_Target
{
float2 polar = float2(
atan2(i.uv.y, i.uv.x) / (2.0f * 3.141592653589f), // angle
log(dot(i.uv, i.uv)) * 0.5f // log-radius
);
// Check how much our texture sampling point changes between
// neighbouring pixels to the sides (ddx) and above/below (ddy)
float4 gradient = float4(ddx(polar), ddy(polar));
// If our angle wraps around between adjacent samples,
// discard one full rotation from its value and keep the fraction.
gradient.xz = frac(gradient.xz + 1.5f) - 0.5f;
// Copy the polar coordinates before we scale & shift them,
// so we can scale & shift the tint texture independently.
float2 tintUVs = polar * _TintTex_ST.xy;
tintUVs += _ScrollSpeeds.zw * _Time.x;
polar *= _MainTex_ST.xy;
polar += _ScrollSpeeds.xy * _Time.x;
// Sample with our custom gradients.
fixed4 col = tex2Dgrad(_MainTex, polar,
_MainTex_ST.xy * gradient.xy,
_MainTex_ST.xy * gradient.zw
);
// Since our tint texture has its own scale,
// its gradients also need to be scaled to match.
col *= tex2Dgrad(_TintTex, tintUVs,
_TintTex_ST.xy * gradient.xy,
_TintTex_ST.xy * gradient.zw
);
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
_Time
zmienną, którą można dodać do współrzędnych tekstury w module cieniującym (wierzchołek), aby uzyskać tanie efekty przewijania. Efekt sześciokąta również byłby prosty. Jeśli edytujesz pytanie, aby podświetlić tylko jeden efekt i pytasz „jak zaimplementuję to w module cieniującym Unity”, prawdopodobnie możemy ci pomóc. Pamiętaj, aby określić, czy moduł cieniujący musi reagować na oświetlenie / cieniowanie, ponieważ ma to wpływ na sposób jego pisania.