Shipping as a desktop application allows to silently use a bundled JRE, Android has its Dalvik machine.
But having to install a public JRE is a major drawback, giving Java’s reputation and not speaking of the latest security problems.
But anyway, first there needs to be a game, so happy developing
About my previous comment:
The ‘haha’ part wasn’t meant to be a ‘haha’ part, but was a little bit angry because of something.
Maybe I have to reformulate my previous comment (cursing = bad game for me so I will avoid this topic from now on) to something more clear like this:
cursing might turn away users like me who don’t approve of swearing
Im not planning to make a big discussion here, I only wanted to point things out…
(If you want to discuss this you can always PM me)
And hopefully we can avoid this ‘bitchfight’ in the future.
Don’t worry about it, I’m difficult to upset lol. Thanks for the clarification though
Hmm, I’ve decided I’m going to put-aside the current lighting implementation and portentially implement an algorithim I found on gamedev.net which is likely more appropriate for isometric engine - “Razorblade’s Isometric Dynamic Lighting Algorithm”
Essentially tiles & actors have a normal map packed with their RGB colour data. I’m pretty sure that if I stick to VolatileImage & only update the lighting on these tiles when they are in the fall-out range of the dynamic light sources are when one of the surrounding light sources have been put into a ‘dirty’ state I should be able to pull off decent performance.
Warning: VolatileImages are glitchy. Basically, drivers don’t work well with them and the performance is gone when adding in transparency on windows. They are fine as long as you use them as backbuffers only and even then avoid them.
Tips on performance in java2D: Keep draw calls down. g.draw stuff. So particles will probably be a bottle neck unless you are doing some crazy stuff with writing to BufferImages.
For particles I can usually get good amount rendered on screen at a decent frame-rate (and considering the machine I am developing on is very old, I’d consider that acceptable.) That said, that number is relatively variable and has been higher - I haven’t isolated it and run it through a profiler really so it’s performance is anyone guess - but it has been working fine for me and I don’t really ever intend on having more particles than that on screen.
I’m not sure how well VolatileImages works for dynamic lighting - but I have experienced a very large performance boost by using them in areas where my image buffers experience frequent change (which is why I figured I should use them for my light-map). What do you mean by glitchy? I know that the VolatileImages can have their contents discard and be displaced in memory - but if that happens(and you check to determine the contents have been lost) you can revalidate it (it will try and allocate an accelerated surface - if it can’t, it would instead allocated one in system memory - which can’t be any worse than using a bufferedimage.)
You will get bizarre results when trying to render with it. Flickering, jacked up colors, anything really. Setting the opengl flag fixes most of the issues but makes them impossibly slow on windows. I have not done much testing on other OS but it seems that the way to go for VI is to use them only as backbuffers. If you look at a topic I made today, I posted some real time lighting stuff. It is not isometric but it is very very fast. I use a VI as a backbuffer and draw onto it which is what you said you will be doing, so that is great.
What are your “old” system specs? I get 10-15k particles at solid 60 fps with other crap going on. That is about where you want things at. Above 20k draw calls and you will bring most systems down below 60 fps. Key thing is to avoid draw calls when ever possible. Use sprite sheets for particle effects if at all possible as you can go from 200 particles for one effect to 20
using animated particles. Fillrate will rarely be an issue. In openGL land, fillrate is the first bottle neck in 2D games.
I would love to see how you come about your lighting in java2D.
Yeah, I’ve seen your topic on lighting and it looks very impressive. I will definitely be looking at it for some hints later if this algorithm doesn’t work out - I’m still trying to figure the best way of doing that algorithm I mentioned above.
The particle engine uses sprite-sheets (but I haven’t bothered to do any animations for them) I do tend to keep the particle count lower and just have several particles per sprite.
My system is:
NVIDIA GeForce 8400 GS 512MB Video Ram
1GB System memory and an AMD Athlon 3200+ CPU
What the hell? I thought I posted a video here - I suppose it has just been floating around the internet, anyway (its about a day old - since then I did some profiling and improved a lot of performance in the core system)
Update on Lighting
I figure I am going to stick with my old lighting system for two reasons:
- The amount of data that would need to be encoded with my current assets would be A LOT of work to produce. Maybe when I acquire a larger team this would be feasible - but until then I need to move on.
- Pixel shading is generally a very poorly performing operation in pure java-2d, while I did come across a few promising algorithms, see (1) for why I am still hesitant to implement them.
With that said, there are a lot of things I can still do with the old lighting system, I’ll see how that works out.
G0bxGhwkaNs
Sorry for the frequent double posting - but I am assuming thats allowed in the WIP project as it is a developer log of sorts?
Anyway, I’ve definitely figured out this dynamic lighting mess. Thankfully, I already had a sort of ‘hidden’ lighting feature already implemented in my engine that was used for AI purposes. Each tile\actor was associated a (blended) visibility obstruction value that ranged between 1.0 & 0.0 - I can use this range to determine how much light to pass through entities.
The lighting algorithm works much like you saw in previous posts however I have managed to have it cast shadows (the method I use allows for all sorts of lights by implementing an ILightSource interface - I’ve only written a diffuse but ofcourse the other implementations like directional light is possible.)
The next challenge will be throwing the generated light map into the maps depth buffer - this shouldn’t be too great of a challenge (and there are a few shortcuts I can take to doing this) - finally then the lighting code will be finished. I’ll post some shots when I get it completed.
I have achieved a (highly unpolished) effect for casting shadows, depth sorting the light map, and applying the lighting without experiencing a drop in frame-rate. Here is how it looks:
You’ll see in this screen-shot a diffuse light (not shaded in but that step I’d say is relatively easy in contrast to the steps to get there) casting shadows - the areas behind the entities that obstructs the light’s passage BUT also in the bounds of the light’s maximum fall-out is shaded in as the ambient lighting but in reality this area is passed to the lights rendering routine to factor in the amount of obstruction that has occurred so that the light can apply a prettier gradient (or fading effect of some sort.)
The current implementation of the diffuse lighting is to just simply render obstructed regions the ambient colour and render non-obstructed regions the light’s colour - obviously though you’d apply a fading effect as you got further from the light (that part of the implementation I haven’t done yet - but as I said it is relatively easy.)
How I did it:
I don’t guarantee this is the most optimal method, but for those interested:
Essentially, this effect is achieved by using a lot of Java shape operations - the rendering of the light map is accelerated by using a VolatileImage.
When the light map is being generated, the render routine does the following:
-
Creates an Area object that contains a Rectangle that covers the
entirety of the screen. This area will contain your ambient
lighting. -
It then iterates through the lights asking them what their
light-casting Area would be if there were no obstructions in the way. -
It takes this area object and searches the world for Actors\Tiles
that are contained within that area that the light would be cast in. -
For every tile that it finds that obstructs view in the light’s casting area, it will calculate the difference in the light source’s position and the obstruction’s
position (essentially creating a vector that points AT the
obstruction from the light source - this is the direction you want to cast your shadow) This pointing vector (in world
space) needs to be translated to screen space. -
Once that has been done, a perpendicular to that vector is taken and
normalized. This essentially gives you a line you can travel up or
down on by multiplying it by any given length to travel the given direction in. This vector is
perpendicular to the direction you want to cast your shadow over. -
Almost done, you consturct a polygon that consists of four points.
The first two points are at the the base of the screen coordinate of
your obstruction’s center point. To get the first point, you want to
travel up your perpendicular vector (calculated in 5) a quantity of
half your tile’s height [ this is a relatively accurate
approximation though I think this part of the algorithm is slightly
incorrect - but it has no noticable decay on the visual effect] -
then ofcourse add to that the obstructions origin. To get the
second, you do the same but instead travel down. -
The remainder of the two points are calculated exactly the same way -
only these points need to be projected outward in the direction of
your shadow’s projection vector calculated in 4. - You can choose any large amount to project it outwards by - just as long as it reaches at least outside of you light’s casting area (so if you just want to do it stupidly multiply your shadow projection vector by a factor of 10 and you should be safe) -
From this polygon you just constructed, construct an area, and then
invoke the “intersect” method with your light’s area as the first
argument - this will assure that your shadows area doesn’t reach
outside of the bounds of the area that your light casts over. -
Subtract from your light’s casting the shadow area you constructed
above. At this point you now have two areas - the area where the
light casts unobstructed, and the area the light casts over
obstructed - if your Actors have a visibility obstruction factor
that you used to determine that a particular actor was obstructing
view - you also have the grade at which it obstructs the view that
you can apply later when you are drawing in the light effect (this will allow you to chose between a darker\brighter shade depending on how much light is being obstructed -
Subtract from your ambient light area you constructed in (1) both
the light area, and the obstructed light area so you don’t apply
the ambient light to areas where the lighting effect will take over
and render into
Now you need to merge your light map with your depth-buffered world’s render routine
Now that you’ve rendered you’re light map and it is contained inside of a volatile image, you need to throw it into your world’s render routine and depth-sorting algorithm. Since the back-buffer and the light map are both volatileimages, rendering the light map over the world is relatively optimal.
You need to construct a polygon that is essentially a strip that contains what a vertical strip of your world tiles would be rendered into (look at my screen shot, you’ll see an array of thin diagonal lines seperating these strips. These strips are what I am referring). You can than render parts of this light map strip by strip (render it over the strip after you’ve rendered the last tile in that strip since - obviously - the light map has to be applied over the map). You can use the same image-map just use that strip as a clip for Graphics - you will need to translate that strip polygon down per render of a strip.
Anyway, like I said I don’t guarantee this is the most optimal way - but so far it is working fine for me.
The light map is applied p
How well do multiple lights work? What you are doing is almost the exact same thing I do. I am going to post the important part here it may be a bit long sorry.
private void createShadows()
{
if(baked)
return;
clear();
for(int i = 0;i<= blocks.size()-1;i++)
{
Block blocker = blocks.get(i);
if(blocker.collision(this))
{
Point p1 = null,p2 = null,p3,p4,p5 = null,p6 = null;
Polygon poly = new Polygon();
if(loc.x < blocker.loc.x + blocker.size.x/2 && loc.x> blocker.loc.x - blocker.size.x/2 )
{
if(blocker.loc.y >= loc.y)
{
p1 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p6 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
}
else
{
p1 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p6 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
}
}
else if (loc.y < blocker.loc.y + blocker.size.y/2 && loc.y > blocker.loc.y - blocker.size.y/2 )
{
if(loc.x <= blocker.loc.x)
{
p1 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p6 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
}
else
{
p1 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p6 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
}
}
else
{
if(blocker.loc.x < this.loc.x)
{
if(blocker.loc.y-blocker.size.y/2 < loc.y)
{
p1 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
}
else
{
p1 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
}
}
else
{
if(blocker.loc.y-blocker.size.y/2 < loc.y)
{
p1 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
}
else
{
p1 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y-blocker.size.y/2));
p2 = new Point((int)(blocker.loc.x - blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
p5 = new Point((int)(blocker.loc.x + blocker.size.x/2),(int) (blocker.loc.y+blocker.size.y/2));
}
}
}
Point p7 = null;
if(blocker.size.x != blocker.size.y)
{
double angle1 = Math.atan2(blocker.loc.y-loc.y, blocker.loc.x - loc.x)* 180/Math.PI;
p7 = new Point((int)(loc.x + size.x *19* Math.cos(angle1 * Math.PI / 180)),
(int)(loc.y + size.y*19 * Math.sin(angle1 * Math.PI / 180)));
}
double angle1 = Math.atan2(p1.y-loc.y, p1.x - loc.x)* 180/Math.PI;
double angle2 = Math.atan2(p2.y-loc.y, p2.x - loc.x)* 180/Math.PI;
p3 = new Point((int)(loc.x + size.x *19* Math.cos(angle1 * Math.PI / 180)),
(int)(loc.y + size.y*19 * Math.sin(angle1 * Math.PI / 180)));
p4 = new Point((int)(loc.x + size.x * 19 * Math.cos(angle2 * Math.PI / 180)),
(int)(loc.y + size.y*19 * Math.sin(angle2 * Math.PI / 180)));
poly.addPoint(p1.x,p1.y);
poly.addPoint(p3.x,p3.y);
if(p7 != null)
poly.addPoint(p7.x,p7.y);
poly.addPoint(p4.x,p4.y);
poly.addPoint(p2.x,p2.y);
poly.addPoint(p5.x,p5.y);
if(p6 != null)
poly.addPoint(p6.x,p6.y);
drawClip(poly);
}
}
}
private void drawClip(Polygon p)
{
p.translate(-(int)loc.x,-(int)loc.y);
p.translate(light.getWidth()/2, light.getHeight()/2);
if(father.getAA())
lightg2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
lightg2d.setColor(BLANK);
lightg2d.fill(p);
}
Basically, find the points of the shape that is going to be casting a shadow, find the angle to the light, stretch out and create another point, add to a polygon, and block out the shadowed part from the light. I have not done a whole lot of tinkering with shapes. Could be fun.
Here is a tip on how to clear a BufferedImage without recreating one or decelerating it. (I think at least)
//take graphics of image
Graphics2D g2d = image.getGraphics();
//Create a cached version of a blank color
static final Color BLANK = new Color(0,0,0,0); // no alpha
//set cached blank color
g2d.setColor(BLANK);
//fill rect with the color
g2d.fillRect(0,0,imageWidth,imageHeight);
//now you have a completely blank image.
Thanks StumpyStrust you’ve been an awesome help - dynamic lighting in Java2D is a bit awkward and thankfully you’ve already done it and I appreciate your help.
With multiple lights it has been working as I expected but I can’t say for sure until I add the light shading effect since the blending of the two will likely require some toying with.
I noticed that our algorithms are virtually the same - the only two minor differences with mine is that my lights are cast as an ellipse rather than a circle (due to the perspective) and that I need to depth buffer my light map (which is a relatively easy task) - so looking at your method of using areas\shapes was definitely a huge help.
I’m on a bit of a break right now - but I’ll get back to it in a couple of hours and I’ll be sure to report progress.
Thanks again.
Mine will work as an ellipse as well or should. Heck, you really could use any source image as long as you change the center point used to find the angles.
Scratch that clearing method for bufferedimages from before. It will work but is not necessary.
lightg2d.drawImage(originalLight,0,0,light.getWidth(),light.getHeight(),null);
Well work as well. About the same speed. That is the slow down that I have found because I am drawing with a bufferedimage. So to make this really fast I need a way to restore the image to its original state without drawing to a bufferedimage. VolatileImages work but these lines KILLS their performance gain.
lightg2d.setColor(BLANK);
lightg2d.fill(p);
Put simply, VI do not like changing transparency or more so, the polygon fill method as that probably is done in software. So, restore it with a VI, draw the shadow shape to a BI then draw that BI to the VI. hmmm…may get a nice performance boost if this works. Just talking about it some more and goofing around has given me more ideas on how to get this faster and better. By the end I could make a tutorial on fast dynamic java2D lighting. But then that would just be leading people astray because you really should not be using java2D.
How are you drawing the actually lightmap to the scene? The way I do it might be different and I would love to know. Anyways, bedtime for me. Good luck.
Thanks for the interest.
My dynamic lighting system doesn’t load light texture from a file - instead I achieve hardware accelerated (at least in my environment I believe it is hardware accelerated) approach of the shading by using a variation of RadialGradientPaint mixed with a GadientPaint to achieve the lighting effects. I would avoid using a light texture because it seems like it would quite expensive to draw that over the light map (plus for a simple diffuse\directional light you don’t need anything fancy.)
I have profiled the code, and I can post some details on that:
(You need to follow the image URL in your browser - the forum seems to be clipping the CPU time column)
You’ll notice that the Lightmap.enqueueRender is an incredibly cheap operation - I spend virtually zero CPU time producing the light map - this is likely because the operations I am using are hardware accelerated with GradientPaint and RadialGradientPain. However, I do spend a decent amount of CPU time calculating the areas in which to render (Area.Subtract\Area.Add) however I believe there are a lot of places I can minimize the calling to those routines so we’ll see how it works out. Either way I don’t believe this is too big of a bottle-neck.
The bottle-neck seems to be on the Graphics2D.setClip routine which seems to be much more expensive than I thought it would be (I did some research and it is just as slow with primitive shapes.) It is literally taking about 2/3rds the cpu time I spend on drawing the depth sorted strip of the produced lightmap just to set the clip - which is way to slow IMO. I’ll need to find a more effective way of depth-sorting the strip. Furhter I
The Area object seems to not behave properly (I think I’ve found a bug in the java.awt library) if you pass it a specific set of data (I haven’t logged what that data might be) - but essentially it causes the Area.intersect\Area.subtract routine to hang the invoking thread endlessly - no exception etc it just gets stuck in an internal loop. The bug is easily reproducible by positioning the light at a very specific location in my world and than translating it diagonally (world coordinates) - so until I get a response form where I report the bug I’ll just avoid that location on the map lol.
On the bright side, I nearly have the lighting effects I am looking for, here is how it looks:
For now though, everything seems to be performing alright. I think I am going to finish polishing up my DiffuseLight, resolve that hanging issue with Area.Subtract\Intersect (probably issue a bug report - since even if I am giving it awkward data by mistake (and I will trace over my algorithm a bit to assure I’m not) it should have done the proper checking and thrown an exception - it’s never really acceptable for it to get stuck in an endless iteration and lock up the main thread) Polish up my lighting a little bit and leave this. If it starts causing performance issues I’ll do some more profiling and try and optimize the lighting a bit.
I don’t think writing a tutorial on Java2D lighting is a bad idea - since most people doing are (like me) trying to avoid the need to elevated permissions on dependencies that would come along with needing to upload a shader to the GPU. I think it offers an interesting new method for lighting in a 2D rendering environment that can’t optimally perform per-pixel operations in 2D games and I don’t think that it is well documented.
I have drawn the conclusion that depth sorting my light-map is pointless - and unneeded.
Depth buffering my light map meant that the applied effect was much like the light being situated very close to the ground - i.e, it wouldn’t cast lighting effects sufficient over entities.
That said, having an excuse to throw away depth buffering for light-map reduces about 90% of my dynamic lighting systems overhead. Though I still need to implement JTS since java’s geometry API performs very poorly in a few rare instances which causes the entire calling thread to lock up until it runs out of memory (I submitted an error report to Oracle on this) - you can take a look at this bug that I found here: http://www.java-gaming.org/topics/does-this-snippet-of-code-hang-your-thread/30393/msg/280400/view/topicseen.html#msg280400 I would link to the bug report but I believe it is remained private until it is confirmed.
Current state of the lighting system:
All that’s left to do now is refactor over to JTS and create some cool torch effect with the particle engine & the new dynamic lighting system.
I’m pretty excited to start implementing a weather system and day\night cycle with the new lighting and particle subsystems.
Looking good. Using an image as the source light is fine. Java is very fast at drawing images. Most g.draw calls are fast. The cool thing with using an image is that you could create all sorts of cool lighting. Also, how dark can you make the game? The trick I use to make it so only the alpha of the main lightmap is used is through alpha composites.
The idea is to take the alpha from the light/main lightmap and make it the destination alpha. That way you can have some much more realistic lighting. Here is the code.
Graphics2D g2d = (Graphics2D) g.create();
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, colorBlend));
g2d.drawImage(lightmap,offX,offY,scrWidth,scrHeight,null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_ATOP, 1));
g2d.drawImage(lightmap,offX,offY,scrWidth,scrHeight,null);
g2d.dispose();
The first draw will draw the amount of color from the lights we are blending. The second will draw the amount of alpha we will blend which in this case it all. To get ambient lighting, just draw a rectangle of some color (ambient color) over the lightmap first with a low alpha or max alpha. Alpha composites are hardware accelerated. They are rather cheap.
How many lights can you have before a slow down? How big can the lights be? In my game Retro I have a light attached to each projectile and that does not seem to slow things down much.
I have found never to use setClip. It has always been slow for me. For the day night or you just drawing a colored, translucent rectangle?
Ofcourse! I don’t know what I was thinking when I said it would be slower (I mean, it probably is - but by an insignificant bit. I wonder though how that scales) I was tired and had just finished profiling and saw that my greatest bottle-neck was drawing the light-map - but that was due to its size I think. Ugh - lighting without pixel-shaders has been complete hell. Anyway, that said, the lighting implementation really leaves the specifics of how the light is rendered into the actual light class implementing the ILight interface. So I am setting up the system to go either way on that (using Textures or Shapes.)
I’m not sure how you’ve set it up, but I’m not sure how I would do things like set the light to a specific colour using a texture - i.e I don’t want to have a texture for every light colour - I am sure there is a way around that (well, I know you can use a software filter and cache - but that’s not practical for a lot of situations like a colour transitioning light etc.) There might be a way to do it through a AlphaComposite? I’m not sure. So I set it up to go either way.
Yep, thats what I am doing (or something like that)
I can’t answer those questions now (well, give me an hour or two, I need to do some profiling & setup a few tests), but what I can say is that the most expensive operation is drawing the light-map to the screen - i.e I don’t believe the number of lights has a linear relationship with the initial performance impact so I have to do some profiling there. I’m going to do a light-attached-to-particles test and see how well it performs.
The way it works is:
- Clear the lightmap with a transparent colour
- Draw over my lights with an alpha composite shape
- Draw over my ambient light with a rectangle using source-out alpha-composite
- Then draw over the lights again with default composite (add the colour)
Hmm, well I’ve just finished fixing the issue regarding overlapping lights, here is how it looks now (I chose a black background so I can be sure colours aren’t being washed out):
here is how it looks with some fine-tuning:
Ugh, all this work on lighting - I forget that the lighting subsystem is such a small component of the engine. Well, I learned a lot about lighting without pixel-shaders which is great - but virtually nothing from my knowledge of prior lighting had any relevance. I really think it would be worth writing a tutorial on this - lighting is complete hell without a shader.
Alright, I have 75 lights on there - a radius of 3 tiles. There is some performance difference between the 2 lights and the 75 lights but not much - I don’t notice any frame drops (albeit I haven’t implemented an FPS counter) but I’d say its running at at least 40 - not being much of a gamer I don’t have a keen eye for it really. It ends up consuming about 48% of CPU time (recorded via task manager - which is a jump from ~10%-15%) All the performance impact comes from filling the shape.
Here is how it looks (really stupid because the lights have their colour randomized)
You’ll notice they aren’t casting shadows - I got rid of that because when you actually apply it - it looks cool for a while but the shadows don’t really make sense. With an isometric world where entities are viewed as 3D - Some objects have completely different normal data and it really doesn’t make any sense until I have time to encode that normal data & height into the objects - so until then I have commented out the shadow-casting code. I am not sure how much performance impact it would have - but it’s really only a few geometry manipulations so I can’t imagine much (especially considering how cheap they’ve been so far)
If you do not want shadows then you can make the system scary fast. At least mine. It will be as fast as java can draw images or in your case shapes. Take your lightmap, clear it, draw all lights to it like you would sprites, and then draw the lightmap to the scene with the method I posted. Should be very very fast. The shadows are what will eat performance. Just tested my current code with turning off shadows and could do a 1500 lights at 256x256 pixels. You can even bake in shadows for dungeons and still get really cool lighting with only a memory hit.
My method is memory intensive but it still is very manageable. Each light has its own BI for the original light and the lights that we will be drawing over. Orig and thrash for thort. The shadow map is drawn onto the (thrash) BI each frame and then that BI is clear by drawing the Orig BI over it. Giving each image their own BI to manage the shadow mapping allows their shadows to not conflict with other lights. Giving them each their own Orig BI also allows them to have their own color. I also re-size each Orig texture to the lights size as to keep the memory down and give larger lights better quality. The down side is having each light have two BI. You can drop the Orig BI and just make them keep a reference to the Orig and clear with it. One thing which I have added is reducing the lights BI size. Sort of like reducing the scale of the lightmap. Reduction the size by 2, (100/scale = 50) doubles performance as the bottle neck is clearing each lights BI and you can barely tell a difference.
The advantage of using your gradient pain is that you can pick what ever color you want on the fly. Very nice and not something I thought of. I would mix my current method with yours by dropping the extra BI and instead just draw the Gradient to the thrash BI for clearing. Something I may look into. I am not sure if drawing the gradient is slow or faster then drawing an Image. There may be some software stuff going on to do it but maybe not.
If you post the code you use to draw you lightmap I can probably help as I have spent many-an-hour tinkering with java2D and finding out what is fast and not.
PS: I have no idea how to do lighting with shaders. You can help me when I venture into that. ;D