We covered how to create static skyboxes in Chapter 1, SDK Game Development Hub. While they are fine for many implementations, some games require day and night cycles.
This recipe will show us how to create a moving sun, which can be superimposed on a regular skybox. In this case, a neutral skybox without any protruding features such as mountains will work best. We'll also learn how to make a sky that changes color during the day. In this case, no skybox is required.
We will also need a texture that should have a transparent background with a filled, white circle in it, as shown in the following figure:
SimpleApplication
.simpleInitApp
method, we first need to create Geometry
for the sun:Geometry sun = new Geometry("Sun", new Quad(1.5f, 1.5f));
sun.setQueueBucket(RenderQueue.Bucket.Sky); sun.setCullHint(Spatial.CullHint.Never); sun.setShadowMode(RenderQueue.ShadowMode.Off);
Material
instance based on the unshaded material definition.ColorMap
, we load the texture with the white circle in it and apply the texture. Then, for Color
we can set an almost white color with a tint of yellow in it. We also have to enable alpha in the material:sunMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); sunMat.setTexture("ColorMap", assetManager.loadTexture("Textures/sun.png")); sunMat.setColor("Color", new ColorRGBA(1f, 1f, 0.9f, 1f));
So, the basic Geometry
is set up and we can create a Control
class to move the sun across the sky by performing the following steps:
SunControl
, which extends AbstractControl
.float
field called time
, a reference to the application camera called cam
, a Vector3f
field called position
, and a DirectionalLight
field called directionalLight
.controlUpdate
method, we start by finding the x
and z
positions based on the time and multiply the result to move it some distance away. We can also make the sun move up and down by doing the same for the y
value:float x = FastMath.cos(time) * 10f; float z = FastMath.sin(time) * 10f; float y = FastMath.sin(time ) * 5f; position.set(x, y, z);
localTranslation
of the sun. Since we want it to appear to be very far away, we add the camera's location. This way it will always appear to be the same distance from the camera:spatial.setLocalTranslation((cam.getLocation().add(position)));
spatial.lookAt(cam.getLocation(), Vector3f.UNIT_Y);
directionalLight
field is set, we should also set its direction. We get the direction by inverting position
, as shown in the following code:directionalLight.setDirection(position.negate());
time
value by a factor of tpf
(depending on how fast we want the sun to move). Since two PI in radians make up a circle, we start over once time
exceeds that value, using the following code:time += tpf * timeFactor; time = time % FastMath.TWO_PI;
application
class, we add the control to the Geometry
sun and Geometry
to the scene graph:sun.addControl(sunControl); rootNode.attachChild(sun);
The previous implementation can be enough for many games but it can be taken much further. Let's explore how to make the sun color dynamic based on its height above the horizon and how to also have a dynamic sky color by performing the following steps:
ColorRGBA
fields in the SunControl
class called dayColor
and eveningColor
. We also add another ColorRGBA
field called sunColor
.controlUpdate
method, we take the y
value of the sun and divide it so that we get a value between -1
and 1
, and store this as the height.ColorRGBA
has a method to interpolate two colors that we can use to get a smooth transition during the day:sunColor.interpolate(eveningColor, dayColor, FastMath.sqr(height));
directionalLight
to the same as sunColor
and also set the material's Color
parameter to the same:directionalLight.setColor(sunColor); ((Geometry)spatial).getMaterial().setColor("Color", sunColor);
Handling the sky color will take a bit more work. To do this, perform the following steps:
SkyControl
extending AbstractControl
.SunControl
, the SkyControl
class needs a Camera
field called cam
. It also needs a ColorRGBA
field called color
and three static ColorRGBA
fields for different times in the day:private static final ColorRGBA dayColor = new ColorRGBA(0.5f, 0.5f, 1f, 1f); private static final ColorRGBA eveningColor = new ColorRGBA(1f, 0.7f, 0.5f, 1f); private static final ColorRGBA nightColor = new ColorRGBA(0.1f, 0.1f, 0.2f, 1f);
SkyControl
class needs to know about the sun's location so we add a SunControl
field called sun
.controlUpdate
method, we set the localTranslation
of the spatial to the location of the cam
.eveningColor
and dayColor
. Otherwise, we interpolate between the eveningColor
and nightColor
instead. Then, we set the resulting color in the sky's material's Color
parameter, as shown in the following code:if(sunHeight> 0){ color.interpolate(eveningColor, dayColor, FastMath.pow(sunHeight, 4)); } else { color.interpolate(eveningColor, nightColor, FastMath.pow(sunHeight, 4)); } ((Geometry)spatial).getMaterial().setColor("Color", color);
application
class, we create a box shaped Geometry
called sky
10f
sides.QueueBucket
, ShadowMode.Offand CullHint.Never
settings applied to it.getAdditionalRenderState
and set FaceCullMode
to FaceCullMode.Off
.Always causing the geometries of this recipe follow the camera around is one of the parts that make this recipe work. The other trick is using the Sky QueueBucket
. The Sky QueueBucket
can be thought of as lists of items to be rendered. Everything in the Sky bucket is rendered first. Because it's rendered first, other things will be rendered on top of it. This is why it appears to be far away even though it's really close to the camera.
We also use the direction of the sun from the camera for DirectionalLight
in the scene, making it follow the sun as it moves across the sky.
When updating the control, we handle the movement of the sun using the time
value, which increases with each update. Using FastMath.sin
and FastMath.cos
for the x
and z
values, we get it to move in a circle around the camera. Using FastMath.sin
again for the y
value will move it in an arc above (and below) the horizon. By multiplying the y
value, we can get it to rise higher in the sky.
The resulting position was added to the camera's location to always make the sun centered around the camera. Since the sun is a simple quad, we also had to rotate it to face the camera with every update.
We went on to change the color of the sun based on the height above the horizon. We used the interpolate method of ColorRGBA
to do this. Interpolation requires a value between 0.0
and 1.0
. That's why we needed to divide the y
value by the max y
value (or amplitude) in the case where we've multiplied it earlier to get a higher arc in the sky.
The movement of the box simulating the sky is similar. We just keep it centered around the camera so that even if it's a small box, it appears to cover the whole sky. Normally, we wouldn't see the sides of the box when we're inside it so we set FaceCullMode
to Off
to always make it render the sides.
SkyControl
was fitted with three instances of ColorRGBA: dayColor
with a bluish tint, eveningColor
with orange, and nightColor
almost black. The SunControl
was supplied to the control and used to interpolate between the colors based on the height of the sun. Anything above 0.0f
is considered day.
In this implementation, the whole sky changes color with the sun. Any further development of SkyControl
could include a more complex shape, such as a cylinder or sphere where only the vertices on the same side as the sun change color. Clouds can be implemented and they also use a quad that moves in the xz-plane.
Another improvement would be to have a night-time, star-filled skybox outside of the box we made and fade the alpha value of nightColor
to let it gradually shine through the night time.
If we try the recipe with the unshaded material definition for the sky, it will work well in most cases. However, when it comes to the postprocessor water filter, it will not pick up the sky color properly. To achieve this, we will have to make some modifications to its material. We don't need to actually change any of the .vert
or .frag
files, but can create a new Material Definition (.j3md)
file.
To make things as easy as possible, we can copy the Unshaded.j3md
file. Refer to the following code within the Unshaded.j3md
file:
VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert
Replace the previous line with the following line:
VertexShader GLSL100: Common/MatDefs/Misc/Sky.vert
This means we'll be using the vertex shader normally used by the Sky material to handle the positions of the vertices for the renderer.
We also need to change the WorldParameters
segment to contain the following:
ViewMatrix ProjectionMatrix WorldMatrix
18.188.198.94