Light Flares in UT

How these guys from Epic done this? The effect of light flare is cool. Are threr any tutus about it?

Here’s what I do in my engine

The code below updates last time certain light was visible. Hope it’s clear enough…

You should call this after the whole scene is rendered…

int i;
GLdouble mvmatrix[16];
GLdouble x, y, z;
float z2;

glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);

for (i = 0; i < numLights; i++)

  if (frustum->testPoint( lights[i].bsphere.mid ))
  {
  	// Project it
  	gluProject(
  		lights[i].bsphere.mid[0],
  		lights[i].bsphere.mid[1],
  		lights[i].bsphere.mid[2],
  		mvmatrix, screen->projmatrix, screen->viewport,
  		&x, &y, &z);

  	// Fits in screen bounds?
  	if ((x < 0) &#0124; &#0124; (SCREEN_WIDTH <= x) &#0124; &#0124; (y < 0) &#0124; &#0124; (SCREEN_HEIGHT <= y))
  		continue;

  	// Get z value from z-buffer
  	glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z2);

  	// Closer?
  	if (z > z2)
  		continue;

  	// Visible! - update last visiblity time
  	lights[i].lastTime = clock.time;
  }

Now all you need is to display your lights with blend params depending on last visibility time, here you go…

int i;

glBindTexture(GL_TEXTURE_2D, lightTex.id);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
glDepthMask(GL_FALSE);
glDisable(GL_DEPTH_TEST);

for (i = 0; i < numLights; i++)
{
if (clock.time - lights[i].lastTime > 500)
continue;

  glPushMatrix();
  	
  	glTranslatef(lights[i].bsphere.mid[0], lights[i].bsphere.mid[1], lights[i].bsphere.mid[2]);
  	glRotatef(-view->angles[1], 0, 1, 0);			
  	glRotatef(-view->angles[0], 1, 0, 0);
  	glRotatef(-view->angles[2], 0, 0, 1);

  	glColor4f(
  		lights[i].rgb[0],
  		lights[i].rgb[1],
  		lights[i].rgb[2],
  		(500.0f - (clock.time - lights[i].lastTime)) * 0.002f);

  	glScalef(
  		lights[i].bsphere.radius,
  		lights[i].bsphere.radius,
  		lights[i].bsphere.radius);

  	glBegin(GL_QUADS);				
  		
  		glTexCoord2f(1, 1);
  		glVertex3f(-1, -1, 0);

  		glTexCoord2f(0, 1);
  		glVertex3f(-1, 1, 0);

  		glTexCoord2f(0, 0);
  		glVertex3f(1, 1, 0);

  		glTexCoord2f(1, 0);
  		glVertex3f(1, -1, 0);

  	glEnd();

  glPopMatrix();

}

Should work although actually you should also do different light corona scaling - until certain distance this should be “distance from viewer” independent.

…or maybe I misunderstood you completely ,…are light flares (what I call light coronas) the kind of lights seen as a circle of some color visible only when an eye of player can see it directly??

sorry if it’s not what you asked for…

Yes, this is exactly what i mean. If i see a small light source and a large corona, then when it is covered by a thin object, corona should also disappear.
But, in Your code You use ReadPixels.
Isn it too slow.
UT on my machine reaches 100FPS, so it must be done in different technique.

Pozdrawiam,

glReadPixels isn’t that slow when it’s only 1x1 pixel area.

OK, but how can i find this 1x1 area?
Thats why i am asking for any tuts.

Sorry, sorry.

The answer is in the code.

Wielkie dzieki, Miki Mouse.

UT doesn’t read the zbuffer. You can tell by moving a character in front of a corona. You’ll see that it doesn’t disappear. If it were reading the zbuffer it would disappear.

UT traces a line from viewer to light and tests this against the BSP tree. So only scene elements hide the light.

Originally posted by fresh:
[b]UT doesn’t read the zbuffer. You can tell by moving a character in front of a corona. You’ll see that it doesn’t disappear. If it were reading the zbuffer it would disappear.

UT traces a line from viewer to light and tests this against the BSP tree. So only scene elements hide the light.[/b]

That’s possible. But it’s also possible that the zbuffer read was done before drawing the dynamic elements (i.e. characters), right? Just a thought.

Good observation though.

If you’re gonna read the zbuffer you might as well do it properly and draw everything. I don’t see why they’d chose to read the zbuffer before drawing the characters. It looks like crap when you see a corona shine through a character.

Reading the zbuffer forces a flush in the graphics pipeline. I’m 99.9% certain they’r enot reading the zbuffer.

You don’t want to be running more than one frame behind anyway. The engine I work on does something like this for lens flare (more or less same as corona):

  1. read back where light source was last frame, in screen space
  2. clear Z
  3. draw skybox
  4. draw terrain
  5. draw objects
  6. if light source Z value == distance to light, make corona more visible, else make corona less visible (to fade in/out over a few frames)
  7. draw lens flare/corona with visibility calculated, if visible at all
  8. run physics
  9. swapbuffers

You will note that this version gives excellent overlap between the CPU and the GPU, and it still uses Z buffer read (which is a problem on cards like i845 and Kyro, though…)

I believe they ray cast with a low pass filter (z or color readback could work too but might be inefficient). In other words they test a ray between the viewer and potentially visible lights each frame (or at least every few frames) When the light is visible they enable the flare, fading it in over several frames, when the light is not visible they do not disable the flare, they fade it out. This avoids the flare flicking on and off suddenly which would be distracting and look unreal (excuse the pun).

The disadvantage of doing this is the flare is often partly visible for a few moments even when the light is obviously hidden from view. Doing the right thing (testing for the exposed area of the flare) would be too expensive for many lights, but has been done…

When rendering, the flare is simply drawn last as a bilboard with no z testing or writing.

The ‘state of the art’ way to accomplish this kind of effect in an even more compelling way was shown by ATI in their HDR (high dynamic range) rendering demo albeit on a high end card, where a convolution filter can be applied in hardware (using straightforward multitexture taps from the same texture) over an image that renders beyond the brightest intended visible range for the whole scene (I believe they actually drew a smaller snapshot for this particular stage). It is much more convincing than simple point lights, the whole sky or portions of it can bloom around the scene in a very compelling way. And of course your lights can bloom/flare too.

Similar things have been done elsewhere, for example rendering the Sun’s glare in the “Nature” demo, by drawing the sun hidden by the scene to a very small texture and using that to apply the glare in eye space.

[This message has been edited by dorbie (edited 10-09-2002).]

[]Similar things have been done elsewhere, for example rendering the Sun’s glare in the “Nature” demo, by drawing the sun
hidden by the scene to a very small texture and using that to apply the glare in eye space.[]

Hmm, thats interesting. You say that this small texture is expanded and used as a glare?
I have heard that it can be achieved by:
-rendering to small texture (say 16x16)

  • read it and average all pixels,
  • modulate the color with sun glare,

BUT, isnt reading the texture (downloading) to average the intensity of glare as much ineficent as reading Z-buffer?
Also ive heard that rendering to texture on GeForce2 “runs like crap”.

Best regards. Michal Krol.

[This message has been edited by MichaelK (edited 10-09-2002).]

You can also use the Pixel Data Range Extension (see new nvOGLSpecs on developer.nvidia.com) from Nvidia to accelarate the zbuffer access.

And the big advantage is, it also works with alphatested pixels (trees). The method with raycasting wouldnt work here. And HDR isn’t possible on every hardware…my poor gf2go :frowning:

Lars

jwatte, yes you’re right it’s no big deal to read back the zbuffer (despite what devrel wants you to believe! ). I have used this method in a previous game with no speed loss. I was just saying that in the specific case of UT, it appears that they’re not reading the zbuffer. You can tell by the fact that only the static scene elements seem to occlude the coronas, and not dynamic objects like characters. My point is that if you’re gonna read the zbuffer, you might as well do it right and do the read after you finish drawing everything.