Wednesday, July 29, 2015

Dynamic Lights, Platforms, and Triggers

My previous blog posts were about features I had added to 3DWorld long before that post, because I had some catching up to do. This time I'm going to talk about the dynamic lighting and moving platform system in 3D world, along with the player trigger system that controls it. I've been working on these features recently and some of them are still unfinished. This post will be more technical than some of my previous posts, but I'll try to add some pretty screenshots at the end showing the lighting to make it more interesting.

Some of these ideas were based on the game Marathon from Bungie Software that I played on my Mac when I was an undergrad at CMU. The game came with a level editor that had lights and platforms as level objects that could be configured and activated by player and AI/enemy actions. I found this system a simple and intuitive method of configuring lights and scripted moving objects, so I've copied many of the parameters in 3DWorld. The scene text file specifies all of the lights, platforms, and triggers as keyword + values. I'll start with the triggers.

Triggers
Triggers are non-visible scene entities that can be used to enable and disable other scene objects such as lights and platforms. They are added to the scene text file and assigned to other objects, where a single trigger can be reused multiple times to control more than one object. A different trigger may be used to enable vs. disable an object. Triggers are activated and/or deactivated by distance (player and/or enemy proximity), player activation regions, or actionable objects (switches). A trigger can have auto on or off time delays. For example, a player can press a switch to turn a light on and it will turn itself off after a specified amount of time. Or a platform can automatically start moving when a player or enemy walks on it, and stop moving when the player or enemy steps off. Or a light can be assigned both an auto-on and auto-off time so that it blinks on and off in a loop. There is no limit to the number or usage of triggers in a scene.

Platforms
Platforms are really just a collection of objects that move together along a fixed path. So far I have only implemented translation, which allows creation of elevators, sliding doors, crushers, moving walkways, etc. Rotations are more difficult because they change the classification of some of the physics object types. For example, a rotated axis-aligned cube may no longer be axis-aligned. Platforms are specified using several parameters (similar to those used in Marathon):
  • Position delta {x, y, z} (determines max extension/end point)
  • Forward Speed
  • Backward Speed
  • Activation Delay 
  • Reverse (Forward => Backward) Delay
A platform's motion is implemented as a finite state machine with the following sequence of states: {Inactive => Active Delay => Forward => Reverse Delay => Backward => Inactive}. For example, an elevator that starts on the bottom floor when inactive will travel up at some speed after an initial delay, reach the top floor, stop there for some time, travel back down at the same speed, then deactivate. I placed small black cylinders at each of the 5 floors of the building for use as elevator buttons. Each button has a trigger object bound to it, and all triggers control the elevator platform activation.

Any entities (the player, enemies, bombs, explosion debris, blood and bullet hole decals, etc.) resting on/attached to a shape or polygon that's part of the elevator platform moves with it. In addition, the player can be crushed to death under a platform in gameplay mode. Triggers can be bound to platform shapes so that they move with the platform. This means that a light switch can be placed inside the elevator and moves with it.

Lights
Lights are currently statically attached to the scene, but can be bound to scene objects so that the lights are disabled when the objects are destroyed. The exception are the player flashlight and candle. This allows the level editor to make light sources dynamically destroyable in-game. One or more triggers may be assigned to control the light. If no triggers are assigned, then the light is always on until it's destroyed. 3DWorld supports directional light sources for the sun and moon plus three types of user-placed dynamic lights:
  • Point Lights (position, radius, and color)
  • Spotlights (position, direction, beamwidth, radius, and color)
  • Line Lights (start and end position, radius, and color)
Point lights and spotlights are pretty standard in games, but line lights are a bit different from what I've seen before. I implemented line lights in the fragment shader using the analytical equation for the light contribution to the fragment (pixel) integrated along the length of the line. They were originally only used for laser beams, but I decided that line lights make nice looking fluorescent tubes.

Light sources can have both direct (diffuse and specular) and/or indirect contributions. The indirect lighting is baked into the scene in a preprocessing step and read from disk at load time, so it only makes sense to enable indirect lighting for static, always-on, non-destroyable lights. As explained in the dynamic lighting post, the shader system can handle a large number of light sources efficiently, currently up to 1024 lights in a scene.

Shadows
Currently, 3DWorld only supports dynamic shadow maps for the sun, moon, and spotlights. These types of lights are easy to do because a single square shadow map works well. Point lights are typically implemented with cube map textures in other engines, and I don't have cube map shadows working yet in 3DWorld. However, some of the point lights on the walls and ceiling still look good when treating them as spotlights for the purpose of shadowing - as long as shadow casters can't get between the light and the wall/ceiling.

The dynamic light shadow maps are currently all 512x512 pixel square textures of a 32-bit depth format, so each one takes 1MB of video memory. I prefer to use simple shadow maps rather than PSMs (perspective shadow maps), etc. because of their stability. I really hate it if the shadow edges flicker when the camera is rotated. I currently have a limit of 16 enabled, visible, shadowed spotlights on my old computer and 32 on the new computer. The limits are mostly there to keep the framerate high when there are many scene lights, by disabling shadow maps for some of them. None of my scenes hit this limit.

Shadow maps are only updated when objects within the light's FOV (field of view) move, or if the light itself moves. Only objects within the FOV are rendered into the shadow maps for efficiency. If all of the potential shadow casters and the light itself are static objects (or happened to not move this frame), the shadow map from the previous frame can be reused, saving some GPU work. Shadow maps for inactive or destroyed lights are also returned to a global pool for reuse. This way, the player can move to different rooms of a scene and the engine only needs to have a small number of shadow maps in GPU memory at any given time. The pool of shadow maps is stored in a single OpenGL texture array that doubles in size when necessary, to reduce the number of texture units used and the number of texture binds/state changes. [I originally used a separate texture + texture-unit for each one, but that caused problems with resource usage on the ATI card in my old computer even though my newer computer with Nvidia card was fine with it.]

The unshadowed lights cause significant light leaking problems. For example, if the lights are on in the basement, their large radius of influence can affect objects in the floor above them. Since light normally doesn't shine through floors (except for those who live in glass houses), this looks obviously wrong. My solution is to disable lights that produce no direct illumination that is visible to the player. Around 100 random, but approximately uniformly distributed, rays are sent out from the light source center within the field of view of the light (light cone for spotlights, hemisphere for lights on a wall/ceiling, etc.) These rays are representative photons. Their hit locations are points in the scene that are directly illuminated by the light source. Another ray is sent from each hit location to the camera, and if no scene objects are intersected then this point is visible to the player, the light must be enabled, and we're done. If none of the hit points have a line-of-sight to the camera, then the light source likely has no influence on the scene and can be disabled. It's not 100% correct since the rays are only sparse samples, but it works well in practice, especially if care was taken during scene/light layout. The scene bounding volume hierarchy provides an efficient acceleration structure for these ray tests, and hit locations can be cached. Overall this approach improves the framerate by reducing the number of active lights. For example, if a light is in a closet behind a closed door, all of the rays from the light source will hit the inside of the closet. None of those hit points will be visible to someone standing outside of the closet (all secondary rays will hit the closet wall and door) and the light can be skipped. The player will never know if the light is on in that closet unless they go inside.

Here are two screenshots of the building lobby with a single large point light source. This light is always on (static) and has indirect lighting enabled. You can see the shadows of the Chinese dragon on the counter top and the soft shadows from the counter and round table on the floor. The indirect lighting component is high because of the high surface albedo of the ceiling, walls, and floor, so the shadows aren't very strong.

The lobby scene illuminated by a single large point light source with indirect lighting. Note the shadow of the dragon statue on the table/desk/counter and the soft shadows around the edges.

Closeup of the golden dragon statue, which is mostly lit by specular light. It is self-shadowing and shadows the counter top.

Here are some screenshots taken in the basement of the building. I put a lot of scene lights in here since there are only two small entrances and very little outside light reaches this area. Thus it has good contrast between lit and unlit surfaces and the shadows are very pronounced. It's good for testing. The lights in the straight hallway are player motion activated. The spotlights along the top of the wall and the line light on the opposite wall are activated by the pink and yellow illuminated light switch. They are set to automatically turn off after 1 minute.

A variety of player-controlled colored point and spot lights in the basement scene.


Basement scene with line light and spotlights in the distance, including normal mapping.

Here you can see that the orange spheres (the "balls" that can be thrown in gameplay mode) create shadows on the ceiling and floor. It's hard to tell from this screenshot, but they also shadow each other. It's normally not possible to throw this many balls at once in gameplay mode, so I had to freeze time in order to do it. One ball is just in front of and above the light so it casts a large shadow on the ceiling. The player also casts a capsule-shaped shadow that's not seen below.

Spheres casting spotlight shadows on the walls, floor, ceiling, and each other.

This is a screenshot of the house map with line lights representing fluorescent tubes in the ceiling. They are controlled by a light switch behind the player and another switch at the top of the stairs. Both switches toggle the light on/off state. You can also see a bright circle from the player's flashlight beam, which is another example of a (moving) spotlight. I experimented with adding a flashlight shadow but it doesn't really add anything useful to the scene. Since the flashlight is mounted to the player's "eye" it's always in the center of the screen, and it's impossible to see anything in the flashlight's shadow.

House scene with line light sources in the ceiling and a player flashlight spotlight.

Here is a picture of the elevator with the light on and a big flashing red warning light on the ceiling.
Player controlled working elevator with lighting. The button the the right wall activates the elevator. Each floor has a button.

I'll end this post with a video of me running around the building basement, activating lights and the elevator, destroying stuff, and shooting out the lights. Much of the scene is destroyable, but I didn't have time to shoot at everything in this large scene in this video. Note that shooting the gas tank on the left causes it to explode, which also destroys the tank on the right and takes out the light on the ceiling. The elevator light only turns on when the player is in the elevator. Oh, and yes, the player shadow is a big capsule/ellipsoid shape. Here is the Blogger version of the video:


And here is the YouTube version:


Which do you think looks better? It looks like the YouTube version is better.


3 comments:

  1. Formatting: "Here you can see that..." should be on a new line. It's bunched up on the right side of the image for me.
    I really like the sparse raycasting approach for non-shadow-casting light activation! Kind of like a binary ray-traced shadow system?
    The "indirect lighting" doesn't look as good as the baked 3D light volume map from before. Is it the same system? The surface geometry details look all washed out.
    The line-lights are a nice touch too. Could you use them to make neon signs?

    ReplyDelete
  2. Really? I don't see that text on the right side of the image. I changed the image from right aligned to center aligned, did that fix it?

    The light ray casting system serves two purposes: it helps improve frame rate when lights are occluded through walls, and it reduces light leakage through walls. I use the same system for procedural buildings today. The current system also computes the bounding cube of the light volume from the ray hit points, which allows for extra occlusion tests and reduction in light tile setup time for shading.

    Indirect lighting here has two differences. First, there are fewer light rays cast for these light sources to improve framerate for dynamic updates, which increases the amount of noise. Second, the lighting voxels are much larger here because the scene is huge. This means that indirect lighting is lower resolution. I decreased the intensity/contrast a bit as well to try and hide this. Some of the later scenes that have multiple buildings use per-building indirect light volumes rather than one global volume, and this works better.

    ReplyDelete
    Replies
    1. Yep, text looks fine now.
      Ooh, a bounding cube for direct lighting is another good improvement.
      Okay, interesting.

      Delete