Realtime Lighting Example in java2D

So I have posted things about lighting in java2D here before but now I am going to give you a nice example and how my library for it works so you could make your own java2D lighting system.

Waning: If you wanted to do any REAL dynamic lighting, for the love of God do not use java2D. I just really like smashing myself in the face. I mean I really like it.

Here is a the download.
http://www.mediafire.com/?764jz882juodw98

Controls:
A and S to change ambient lighting.
Z and X to change color blend.
Use arrow keys to change size of lights and blocks.
1 and 2 to change between light placing mode and block placing mode respectively.
Click to selected and move a light/block
Control + Right Click to add light.
Control + Left Click to add block.

And here is a screen to show.

Now the lights will not look like openGL type lights mainly because java2D cannot do hardware additive blending.

This is the way the current api works.


//create the lightmap with the screen width and height. The third arg is the lightmap scale reduction. 1 is no reduction
		lightmap = new LightMap(window.getWidth(),window.getHeight(),1);
		lightmap.setAmbientColor(Themes.getColor(Themes.GREEN));
		lightmap.setAmbientLuminosity(luma);
		lightmap.setColorBlend(colorBlend);
		lightmap.setAmbientLight(ambLight);


//add a light with location and size. Fifth arg is a color. By not passing in a BufferedImage as a fifth arg to use as a light, it will use a default one. 
			Light l = new Light(0,0,size,size, Themes.getColor(Themes.CYAN));
//there are some vars and methods that will make the light do stuff. 
			l.jump(window.mouseX, window.mouseY);
			l.decay = 0;
			l.intensity = 1;
// and add the light to the lightmap
			lightmap.addLight(l);

//Then update the lightmap
		lightmap.update();
//and finally render everything that will be effected by the lightmap first and then render the lightmap
		Graphics2D g2d = window.begin();
		//draw stuff
		lightmap.render(g2d);
//render stuff not lit here
		g2d.dispose();
		window.end();

The only thing that is left is to add blocks to the lightmap via

lightmap.cullers.add(new Block(x,y,width,hieght)

These could be walls or a set of blocks for a player or other things.

Circle cullers are actually cheaper and the code has been done but they don’t play well with blocks but for a player or sprite they would work great. If people want I can post the source in the shared code section and explain a little more on how it is done.

No source? :stuck_out_tongue:

Like you say, generally Java2D is not a good way to go for this, but it’s a nice learning experience. And it could give you some insight later down the road if you need to use HTML5 canvas or another platform that doesn’t support shaders.

I’ll piggyback on your thread by posting another approach in Java2D that I did for a school project. It’s very basic, but fast enough.

Full source:

The “falloff” is created with RadialGradientPaint. The trick is to just treat each entity as a circle. Alternatively, you could use classic “shadow boundary” technique described by Orangy Tang to get a more accurate geometry; in brief it looks like this:

Yup what you are doing is similar to what I do. Down side is that your block is off a bit because of using circles. What I do is basically the same as you.

Here is the source.

http://pastebin.java-gaming.org/3d4277f6960

I also support culling when you have an offset to the camera.

Basic idea is finding the corner points of the squares and creating a polygon from that based on the points angle to the light. Cool thing is that this supports baking lights. Basically, find the shadows, bake to BufferedImage, and render said image. This is a huge performance boost as you lose the fully dynamic lights but gain static fake dynamic lights.

Because I can.

http://img198.imageshack.us/img198/3122/lamm.jpg

Fully dynamic lighting with arbitrary shapes. Support rotation and scaling of said shapes. ;D

StumpyStrust!

I have to say that one of your previous posts was really helpful for me in order to make a (fake) dynamic lighting system. Thanks a lot!! I owe you a beer.

Using an idea similar to yours I managed to do dynamic shadows. Sorry for the bad video framerate. The performance is quite acceptable.

crrH_WHF4WQ

The Idea I chose was to, from the light source and the picture contour, create a polygon in real time and in an intermediate volatileimage, remove the polygon to the light sprite with the AlphaComposite.SRC_OUT. If you are interested in some details just ask me

Absolutely! That is so close to what I am doing. How are you creating the polygon from the image contours? How fast is it? How many lights can you have? Are you using an image as the light or drawing a gradient? Right now I can do 200 lights that are 256x256 in size where after I hit a fillrate limit. So basically, no limit on the number of lights.

Ok, first what I have is an “Image_container class”, which stores both images and polygons:

public class Image_container extends Identificable_element {

 private BufferedImage[] Image;
 private Polygon[] perimeter;

So, each image has its own associated perimeter polygon. I get these polygons with a (quite) improved version of this idea: http://stackoverflow.com/questions/7052422/image-graphic-into-a-shape

Once you have the light source location and the location of the polygon, you can create these fake shadows for each facet of the polygon. I optimized this by just creating a complex polygon which is composed by all the exposed object facets plus the shdow projection. If I have time I will try to clean up some code and post it here :stuck_out_tongue:

After some thinking and optimizations you can reach nice performance. Not 300 lights of course, but maybe 5-6 lights with a few tens of objects in screen is feasible (on a mid-end card). The biggest limitation here is the shadow rendering, (not the calculation, just the “drawpolygon”). If you have to render 10 shadows per light, you will become limited by fillrate, specially on lowend cards like intedl HD3000 (I works sooo bad with java2D).

For the light I use an existing image, as you did in your older posts about this. It works fine.

Also, you can get smoother shadows by doing the following, it is like a “cheap” antialias. It draws a traslucent shadow contour several time before drawing the shadow.

private void paintShadow(Graphics2D gbsombras){

//"remove" contains the shadow polygon
//"gbsombras" is the target graphics2D    

  if (antiAlias) {
   
     gbsombras.setColor(new Color(0, 0, 0, 0.5f));
     
                       for (int a=-1;a<2;a++){
                       for (int b=-1;b<2;b++){  
                            remove.translate( a, b);
                            gbsombras.drawPolygon(remove.xpoints,remove.ypoints,remove.npoints);
                            remove.translate(-a,-b);
                            }}}
                              

      gbsombras.setColor(Color.BLACK);                 
      gbsombras.fillPolygon(remove.xpoints,remove.ypoints,remove.npoints);
}

So how to you cast the shadow? Right now I am using the java Area class which is very slow for constructing the shadow. Please do post code. I also would love to see how you create these polygons. Are they done in real time? or pre-computed.

That anti-aliasing is not cheap. Java can draw quite a few Images fast. Things like polygon fills, and gradients, are much slower. I have an idea for softish shadows that will be truly free. Need to test it. I can confirm that normally finding shadows is fast but drawing them is slow. I fixed this with volatile images. Now, very little to no penalty for size or number of objects.

On my laptop which I think is HD 3000 I have the most Fing weird bugs because the drivers are such shit. I have solved most and know what is going on but it pisses me off. On that one, I can get 100+ lights.

Do you combine all the shadows as you find them? This could allow you to cull things out if they are already blocked by a shadow caster but in my tests it is faster to just draw each shadow as you find it. Also, how did you solve the shadows of one gear not clipping on to another?

OMG I’m sorry for the delay!!!

Finally, this saturday morning I have some spare time :stuck_out_tongue:

I cast the shadow using some trigonometry. If you know the location of a polygon point and the location of the light source, you can get the expression of the line that connects them. This line is one of the rays casted by the shadow.

I use polygons, not areas. I just add points to that Polygon and then I send the polygon to render. Pretty fast and simple.

I calculate all the shadow polygons in real time, it is not very resource consuming. This is some graphical sample of what I do. lines p3-p4 and p2-p4 are ignored as they are in the back of the object.

https://dl.dropboxusercontent.com/u/96150028/bitmap.png

I agree with you that

This is part of the code (removed some non important stuff) . It is a mess and it is full of spanish variable names, so it may be quite confusing for you. I will try to fix this some day. Anyways I hope that you will get the general idea.

For any doubt, please ask me!! ;(

//I call this method n times for each light, where n is the number of objects inside the scene

//ii: object number ii of the scene
//luz_baseX,int luz_base, screen location of the light
//distancia_fuera: distance of shadow trail
//lightScale: scale of the light
//gsombras: volatileimage where I store all the shadows
//dimMaxMedia: size/ of the light


private void AddSombras_to_luz(int ii,int luz_baseX,int luz_baseY,float distancia_fuera,float lightScale,Graphics2D gbsombras,int dimMaxMedia){

    
          //LIGHT POSITION ON SCREEN (X,Y)
          float centroX=(luz_baseX*light_resolution);           
          float centroY=(luz_baseY*light_resolution);       
          
          int quita_baseX,quita_baseY;

           
          //POLYGON THAT STORES THE IMAGE CONTOUR
          Polygon quita;
           
          //GET POLYGON     
          
   
                if (!A_e.get_non_bg_objects(ii).get_cast_shadow()) return;               
                quita      =A_e.get_non_bg_objects(ii).getPolygon(tiempoms);

                
                //LOCATION OF THE UP-LEFT CORNER OF THIS POLYGON ON SCREEN
                quita_baseX=A_e.get_non_bg_objects_x(ii);
                quita_baseY=A_e.get_non_bg_objects_y(ii);
                
                
                        
          //NO POLYGON OR LESS THAN 3 VERTEX
           if (quita== null || quita.npoints<3) return;
           
         
           

           
          boolean anterior_shadow=false;
          int cont=0;
          int pos=0;
          
           //two points of the object polygon
            float pto1_x,pto1_y,pto2_x,pto2_y;
            
            //distance from these points to light source
            float dist1,dist2;
          
            pto2_x=((quita_baseX+quita.xpoints[0]));
            pto2_y=((quita_baseY+quita.ypoints[0]));
            dist2=(float)Math.sqrt( (pto2_y-centroY)*(pto2_y-centroY)+(pto2_x-centroX)*(pto2_x-centroX));
            
            

            
          //for each point of the polygon
          while(cont<quita.npoints){
          boolean actual_shadow=true;    
              
          
          //move point 2 to point 1
          pto1_x=pto2_x;
          pto1_y=pto2_y;
                    
           //read a new point from the polygon
          if (pos<quita.npoints-1) {
            pto2_x=((quita_baseX+quita.xpoints[pos+1]));
            pto2_y=((quita_baseY+quita.ypoints[pos+1]));}
          else {
            pto2_x=((quita_baseX+quita.xpoints[0]));
            pto2_y=((quita_baseY+quita.ypoints[0]));}  
          
          
         //distances to light source 
         dist1=dist2;
         dist2=(float)Math.sqrt( (pto2_y-centroY)*(pto2_y-centroY)+(pto2_x-centroX)*(pto2_x-centroX));
        
         
        //optimizations
        
         //this vertex is outside light range, it should not project shadow
        if (dist1>(dimMaxMedia)*lightScale   && dist2>(dimMaxMedia)*lightScale  ) {actual_shadow=false;}
        
        
        else{
       
        //the line between these two points creales a line. is this line in the front side of the object or in the back side
        //if it is in the back side it should not project a light (useless)
        
        //we calculate the bisector and we move us 4 pixels from the middle of the line to the line in the direction of the bisector.
        //is we are inside the polygon we don't project a shadow, othwerwise we project it
            
        float bisectriz_X=(pto1_x+pto2_x)*0.5f;
        float bisectriz_Y=(pto1_y+pto2_y)*0.5f;
        float dist_bis=(float)Math.sqrt( (bisectriz_Y-centroY)*(bisectriz_Y-centroY)+(bisectriz_X-centroX)*(bisectriz_X-centroX));
        
        float bcos_1=(bisectriz_X-centroX)/dist_bis;
        float bsin_1=(bisectriz_Y-centroY)/dist_bis;
        float binIn_X=(bisectriz_X -bcos_1*4);
        float binIn_Y=(bisectriz_Y-bsin_1*4);
  
        
        if (quita.contains((binIn_X*light_resolution_inv-(quita_baseX)),
                           (binIn_Y*light_resolution_inv-(quita_baseY)))) {actual_shadow=false;}}
        
        
        
        
        if (cont==0 && actual_shadow) {if (pos==quita.npoints-1) break; pos++;continue;}
        
        
        //even more optimization!!
        
        //cases    anterior_shadow, actual shadow
        //               false        true                               add pto 3 (shadow ray) y 1
        //               false        false                              no shadow is projected, we ignore these points.
        //               true         true                               add pto 1.
        //               true         false   (||actual=last)            add pto 1, pto 2, pto 4 (shadow ray) and render

        
                      float cos_1 = (pto1_x - centroX) / dist1;
                      float sin_1 = (pto1_y - centroY) / dist1;
                      float cos_2 = (pto2_x - centroX) / dist2;
                      float sin_2 = (pto2_y - centroY) / dist2;
        
              if (actual_shadow == true) {
                  if (anterior_shadow == true) {
                      //add point to the polygons
                      quita2.addPoint(Math.round(pto2_x+ cos_2*2), Math.round(pto2_y+ sin_2*2));
                      

                  } else {
                      float pto3_X = (centroX + cos_1 * distancia_fuera);
                      float pto3_Y = (centroY + sin_1 * distancia_fuera);
                       //add point to the polygons
                      quita2.addPoint(Math.round(pto3_X), Math.round(pto3_Y));
                      quita2.addPoint(Math.round(pto1_x+ cos_1*2), Math.round(pto1_y+ sin_1*2));
                      quita2.addPoint(Math.round(pto2_x+ cos_2*2), Math.round(pto2_y+ sin_2*2));}

                  if (cont == quita.npoints - 1) {

                      float pto4_X = (centroX + cos_2 * distancia_fuera);
                      float pto4_Y = (centroY + sin_2 * distancia_fuera);
                        //add point to the polygons
                        quita2.addPoint(Math.round(pto4_X+ cos_2*2), Math.round(pto4_Y+ sin_2*2));
                        
                     //render the polygon
                     { paintShadow(gbsombras);}
                      quita2.reset();}


              } else {
                  if (anterior_shadow == true) {
                      float pto3_X = (pto1_x + cos_1 * distancia_fuera);
                      float pto3_Y = (pto1_y + sin_1 * distancia_fuera);
                       //add point to the polygons      
                      quita2.addPoint(Math.round(pto3_X+ cos_1*2), Math.round(pto3_Y+ sin_1*2));
                      
                      //render the polygon
                           paintShadow(gbsombras); 
                      quita2.reset();
                      
                  }

              }
        
                   anterior_shadow=actual_shadow;
                   cont++;
                   pos++;if (pos==quita.npoints) pos=0;
          }

}

Sweet. I am not sure if you do this already but give the lights a max radius and check if a shadow caster is inside before you add its shadow. You cull out points but you could drop the whole shape much faster.

I do the exact same thing with circles and squares. I only used areas with general polys as I could not solve the issue where a object casts a light on its self. I can get the front or back facing polys very very fast. The issues is when the object has an edge that could occlude an inner part of itself. Normally you project the points furthest (perpendicular from the center point of the shape and light point) out; p2 and p3 in your picture. But if you get some edge that protrudes out. Ray casting deals with these but requires checking if a ray intersects the shape and the point of said intersection to form the polygon shadow.

I looks like you do that towards the end but I am not quite sure how.

Hehe it is Friday night here.