SFML 2.X - Cienie w grze 2D RPG

2

Witam.
Piszę sobie hobbystycznie grę 2D RPG. Postanowiłem zaimplementować system cienii. Jak na razie mam takie coś ale wiem, że to nie spełnia swojej roli, gdyż cienie jako Sprajty mogą na siebie nachodzić.

sf::Sprite shadow;
shadow = sprite;
shadow.setColor( sf::Color( 0, 0, 0, 128 ) );
shadow.setOrigin( currentTexture->cx, currentTexture->cy );
shadow.setScale( 1.0f, 1.5f );
sf::Vector2f p;
p.x = position.x;
p.y = position.y -( float( currentTexture->texture->getSize().y ) - currentTexture->cy ) * shadow.getScale().y / 2.0f;
shadow.setPosition( p );

RPG2D 165.png

I nie wygląda to wtedy jak cień, a zlepek półprzeźroczystych obiektów. Moje pytanie brzmi jak zabrać się za cienie ? Mam wstępny pomysł, żeby użyć dwóch tekstur jedna do renderowania gry a druga do renderowania cieni i na końcu tę z cieniem dodawać do tej pierwszej. Ale czy to dobry pomysł ?

1

W mojej ocenie wygląda to bardzo dobrze:

  1. Cień posiada dobrą przezroczystość — nie za małą, nie za dużą.
  2. Cienie sumują się — miejsce, w które dwa obiekty rzucają cień jest ciemniejsze niż pojedynczy cień.
  3. Cienie rzucane są również na obiekty, a nie tylko na podłoże (cień golema na golemie).

To co zrobiłeś to jeden z najprostszych sposobów, a mimo to daje bardzo ładne efekty. Wiadomo, że jeśli ze cztery obiekty rzucą cień w jedno miejsce, to to miejsce będzie czarne, co nie jest zgodne z tym co widzimy na Ziemi — tu atmosfera rozprasza światło, więc miejsca zacienione tak czy siak są oświetlane rykoszetem. Ale to co widać na zrzucie wygląda bardzo fajnie, więc wcale nie jest powiedziane, że trzeba to ulepszyć.

tBane napisał(a):

Witam.
Piszę sobie hobbystycznie grę 2D RPG.

Myślę, że już całą Polska wie, że piszesz grę, więc już nie musisz o tym pisać. 😉

I nie wygląda to wtedy jak cień, a zlepek półprzeźroczystych obiektów.

Tyle że tak właśnie wyglądają cienie w rzeczywistości. Co najwyżej mają rozmyte krawędzie.

Mam wstępny pomysł, żeby użyć dwóch tekstur jedna do renderowania gry a druga do renderowania cieni i na końcu tę z cieniem dodawać do tej pierwszej. Ale czy to dobry pomysł ?

Zależy co konkretnie chcesz z tą teksturą dla cieni robić, mam na myśli to jak chcesz na niej cienie renderować. Nie wiem też na co konkretnie pozwala SFML, ale modulacja koloru to nie tylko koloryzacja tekstur — można nią negować kolory, dodawać, mnożyć, dzielić itd., a więc uzyskać różne efekty. W tym np. możesz wszystkie cienie wyrenderować na teksturze, ale miejsca zacienione przez wiele obiektów będą tak samo ciemne jak te zacienione przez jeden obiekt. Czyli kolor i przezroczystość cienia zawsze będzie taki sam, nieważne ile cieni w jednym miejscu namalujesz.

Gdybym sam robił tego typu grę, to — jak pisałem kiedyś — skorzystałbym z warstw. Dno, woda, teren i obiekty na terenie — każda po jednej teksturze. W trakcie renderowania, najpierw renderowałbym teksturę warstwy na ekranie (w oknie), następnie cienie obiektów, które się na niej znajdują, na koniec obiekty. Natomiast obiekty renderowałbym w taki sposób, aby cień innych mógł na nie padać — czyli najpierw malowałbym obiekt na pomocniczej teksturze, następnie sprawdzał czy coś na niego rzuca cień i jeśli tak, to renderowałbym cień (albo cienie), a na koniec ten ocieniowany obiekt na ekranie (w oknie). Coś w ten deseń, tak na szybko wymyślone.

Bez tekstur pomocniczych trudno będzie się za ten temat zabrać.

0

Ale jakoś średnio to wygląda moim zdaniem. Postaram się poprawić parametry może to coś da.
screenshot-20241105202559.png

1

IMO wystarczy jedynie pobawić się parametrami. Rzucanie cienia w kierunku od kamery (w górę ekranu) wygląda całkiem nieźle. Na tym zrzucie, który pokazałeś wyżej, cienie są zbyt ciemne i całość wygląda jak oświetlona latarką — spróbuj obniżyć wartość kanału alpha.

1

Teraz jest chyba dobrze :-)

screenshot-20241105202912.png

0

Poprawione wszystkie cienie. Szkoda, że nie da się zrobić tak, żeby te cienie się blendowały na jedną wartość RGBA łącznie tzn żeby każdy cień nawet suma cienii wynosiła (0,0,0,64)

screenshot-20241105203114.png

1
tBane napisał(a):

Szkoda, że nie da się zrobić tak, żeby te cienie się blendowały na jedną wartość RGBA łącznie tzn żeby każdy cień nawet suma cienii wynosiła (0,0,0,64)

W tym właśnie rzecz, że da się — poczytaj o sf::BlendMode. Jeśli będziesz miał dedykowaną teksturę dla cieni (pustą, przezroczystą), to wystarczy skorzystać z trybu Max, aby wszystkie wyrenderowane cienie miały ten sam kolor (maksimum). To tak w teorii, bo SFML-a nie znam, ale to samo jest w SDL. Ot pobaw się tym.

Choć wg mnie nie powinieneś mieć cieni w tym samym kolorze, bo to będzie sztucznie wyglądało. Miejsce zacienione przez dwa obiekty powinno być ciemniejsze niż to zacienione przez jeden obiekt — tak to w naturze działa.

0

@flowCRANE no dobra. Namówiłeś mnie. Zostawiam jak jest :-)

0

Trochę nie pasuje mi perspektywa tzn cień będzie zmieniał swój kształt m.in w zależności od kata padania światła na obiekt. Z tego powodu wygląda to nienaturalne.

0

Poprawiłem jeszcze skalowanie cienia w zależności od wysokości obiektu, który ten cień rzuca:

screenshot-20241105214326.png

0

Teraz cienie postaci są praktycznie niewidoczne. ;)

Ich skalowanie względem wysokości obiektów to prawidłowa taktyka, ale jak sam widzisz, efekt końcowy nie jest najlepszy. Spróbuj rzucać te cienie lekko na ukos — wtedy zawsze będą widoczne.

0

Ma ktoś jakąś funkcję wykładniczą do tego ? Potrzebowałbym wartość z zakresu 0.0f - 0.3f na podstawie texture.getSize().y/256.0f

0

A do czego konkretnie? I dlaczego dzielisz wysokość tekstury przez 256?

0

Bo taki ma rozmiar tekstura drzewa. Wydaje mi się, że to dobry fragment kodu.

0

A jeśli zmienisz rozmiar drzewa, albo dodasz inne (niższe lub wyższe), to będziesz musiał modyfikować kod renderowania cieni — a to bardzo niedobrze.

Cień powinien być obliczany na podstawie wysokości tekstury obiektu, bez względu na to jaki jest jej rozmiar. Ot bierzesz wysokość tekstury i mnożysz razy jakiś współczynnik — 1.0f da Ci cień w takim samym rozmiarze jak obiekt, 2.0f da dwa razy wyższy cień itd. Im wyższy obiekt, tym większy cień będzie rzucał (większa jego część będzie widoczna nad obiektem). Taka samosia.

0

Zrobiłem tak, ale dla potworków cienie są za małe. Istnieje jakaś funkcja wykładnicza, by to poprawić ?

sf::Vector2f scale;
scale.x = 1.0f + 0.0015f * collider->width;
scale.y = 1.0f + 0.0030f * collider->height;
shadow.setScale(scale);

screenshot-20241105224221.png

0

Jakich wyników od niej oczekujesz? Np. ile ma wynosić wysokość cienia dla drzewa, a ile dla dziobaka?

Wcześniej chodziło mi o coś takiego:

sf::Vector2f scale;

scale.x = 1.0015f * collider->width;
scale.y = 1.0030f * collider->height;

shadow.setScale(scale);

Czyli przemnożenie wysokości o stały mnożnik — wyżej to 1.0030f, ale tu bardziej by pasowało coś pokroju 1.5f, żeby sensowna część cienia była widoczna nad obiektem (dla 1.5f to cień wyższy o 50% od obiektu).

0

Dla Dziobaka potrzebowałbym scale.x = 1.216, scale.y = 1.3
Dla Drzewa potrzebowałbym scale.x = 1.075, scale.y = 1.555
Mniej więcej. Czyli powiększyć cień Dziobaka, nie powiększając jednocześnie cienia drzewa.

Obecnie jest tak:
screenshot-20241105232747.png

A potrzebowałbym, by było tak:
screenshot-20241105232844.png

0

Najlepiej będzie jeśli podasz liczby — jaki ma być finalny mnożnik dla drzewa, a jaki dla dziobaka.

0

Dla Dziobaka: 1.216, 1.3
Dla Drzewa: 1.075, 1.555

0

To są mnożniki odpowiednio dla osi X i Y?

0

Dokładnie tak

0

Dobra, a teraz podaj mi wymiary drzewa oraz dziobaka (szerokość i wysokość). Bez tego ani rusz.

0

Wymiary drzewa: 50, 185
Wymiary Dziobaka: 72, 50

1

Jeśli chcesz sobie wymodelować krzywą, która będzie określała mnożnik wysokości cienia względem wysokości obiektu, to możesz skorzystać ze wzoru na obliczanie krzywych Béziera:

x = ((1 - t) * (1 - t) * p0.X) + (2 * t * (1 - t) * p1.X) + (t * t * p2.X);
y = ((1 - t) * (1 - t) * p0.Y) + (2 * t * (1 - t) * p1.Y) + (t * t * p2.Y);

Powyższe dotyczy krzywej w środowisku 2D — ty masz jedną wartość wejściową i jedną wyjściową, więc potrzebujesz wzoru dla jednej osi. Po przystosowaniu go do Twojego przypadku, wygląda tak:

m = ((1 - t) * (1 - t) * p0) + (2 * t * (1 - t) * p1) + (t * t * p2);

Twoim zadaniem jest wybrać i podstawić do tego wzoru stałe liczbowe t (współczynnik interpolacji) oraz p0, p1 i p2 (współrzędne kontrolne, do modelowania krzywej). Stwórz sobie prosty programik, który pozwoli Ci edytować te parametry i pokaże wartość wejściową oraz wyjściową. Baw się tymi parametrami dotąd, aż wynik będzie taki, jakiego oczekujesz.

Najprościej będzie użyć dwóch progressbarów do zwizualizowania wyników — pozycja pierwszego niech pokazuje wartość wejściową (wysokość obiektu w pikselach), a drugi wyjściową, po przepuszczeniu przez powyższy wzór (wysokość cienia w pikselach). Przesuwając pierwszy progressbar, podawaj jego wartość do funkcji i jej wyniku użyj jako pozycji drugiego progressbara. Łatwo będzie Ci testować cały zakres dostępnych wartości — ot ruszasz pierwszym i patrzysz jak się przesuwa drugi. W razie czego nie musisz używać pikseli jako jednostek — możesz użyć mnożników.


Z tego sposobu, czyli z wzoru na krzywe Béziera korzystam w swoim silniku, do modelowania wartości pozycji drążków analogowych joysticków. Dobrałem parametry w taki sposób, że osłabiam czułość drążka blisko środka osi. Im bardziej wychyli się drążek, tym wartość wynikowa (wymodelowana) zbliża się do tej oryginalnej. Wynikiem jest magnituda (siła wychylenia drążka) w formie znormalizowanej, w przedziale [0.0,1.0].


Jeśli potrzebujesz większej kontroli nad kształtem krzywej, będziesz potrzebował więcej punktów kontrolnych.

0

Jednak nie podobają mi się te cienie, które mam obecnie w grze i chcę je poprawić. Znalazłem video na youtubie, jak jeden gościu zrobił cienie i chciałbym zrobić takie same. Jak się za to zabrać ? Najlepiej jakby działały na podstawie sf::Sprite.
Cienie jakie potrzebuję

shadows.png

0

Tutaj masz bardzo dobre wyjaśnienie tematu:

W razie czego, kod projektu, któremu zrobiłeś zrzut ekranu, jest w GitHub — https://github.com/xSnapi/Shadow-Casting

0

Przepisałem ten shader z GitHuba ale nie działa tak jak powinien. Byłbym wdzięczny gdyby ktoś mi pomógł a jak nie to nie dodam cieni do gry - w końcu to moja pierwsza gra :P

// shadow.frag
uniform sampler2D texture;
uniform vec2 resolution;
uniform float time;
varying vec2 worldPos;
uniform vec2 camPosition;

void main()
{
    vec2 uv = gl_FragCoord.xy/resolution;
    vec2 u_cam = camPosition/resolution;
    u_cam = 1.0 - u_cam;
    vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);
    float circle = 1.0 - length(uv-u_cam)*10.0;
    gl_FragColor = (0.1, 0.1, 0.1, circle*(1.0-pixel.y)*1.35);
}

RPG2D 182.png

2

Problem polega na tym, że tego typu algorytmy sprawdzają się w przypadku, gdy mapa jest dwuwymiarowa oraz kamera ustawiona jest prostopadle nad płaszczyzną mapy. Po drugie, taki ray casting można zastosować gdy ma się obiekty o relatywnie prostych kształtach, zbudowanych z raptem kilku(nastu) wierzchołków.

No właśnie — Twoja mapa taka nie jest. Co z tego, że jest dwuwymiarowa, skoro wykorzystuje rzut ortograficzny pod kątem 45° (udając głębię), zamiast widoku z lotu ptaka (prostopadle z góry). Po drugie, Twoje obiekty nie są zbudowane z wierzchołków, a opisywane są teksturami. Dlatego też aby coś takiego zrobić, czyli prawidłowo rzucić cień obiektu (np. drzewa), najpierw musiałbyś przekonwertować teksturę na zestaw wierzchołków (co ani nie jest wygodne, ani wydajne). Poza tym aby poprawnie rzucać takie cienie, musiałbyś jakoś wykorzystać trójwymiarowość świata — im wyższy obiekt, tym inaczej powinien jego cień wyglądać, w zależności od pozycji źródła światła. Do tego nie wiadomo gdzie umieścić źródło światła, aby kształt cieni nie sugerował, że Słońce jest 5m nad graczem. Bug jeden wie jak się za to zabrać. 😉

W grach renderowanych w taki sposób jak Ty to robisz, zwykle cienie renderuje się stosując sztuczki z teksturami — tekstura definiuje kształt cienia, za odpowiednie rozciągnięcie odpowiada modyfikacja jej czterech wierzchołków (albo caging), natomiast za kolor i przezroczystość odpowiadają parametry modulacji. Shaderem można uzyskać jeszcze większą kontrolę, dlatego że można dodać zmienną przezroczystość/kolor cienia, odpowiednio rozmyć jego krawędzie itd. — piękne efekty można dzięki temu uzyskać. Natomiast ray casting raczej nie jest przeznaczony do typu silnika, jaki tworzysz, ze względu na imitowanie głębi.

0

Mam już napisany program Mesh Editor jakby co więc mógłbym generować cienie na podstawie wierzchołków. Tylko jak ?

screenshot-20241120175940.png

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.