|
Post by tbird on Jan 29, 2021 10:41:50 GMT -6
Here is something I was playing with last night, it's pretty neat simulated shadows. The performance is poor due to using drawn polygons, there is another method I would like to try, but that will be way later. The light is on the mouse and the 4 green lines represent "walls", if you get really close you will see an end to the shadow, this is due to lightDistance variable, if it gets so huge that you can get close to the wall it makes the polygon huge and performance tanks, so keep the light a little ways away to enjoy the effect.
I also put a gradient image underneath to simulate fading light, its a common technique which was easy to add.
SCR_W = 640 SCR_H = 480
WindowOpen(0, "2D Light/Shadow Test", 0, 0, SCR_W, SCR_H, 1) CanvasOpen(0, SCR_W, SCR_H, 0, 0, SCR_W, SCR_H, 1) Canvas(0)
LoadImage(0, "testPattern.png") LoadImage(1, "lightFade.png") SetImageAlpha(1, 250)
DIM mX : DIM mY : DIM mB1 : DIM mB2 : DIM mB3 'Global mouse variables
DIM numWalls : numWalls = 4 DIM point1X[numWalls] : DIM point1Y[numWalls] : DIM point2X[numWalls] : DIM point2Y[numWalls] 'Start and end points of the walls
point1X[0] = 200 : point1Y[0] = 120 point2X[0] = 440 : point2Y[0] = 120 point1X[1] = 200 : point1Y[1] = 360 point2X[1] = 440 : point2Y[1] = 360 point1X[2] = 180 : point1Y[2] = 140 point2X[2] = 180 : point2Y[2] = 340 point1X[3] = 460 : point1Y[3] = 140 point2X[3] = 460 : point2Y[3] = 340 'point1X[4] = 380 : point1Y[4] = 120 'point2X[4] = 380 : point2Y[4] = 130 'point1X[5] = 380 : point1Y[5] = 140 'point2X[5] = 380 : point2Y[5] = 150 'point1X[6] = 380 : point1Y[6] = 160 'point2X[6] = 380 : point2Y[6] = 400 'point1X[7] = 380 : point1Y[7] = 410 'point2X[7] = 380 : point2Y[7] = 480
DIM rayAngle1[numWalls] : DIM rayAngle2[numWalls] DIM lightDistance lightDistance = 1500
DIM lineColor
lineColor = RGB(50,220,10) shadowColor = RGB(0, 0, 0) pointSize = 4
Function Atan2(a, b) radian = 0 pi = 3.14
if b > 0 then radian = atan(a/b)
elseif b < 0 and a >= 0 then radian = atan(a/b) + pi
elseif b < 0 and a < 0 then radian = atan(a/b) - pi
elseif b = 0 and a > 0 then radian = pi / 2
elseif b = 0 and a < 0 then radian = 0 - (pi / 2 )
elseif b = 0 and a = 0 then radian = 1000 'represents undefined end if
return (radian) End Function
Sub Draw(x, y) SetColor(lineColor) For i = 0 To numWalls - 1 Line(point1X[i], point1Y[i], point2X[i], point2Y[i]) Next End Sub
DIM contactPoint1X[numWalls] : DIM contactPoint1Y[numWalls] : DIM contactPoint2X[numWalls] : DIM contactPoint2Y[numWalls]
DIM shadowX[4] : DIM shadowY[4]
Sub RayCast() For i = 0 To numWalls rayAngle1[i] = Atan2(mX - point1X[i], mY - point1Y[i]) rayAngle2[i] = Atan2(mX - point2X[i], mY - point2Y[i]) SetColor(shadowColor)
contactPoint1X[i] = mX - SIN(rayAngle1[i]) * lightDistance contactPoint1Y[i] = mY - COS(rayAngle1[i]) * lightDistance contactPoint2X[i] = mX - SIN(rayAngle2[i]) * lightDistance contactPoint2Y[i] = mY - COS(rayAngle2[i]) * lightDistance
shadowX[0] = point1X[i] : shadowY[0] = point1Y[i] shadowX[1] = point2X[i] : shadowY[1] = point2Y[i] shadowX[2] = contactPoint2X[i] :shadowX[3] = contactPoint1X[i] shadowY[2] = contactPoint2Y[i] : shadowY[3] = contactPoint1Y[i] PolyFill(4, shadowX, shadowY) Next End Sub
While NOT KEY(K_Escape) GetMouse(mX, mY, mB1, mB2, mB3) ClearCanvas() DrawImage(0,0,0) DrawImage(1, mX - 650, mY - 487.5) RayCast() Draw(mX, mY) Update() Wend And the images
|
|
|
Post by n00b on Jan 29, 2021 14:38:40 GMT -6
tbird This is really impressive. The performance was actually pretty good to.
|
|
|
Post by johnno56 on Jan 29, 2021 17:07:16 GMT -6
This is really good. Just like n00b, my performance, is quite good. No lag. One minor suggestion. As the 'light source' is either 'feathered' or a gradient, may I suggest that the 'shadow' be not a 'stark' black. Perhaps a very dark grey (gray) of rgb(8,8,8).
This could serve as basis for an interesting dungeon crawl or a 2D Haunted House...
Two thumbs way up!!
|
|
|
Post by tbird on Jan 29, 2021 20:28:57 GMT -6
Glad you guys enjoyed it. johnno56 I did try actually a not so dark shading, it looked better until the shadows bypass one another making the "light" go through walls, the only way to avoid that would be the make the light radius on my gradient much smaller...I will try it. I am surprised the performance was ok, but then again my dev laptop is ....ancient, and slow lol.
|
|
|
Post by n00b on Jan 29, 2021 22:48:40 GMT -6
I am surprised the performance was ok, but then again my dev laptop is ....ancient, and slow lol. I think your dev setup is an advantage because if you can run it on your laptop it is going to run on any modern device. I have actually seen a game that uses a similar technique and it has actually been quite successful: PhilophobiaI am going to port it to android and see how well it performs on my phone.
|
|
|
Post by tbird on Jan 30, 2021 21:51:04 GMT -6
Yes dev'n on a slow machine has its advantages and disadvantages. I took johnno56 suggestion and greyed the shadows, also added another transparent background pic ontop and this fixed the shadows blending problem I was having, it looks much nicer now, good idea!
SCR_W = 800 SCR_H = 600
WindowOpen(0, "2D Light/Shadow Test", 0, 0, SCR_W, SCR_H, 1) CanvasOpen(0, SCR_W, SCR_H, 0, 0, SCR_W, SCR_H, 1) Canvas(0)
LoadImage(0, "testPattern.png") LoadImage(2, "testPattern.png") LoadImage(1, "lightFade.png") SetImageAlpha(1, 200) SetImageAlpha(2, 20)
DIM mX : DIM mY : DIM mB1 : DIM mB2 : DIM mB3 'Global mouse variables
DIM numWalls : numWalls = 6 DIM point1X[numWalls] : DIM point1Y[numWalls] : DIM point2X[numWalls] : DIM point2Y[numWalls] 'Start and end points of the walls
point1X[0] = 200 : point1Y[0] = 150 point2X[0] = 600 : point2Y[0] = 150 point1X[1] = 180 : point1Y[1] = 165 point2X[1] = 100 : point2Y[1] = 315 point1X[2] = 620 : point1Y[2] = 165 point2X[2] = 700 : point2Y[2] = 315 point1X[3] = 100 : point1Y[3] = 330 point2X[3] = 180 : point2Y[3] = 480 point1X[4] = 700 : point1Y[4] = 330 point2X[4] = 620 : point2Y[4] = 480 point1X[5] = 300 : point1Y[5] = 400 point2X[5] = 500 : point2Y[5] = 400 'point1X[6] = 380 : point1Y[6] = 160 'point2X[6] = 380 : point2Y[6] = 400 'point1X[7] = 380 : point1Y[7] = 410 'point2X[7] = 380 : point2Y[7] = 480
DIM rayAngle1[numWalls] : DIM rayAngle2[numWalls] DIM shadowDistance shadowDistance = 2000
DIM lineColor
lineColor = RGB(50,220,10) shadowColor = RGB(15, 15, 15) pointSize = 4
Function Atan2(a, b) radian = 0 pi = 3.14
if b > 0 then radian = atan(a/b)
elseif b < 0 and a >= 0 then radian = atan(a/b) + pi
elseif b < 0 and a < 0 then radian = atan(a/b) - pi
elseif b = 0 and a > 0 then radian = pi / 2
elseif b = 0 and a < 0 then radian = 0 - (pi / 2 )
elseif b = 0 and a = 0 then radian = 1000 'represents undefined end if
return (radian) End Function
Sub Draw(x, y) SetColor(lineColor) For i = 0 To numWalls - 1 Line(point1X[i], point1Y[i], point2X[i], point2Y[i]) Next End Sub
DIM contactPoint1X[numWalls] : DIM contactPoint1Y[numWalls] : DIM contactPoint2X[numWalls] : DIM contactPoint2Y[numWalls]
DIM shadowX[4] : DIM shadowY[4]
Sub RayCast() For i = 0 To numWalls - 1 rayAngle1[i] = Atan2(mX - point1X[i], mY - point1Y[i]) rayAngle2[i] = Atan2(mX - point2X[i], mY - point2Y[i]) SetColor(shadowColor)
contactPoint1X[i] = mX - SIN(rayAngle1[i]) * shadowDistance contactPoint1Y[i] = mY - COS(rayAngle1[i]) * shadowDistance contactPoint2X[i] = mX - SIN(rayAngle2[i]) * shadowDistance contactPoint2Y[i] = mY - COS(rayAngle2[i]) * shadowDistance
shadowX[0] = point1X[i] : shadowY[0] = point1Y[i] shadowX[1] = point2X[i] : shadowY[1] = point2Y[i] shadowX[2] = contactPoint2X[i] :shadowX[3] = contactPoint1X[i] shadowY[2] = contactPoint2Y[i] : shadowY[3] = contactPoint1Y[i] PolyFill(4, shadowX, shadowY) Next End Sub
While NOT KEY(K_Escape) GetMouse(mX, mY, mB1, mB2, mB3) ClearCanvas() DrawImage(0, 0, 0) DrawImage(1, mX - 1400, mY - 800) RayCast() DrawImage(2, 0, 0) Draw(mX, mY) Update() Wend
Yes you need the bigger images.
|
|
|
Post by johnno56 on Jan 30, 2021 22:36:44 GMT -6
I agree. Looks much nicer. Did you have any plans for future development?
|
|
|
Post by n00b on Jan 30, 2021 23:13:49 GMT -6
I can see a reason to use either one. The first version would be good for when you want your shadows to hide objects from the player and the second for when you want more accurate lighting. These are both really cool tricks to add to the toolbox.
|
|
|
Post by johnno56 on Jan 31, 2021 6:07:55 GMT -6
tbird, Found a Game Maker tutorial (a pyramid explorer/dungeon type game) and managed to create a couple of "sheets" that may be handy. Cannot vouch for copyright conditions, so it may be best, to use them for personal use. Main sheet is a set of 4 images walking in 4 compass directions and a smaller sheet containing 4 static images (standing) in all 4 directions. If you need them changed in any way just let me know? J
|
|
|
Post by tbird on Jan 31, 2021 21:20:53 GMT -6
Just had to didn't you johnno56 lol. I am supposed to be working on my demo and now my brain is bombarding me with ideas for the lights.... I am just playing, but I still blame you. I will for sure use the sprites you have provided, and have thought of a way to make some cool effects. It will probably take me a couple days, to get a couple hours to put it together but I think it will be pretty neat.
|
|
|
Post by johnno56 on Feb 1, 2021 1:47:09 GMT -6
Too good an opportunity to pass up! If you do end up using the images perhaps a change in colour for the light source... maybe a yelowish-orange?
Sorry for causing the distraction. Perhaps continue with the demo and if time and the mood persists....
|
|
|
Post by tbird on Feb 1, 2021 19:48:38 GMT -6
I am going to have to do this a different way, once the shadows start stacking up the performance is horrible, it looks nice, but completely impractical. I have been doing a bit of reading and the proper method and harder one lol, is to calculate the edge points and draw just the short light polygon, as opposed to drawing all the shadows all the time. Here is the latest revision, no character yet I wanted to try and see if I made a simple hallway what would happen, my old lappy averages 14 FPS
SCR_W = 1366 SCR_H = 768
WindowOpen(0, "2D Light/Shadow Test", 0, 0, SCR_W, SCR_H, 1) CanvasOpen(0, SCR_W, SCR_H, 0, 0, SCR_W, SCR_H, 1) Canvas(0)
LoadFont(1, "zrnic rg.ttf", 40) : Font(1)
LoadImage(0, "testPattern.png") LoadImage(2, "testPattern.png") LoadImage(1, "torchFadeSmall.png") LoadImage(4, "fadeBack.png") SetImageAlpha(1, 80) SetImageAlpha(2, 1) SetImageAlpha(4, 250)
DIM mX : DIM mY : DIM mB1 : DIM mB2 : DIM mB3 'Global mouse variables DIM CurrentFPS
DIM numWalls : numWalls = 11 DIM point1X[numWalls] : DIM point1Y[numWalls] : DIM point2X[numWalls] : DIM point2Y[numWalls] 'Start and end points of the walls
point1X[0] = 200 : point1Y[0] = 100 point2X[0] = 200 : point2Y[0] = 220 point1X[1] = 200 : point1Y[1] = 260 point2X[1] = 200 : point2Y[1] = 640 point1X[2] = 200 : point1Y[2] = 640 point2X[2] = 360 : point2Y[2] = 640 point1X[3] = 400 : point1Y[3] = 640 point2X[3] = 700 : point2Y[3] = 640 point1X[4] = 280 : point1Y[4] = 100 point2X[4] = 280 : point2Y[4] = 400 point1X[5] = 280 : point1Y[5] = 440 point2X[5] = 280 : point2Y[5] = 560 point1X[6] = 280 : point1Y[6] = 560 point2X[6] = 400 : point2Y[6] = 560 point1X[7] = 440 : point1Y[7] = 560 point2X[7] = 780 : point2Y[7] = 560 point1X[8] = 780 : point1Y[8] = 560 point2X[8] = 780 : point2Y[8] = 768 point1X[9] = 700 : point1Y[9] = 640 point2X[9] = 700 : point2Y[9] = 768 point1X[10] = 440 : point1Y[10] = 560 point2X[10] = 700 : point2Y[10] = 560
DIM rayAngle1[numWalls] : DIM rayAngle2[numWalls] DIM shadowDistance shadowDistance = 1000
DIM lineColor
lineColor = RGBA(101, 67, 33, 80) shadowColor = RGB(5, 5, 5) pointSize = 4
Function Atan2(a, b) radian = 0 pi = 3.14
if b > 0 then radian = atan(a/b)
elseif b < 0 and a >= 0 then radian = atan(a/b) + pi
elseif b < 0 and a < 0 then radian = atan(a/b) - pi
elseif b = 0 and a > 0 then radian = pi / 2
elseif b = 0 and a < 0 then radian = 0 - (pi / 2 )
elseif b = 0 and a = 0 then radian = 1000 'represents undefined end if
return (radian) End Function
Sub Draw(x, y) SetColor(lineColor) For i = 0 To numWalls - 1 Line(point1X[i], point1Y[i], point2X[i], point2Y[i]) Next End Sub
DIM contactPoint1X[numWalls] : DIM contactPoint1Y[numWalls] : DIM contactPoint2X[numWalls] : DIM contactPoint2Y[numWalls]
DIM shadowX[4] : DIM shadowY[4]
Sub RayCast() For i = 0 To numWalls - 1 rayAngle1[i] = Atan2(mX - point1X[i], mY - point1Y[i]) rayAngle2[i] = Atan2(mX - point2X[i], mY - point2Y[i]) SetColor(shadowColor)
contactPoint1X[i] = mX - SIN(rayAngle1[i]) * shadowDistance contactPoint1Y[i] = mY - COS(rayAngle1[i]) * shadowDistance contactPoint2X[i] = mX - SIN(rayAngle2[i]) * shadowDistance contactPoint2Y[i] = mY - COS(rayAngle2[i]) * shadowDistance
shadowX[0] = point1X[i] : shadowY[0] = point1Y[i] shadowX[1] = point2X[i] : shadowY[1] = point2Y[i] shadowX[2] = contactPoint2X[i] :shadowX[3] = contactPoint1X[i] shadowY[2] = contactPoint2Y[i] : shadowY[3] = contactPoint1Y[i] PolyFill(4, shadowX, shadowY) Next End Sub
While NOT KEY(K_Escape) GetMouse(mX, mY, mB1, mB2, mB3) ClearCanvas() DrawImage(0, 0, 0) DrawImage(4, 0, 0) DrawImage(1, mX - 150, mY - 150) RayCast() DrawImage(2, 0, 0) Draw(mX, mY) CurrentFPS = FPS() DrawText("FPS: " + STR$(CurrentFPS), 100, 0) Update() Wend
Try this out and see how bad it does..?
|
|
|
Post by johnno56 on Feb 1, 2021 23:17:10 GMT -6
That's looking pretty good so far... Performance is running at 60fps... Curious. Did you create all of this from scratch or use other reference material? This is interesting stuff!
|
|
|
Post by tbird on Feb 2, 2021 6:04:28 GMT -6
|
|
|
Post by johnno56 on Feb 2, 2021 13:19:28 GMT -6
The basics (no pun intended) of 2D shadow casting, according to those sites, are quite straight forward. I am going to have to go through the FB code with a fine toothed comb... Wouldn't it be cool if a library could be made? If you 'really' want to get into 'light theory'.... There should be two shadows (unless the light source is a pinpoint). This can be demonstrated by observing a Lunar eclipse. The moon casts two shadows. Umbra - the darkest shadow and the Penumbra - the lightest shadow. Of course, that all occurs in space, where both shadows are noticeable. ( www.nasa.gov/audience/forstudents/k-4/stories/umbra-and-penumbra ) Back on Terra Firma, introducing an atmosphere, there is a 'gradient' effect between the two shadows. Turn on the light and hold you hand above a flat, lightly coloured surface, then notice the gradient effect on the hands shadow. The effect is more pronounced as you move your hand closer to the light source. This, of course, is what is needed to simulate a 'realistic' shadow effect, but for a 2D simulation/game, what you have done is more than adequate... Isn't science fun? Looking forward to what you do next... (no pressure, right?) Great job! J
|
|