Complete sample code for both OpenGL and Direct3D is found in the SampleCode/OpenGLExample, SampleCode/DirectX9Example, SampleCode/DirectX10Example, and SampleCode/DirectX11Example directories of the SDK (The Linux SDK only includes OpenGL). Here's an overview of some of the key bits of code found in each.
When your application starts up, you'll want to initialize OpenGL or Direct3D, and create and initialize an Atmosphere object. If you want your clouds to be different every time your application is run, be sure to seed the random number generator as well.
Under DirectX9, it's recommended that you create your IDirect3DDevice object without the D3DCREATE_PUREDEVICE flag. Silverlining needs to call GetTransform and GetRenderState on your device, in order to put things back the way we found them when we're done drawing. We also recommend creating your device with the D3DPRESENTFLAG_LOCKABLE_BACKBUFFER flag, which will allow you more flexibility in configuring how SilverLining does its lighting (specifically, this will allow you to set render-offscreen to "no" in SilverLining.config.)
If your Atmosphere initializes successfully, you can go ahead and start configuring it to add your clouds, and specify the time and location you wish to simulate. We'll get into that next. If it does not initialize successfully, it's likely that the user needs to update the graphics driver. SilverLining has fallback cases for most cases where compatibility might be an issue, but some older drivers have issues even with standard functionality.
// If you want different clouds to be generated every time, remember to seed the // random number generator. srand(time(NULL)); // Initialize your Direct3D device object, glut, or wgl as appropriate InitializeGraphicsSubsystem(); // Instantiate an Atmosphere object. Substitute your own purchased license name and code here. atm = new Atmosphere("Your Company Name", "Your License Code"); atm->ShowFramerate(true); // Tell SilverLining we're rendering in OpenGL, the Resources directory is 2 directories // above the working directory, and we're using a right-handed coordinate system. if (atm->Initialize(Atmosphere::OPENGL, "..\\..\\Resources\\", true, 0) == Atmosphere::E_NOERROR) { // Set up all the clouds SetupAtmosphericConditions(); // Configure where and when we want to be SetTimeAndLocation(); // Start rendering. glutMainLoop(); }
Let's flesh out SetupAtmosphericConditions() from above. Once you have an Atmosphere object initialized, you can access its AtmosphericConditions object to do things like add cloud decks, change the time of day, and simulate wind. Let's write a code snippet to add a cirrus deck, a cumulus congestus deck, and make it a windy day. The code is self-explanatory:
static void SetupCirrusClouds() { CloudLayer *cirrusCloudLayer; cirrusCloudLayer = CloudLayerFactory::Create(CIRRUS_FIBRATUS); cirrusCloudLayer->SetBaseAltitude(8000); cirrusCloudLayer->SetThickness(500); cirrusCloudLayer->SetBaseLength(100000); cirrusCloudLayer->SetBaseWidth(100000); cirrusCloudLayer->SetLayerPosition(0, 0); cirrusCloudLayer->SeedClouds(*atm); atm->GetConditions()->AddCloudLayer(cirrusCloudLayer); }
The above routine will create a Cirrus cloud (the high, wispy ones) at an altitude of 8,000 meters and 100 km across. It's centered at the camera position.
Anytime you create a new CloudLayer, you must first
Similarly, let's set up a cumulus congestus deck:
// Add a cumulus congestus deck with 40% sky coverage, which stays centered around the camera position. static void SetupCumulusCongestusClouds() { CloudLayer *cumulusCongestusLayer; cumulusCongestusLayer = CloudLayerFactory::Create(CUMULUS_CONGESTUS); cumulusCongestusLayer->SetIsInfinite(true); cumulusCongestusLayer->SetBaseAltitude(1500); cumulusCongestusLayer->SetThickness(100); cumulusCongestusLayer->SetBaseLength(30000); cumulusCongestusLayer->SetBaseWidth(30000); cumulusCongestusLayer->SetDensity(0.4); cumulusCongestusLayer->SetLayerPosition(0, 0); // Enable convection effects, but not growth: cumulusCongestusLayer->SetCloudAnimationEffects(0.1, false, 0); cumulusCongestusLayer->SeedClouds(*atm); cumulusCongestusLayer->GenerateShadowMaps(false); atm->GetConditions()->AddCloudLayer(cumulusCongestusLayer); }
Our SetupAtmosphericConditions function will set up the simulated wind, call the above two functions to create the cirrus and cumulus cloud decks, and finally set the simulated visibility - which will affect the fog effects on the clouds themselves:
// Configure SilverLining for the desired wind, clouds, and visibility. static void SetupAtmosphericConditions() { // Set up wind blowing south at 50 meters/sec WindVolume wv; wv.SetDirection(180); wv.SetMinAltitude(0); wv.SetMaxAltitude(10000); wv.SetWindSpeed(50); atm->GetConditions()->SetWind(wv); // Set up the desired cloud types. SetupCirrusClouds(); SetupCumulusCongestusClouds(); // Set visibility in meters atm->GetConditions()->SetVisibility(100000); }
If you wish to simulate a particular place and time, you should also set that up in your initialization. You can also change this at any time while the application is running. Be sure that the time zone you specify in the LocalTime object is consistent with the longitude you specify in the Location object, or else you'll be very confused by the results! (It's also OK to specify all times as in the GMT time zone if you want to use UTC time consistently instead of local times.)
// Sets the simulated location and local time. // Note, it's important that your longitude in the Location agrees with // the time zone in the LocalTime. static void SetTimeAndLocation() { Location loc; loc.SetLatitude(45); loc.SetLongitude(-122); LocalTime tm; tm.SetYear(1971); tm.SetMonth(8); tm.SetDay(5); tm.SetHour(14); tm.SetMinutes(0); tm.SetSeconds(0); tm.SetObservingDaylightSavingsTime(true); tm.SetTimeZone(PST); atm->GetConditions()->SetTime(tm); atm->GetConditions()->SetLocation(loc); }
Cumulus congestus, cumulus mediocris, stratus, cirrus, stratocumulus, and cirrocumulus cloud layers may be modified by using the CloudLayer::SetIsInfinite() method.
Infinite cloud layers will stay centered at the camera location; as the camera moves, clouds that leave the bounding area defined by the cloud layer's length and width will be repositioned to pop in where the camera's moving toward. Similarly, if wind blows clouds outside of the cloud layer, they will wrap around to the other side of the layer.
Infinite cloud layers allow you to not worry about positioning the cloud layer, or setting up multiple cloud layers to cover large areas.
This gives you the effect of an "infinite" cloud layer, where the clouds will never blow away and you can't move the camera away from them. The larger the length and width you create the cloud layer with, the less noticable the popping will be as clouds are repositioned - especially if the clouds are being fogged in the distance by setting AtmosphericConditions::SetVisibility() to a value similar to the cloud layer's dimensions. You can also use CloudLayer::SetFadeTowardEdges() to fade the clouds out before they reach the edge of the layer, ensuring popping is never visible.
CloudLayer::SetIsInfinite() should be called when initializing the cloud layer; by default, cloud layers are not infinite. Cumulonimbus cloud layers are not affected by SetIsInfinite(), as they only contain a single cloud.
Cumulus cloud layers support optional animation effects, to simulate convection and growth of the clouds at runtime. Use the CloudLayer::SetCloudAnimationEffects() method to control these effects.
The first parameter controls whether individual cloud puffs will rotate at a random rate, giving the clouds a convection effect. The value is the maximum rotation in radians per second. There is no performance cost to this effect.
The second parameter controls a real dynamic cloud growth simulation, powered by cellular automata. This does incur a performance hit on the CPU, but in most cases it is negligable. If you do enable growth effects, the third parameter lets you control how "evolved" the cloud is at startup time. Leave it set to zero to start off with fully grown clouds that change their shape gradually, or to a smaller value such as one to watch the clouds form in real-time. The fourth parameter controls the number of seconds between iteration of the cellular automata. Set this to longer values for slower growth effects, or pass 0 to use the default values.
When used together with time-lapse effects (by using a custom MillisecondTimer class,) dramatic effects are possible with clouds shifting and growing over time.
That's pretty much it for initialization. Now, how do you integrate SilverLining with the rendering of each frame? It's quite simple. When you render a frame of animation, you'll want to follow these steps:
Here's an example of the render loop under OpenGL:
void Display() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, aspectRatio, 2, 100000); // Increment the yaw each frame to spin the camera around yaw += 0.05; glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(-10, 1, 0, 0); glRotatef(yaw, 0, 1, 0); glTranslatef(0, -100, 0); // Pass in the view and projection matrices to SilverLining. double mv[16], proj[16]; glGetDoublev(GL_MODELVIEW_MATRIX, mv); glGetDoublev(GL_PROJECTION_MATRIX, proj); atm->SetCameraMatrix(mv); atm->SetProjectionMatrix(proj); // After setting up your projection and modelview matrices to reflect the current // camera position, call Atmosphere::DrawSky() to draw the sky and do the lighting // pass on the clouds, if necessary. atm->DrawSky(true); // Now, do all your own drawing... SetSceneLighting(); SetSceneFog(); DrawGroundPlane(); // When you're done, call Atmosphere::DrawObjects() to draw all the clouds from back to front. atm->DrawObjects(); // Now swap the back and front buffers. glutSwapBuffers(); glutPostRedisplay(); }
And a main rendering loop under DirectX 9... note that this example uses a right-handed coordinate system. The handedness must match what you specified when initializing the Atmosphere object. This example doesn't handle lost devices; you can simply delete and recreate your Atmosphere object when a device is lost in order to release and recreate its resources.
DirectX10 and DirectX11 would be similar, but instead of setting the view and projection matrices into the device's fixed function pipeline as you would in DirectX9, you'll need to pass those to your vertex programs directly. See the DirectX 10 and 11 sample code provided with the SDK for a complete example.
static void RenderFrame(HWND hWnd) { static float lastTime = (float)timeGetTime(); if (atm && device) { D3DXMATRIX Rot, Yaw, Pitch; D3DXMatrixRotationX(&Pitch, -10.0f * (3.14f / 360.0f)); D3DXMatrixRotationY(&Yaw, yaw); D3DXMatrixMultiply(&Rot, &Yaw, &Pitch); D3DXMATRIX Pos; D3DXMatrixTranslation(&Pos, 0, -100, 0); D3DXMATRIX view; D3DXMatrixMultiply(&view, &Pos, &Rot); device->SetTransform(D3DTS_VIEW, &view); // // Set projection matrix. // D3DVIEWPORT9 vp; device->GetViewport(&vp); D3DXMATRIX proj; D3DXMatrixPerspectiveFovRH( &proj, 45.0 * (D3DX_PI / 180.0), (float)vp.Width / (float)vp.Height, 2.0f, 200000.0f); device->SetTransform(D3DTS_PROJECTION, &proj); // Set view and proj matrices with SilverLining if (atm) { double pView[16], pProj[16]; int i = 0; for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { pView[i] = view(row, col); pProj[i] = proj(row, col); i++; } } atm->SetCameraMatrix(pView); atm->SetProjectionMatrix(pProj); } device->BeginScene(); // Call DrawSky after scene has begun and modelview / projection matrices // properly set for the camera position. This will draw the sky if you pass true. atm->DrawSky(true); // Now, do all your own drawing... SetSceneLighting(); SetSceneFog(); DrawGroundPlane(); // Call DrawObjects to draw all the clouds from back to front. atm->DrawObjects(); device->EndScene(); device->Present(0, 0, 0, 0); // Trigger another redraw. InvalidateRect(hWnd, NULL, FALSE); lastTime = currTime; } }
In order to light the objects in your scene consistently with the appearance of the sky, SilverLining allows you to query for its modeled directional and ambient light information. It's easy to use this information to light your scene. Here's an example of setting up lighting under OpenGL using SilverLining as guidance:
void SetSceneLighting() { float x, y, z, r, g, b, ra, ga, ba; atm->GetSunOrMoonPosition(&x, &y, &z); atm->GetSunOrMoonColor(&r, &g, &b); atm->GetAmbientColor(&ra, &ga, &ba); GLfloat light_ambient[] = {ra, ga, ba, 1.0}; GLfloat light_diffuse[] = {r, g, b, 1.0}; GLfloat light_specular[] = {0.0, 0.0, 0.0, 1.0}; GLfloat light_position[] = {x, y, z, 0}; glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glEnable(GL_LIGHT0); GLfloat mat_amb_diff[] = {1.0, 1.0, 1.0, 1.0}; GLfloat no_mat[] = {0, 0, 0, 0}; glMaterialfv(GL_FRONT, GL_AMBIENT, mat_amb_diff); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_amb_diff); glMaterialfv(GL_FRONT, GL_SPECULAR, no_mat); glMaterialfv(GL_FRONT, GL_EMISSION, no_mat); }
And the same function for setting the scene's lighting, under DirectX9:
static void SetSceneLighting() { D3DXCOLOR light_ambient, light_diffuse, light_specular; D3DXVECTOR3 light_position; atm->GetSunOrMoonPosition(&light_position.x, &light_position.y, &light_position.z); atm->GetSunOrMoonColor(&light_diffuse.r, &light_diffuse.g, &light_diffuse.b); atm->GetAmbientColor(&light_ambient.r, &light_ambient.g, &light_ambient.b); light_diffuse.a = light_ambient.a = 1.0; D3DLIGHT9 light; ::ZeroMemory(&light, sizeof(light)); light.Type = D3DLIGHT_DIRECTIONAL; light.Ambient = light_ambient; light.Diffuse = light_diffuse; light.Specular = D3DXCOLOR(1, 1, 1, 1); light.Direction = D3DXVECTOR3(-light_position.x, -light_position.y, -light_position.z); if (device) { device->SetLight(0, &light); device->LightEnable(0, true); device->SetRenderState(D3DRS_NORMALIZENORMALS, true); device->SetRenderState(D3DRS_SPECULARENABLE, false); } }
DirectX 10 and 11 would be similar, but you'd pass the lighting values and position into your shaders instead of to the device.
Finally, SilverLining will also give you guidance on how to configure fog for your scene. If you're inside a stratus cloud deck or inside precipitation, SilverLining will request that you set the fog in your scene appropriately to simulate being inside a thick cloud or inside rain or snow. If Atmosphere::GetFogEnabled() returns true, then SilverLining is asking that you query Atmosphere::GetFogSettings() to obtain information about the fog volume you're currently inside.
Even if you're not inside or under a cloud, SilverLining can help you set your fog to blend your distant terrain into the sky. SilverLining constantly computes the average color of the sky at the horizon within the current field of view, and makes this accessible via Atmosphere::GetHorizonColor(). Setting the fog color to this, and setting the density consistently with the visibility you passed earlier to Atmosphere::SetVisibility(), will yield realistic results for scenes with light haze.
It is possible to simulate a thick layer of colored haze hugging the ground. This can be useful if Atmosphere::GetHorizonColor() does not produce desirable results for your application. Effectively, this allows you to blend the skybox to a specified color as it approaches the horizon. If you fog your terrain with this same color, you can obscure the horizon line quite nicely for applications that do not render terrain all the way out to the horizon.
The haze layer is set via Atmosphere::SetHaze(). You may simulate any depth of haze, any color, and any density. Set the depth to 0 to disable haze. It's important to realize that no lighting is performed on the haze; if you want the sky to blend toward a darker color at night, you must pre-multiply the haze color by the light in the scene.
AtmosphericConditions::SetVisibility() may be used to apply atmospheric perspective effects to the clouds in the scene - it causes the clouds to blend into the sky with distance. It does not fog the sky itself - for thicker fog, use Atmosphere::SetHaze() for volumetric-style fog, or AtmosphericConditions::SetFog() for exponential fog. Think of SetVisibility() of just simulating particulate matter in the atmosphere that affects atmospheric perspective; it only makes sense with relatively high visibilities. If you're really simulating being in fog, use SetFog() instead.
Here's an example of setting fog under OpenGL, using the GetHorizonColor() method instead of a haze layer of a specified color. Note that this code will not result in the sky being fogged; it only provides guidance to your application for fogging the objects in your scene toward a color that blends with the horizon. For thicker fog that does obscure the sky, you'll want to use Atmosphere::SetHaze(), or AtmosphericConditions::SetFog() with a backbuffer cleared to your fog color, in addition to this code.
void SetSceneFog() { glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_EXP); float hazeDensity = 1.0 / kVisibility; // Decrease fog density with altitude, to avoid fog effects through the vacuum of space. static const double H = 8435.0; // Pressure scale height of Earth's atmosphere double isothermalEffect = exp(-(atm->GetConditions()->GetLocation().GetAltitude() / H)); if (isothermalEffect <= 0) isothermalEffect = 1E-9; if (isothermalEffect > 1.0) isothermalEffect = 1.0; hazeDensity *= isothermalEffect; bool silverLiningHandledTheFog = false; if (atm->GetFogEnabled()) { float density, r, g, b; // Note, the fog color returned is already lit atm->GetFogSettings(&density, &r, &g, &b); if (density > hazeDensity) { glFogf(GL_FOG_DENSITY, density); GLfloat fogColor[4] = {r, g, b, 1.0}; glFogfv(GL_FOG_COLOR, fogColor); silverLiningHandledTheFog = true; } } if (!silverLiningHandledTheFog) { GLfloat fogColor[4]; atm->GetHorizonColor(yaw, 0, &fogColor[0], &fogColor[1], &fogColor[2]); glFogfv(GL_FOG_COLOR, fogColor); glFogf(GL_FOG_DENSITY, hazeDensity); } }
And here's the equivalent DirectX9 code for setting fog:
static void SetSceneFog() { DWORD fogColor; float density, r, g, b; // If you're inside a cloud, SilverLining will request that you set the fog accordingly. if (atm->GetFogEnabled()) { atm->GetFogSettings(&density, &r, &g, &b); // This fog color is pre-lit } else // Otherwise, setting the fog to the average color of the sky at the horizon works well. { atm->GetHorizonColor(yaw, &r, &g, &b); density = 1.0f / kVisibility; // Decrease fog density with altitude, to avoid fog effects through the vacuum of space. static const double H = 8435.0; // Pressure scale height of Earth's atmosphere double isothermalEffect = exp(-(atm->GetConditions()->GetLocation().GetAltitude() / H)); if (isothermalEffect <= 0) isothermalEffect = 1E-9; if (isothermalEffect > 1.0) isothermalEffect = 1.0; density *= isothermalEffect; } BYTE cr, cg, cb, ca; cr = (BYTE)(r * 255.0); cg = (BYTE)(g * 255.0); cb = (BYTE)(b * 255.0); ca = 255; fogColor = D3DCOLOR_RGBA(cr, cg, cb, ca); // Enable fog blending. device->SetRenderState(D3DRS_FOGENABLE, TRUE); // Set the fog color. device->SetRenderState(D3DRS_FOGCOLOR, fogColor); // Set fog parameters. device->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_EXP); device->SetRenderState(D3DRS_FOGDENSITY, *(DWORD *)(&density)); }
Again, DirectX10 and 11 is similar, but you would need to pass in the fog parameters to your shaders instead of to the device.
That's all there is to it! Using the techniques above, you'll be able to integrate SilverLining's sky and cloud rendering into your 3D application, and light and fog the objects in your scene consistently with the sky and clouds rendered by SilverLining.
If your application specifies its own fog - for example, you're simulating being inside a fog bank - you can tell SilverLining to fog its clouds to a specified color and density. See the AtmosphericConditions::SetFog() method for more information. When you're in thick fog, you'll usually want to just clear your back buffer to the fog color and not call SilverLining at all. The sky won't be visible in such conditions anyhow. Remember to call ClearFog() when you leave the simulated fog bank, as calling SetFog() will override all other atmospheric perspective effects in SilverLining.
Our instructions above describe calling Atmosphere::DrawSky() as the first thing in your frame in order to keep things simple and as compatible as possible with other engines. As you're getting your SilverLining integration up and running, it makes sense to start with this approach. However, in some situations, drawing the sky box last instead of first will actually lead to performance benefits.
The issue is that if you draw your sky box first, a lot of the box ends up getting overdrawn by the objects in your scene, which is a waste of fill rate. By drawing the sky box last, with depth reads enabled, only parts of the scene with a still-cleared depth buffer will be filled with the sky.
To do this, you must clear the depth buffer yourself at the beginning of each frame (but don't clear the color buffer.) Draw the non-translucent objects in your scene with depth writes on. Then, call Atmosphere::DrawSky() with the clearDepth parameter set to false - this will tell SilverLining that you don't want DrawSky() to clear the depth buffer before drawing the sky box. Finally, call Atmosphere::DrawObjects() to draw the clouds and precipitation, and the translucent objects in your scene.
Some engines don't use the depth buffer at all, and use depth textures instead - this technique will not work in such situations. However, for engines that do rely on the depth buffer, this technique can lead to slight gains in performance if your application is fill-rate bound.
All of the platform-specific code for OpenGL or DirectX in SilverLining is isolated into renderer code that may be loaded dynamically at runtime. This means the core of SilverLining is platform-agnostic; you may substitute our renderer implementations for your own, and tie SilverLining directly into your own rendering engine. Licensed users receive the full source of SilverLining, which essentially means you can use SilverLining on any system with a C++ compiler with STL, such as gaming consoles.
In this mode, you can essentially think of SilverLining as a numerical engine that calls back into your own renderer to do the actual drawing, texture loading, setup of vertex buffers, etc.
Bear in mind that if your rendering engine is built on top of OpenGL or DirectX, it's easier to use SilverLining's native support of OpenGL or DirectX. Even though it's going "behind the back" of your engine to render directly at a lower level, you don't need to worry about SilverLining interfering with your engine. We're careful to restore any state that was set prior to calling SilverLining API's, and since we do our drawing at the very beginning and very end of the scene surrounding your own frame update code, our drawing stays out of the way of the rest of your scene. Our sample integration code for OpenSceneGraph, Gamebryo Lightspeed, Ogre3D, SceniX, and Carmenta all work in this manner without any problems.
However, if you're using SilverLining on a system that's not built on standard OpenGL or DirectX (such as Playstation, Wii, or XBox,) you'll need to implement hooks into your system's renderer. We provide sample code to get you started.
If you're tying into your own renderer, use the "norenderer" version of the SilverLining libraries (for example, lib/vc9/win32/SilverLining-MT-norenderer.lib/) These libraries do not attempt to dynamically load a renderer at runtime, and do not include any rendering code of their own. You then need to implement all of the functions defined in the header file "SilverLiningDLLCommon.h." SilverLining is able to operate with some of these functions stubbed out; the comments indicate when this is the case. Essentially, you need to provide hooks for loading and activating shaders, vertex buffers, index buffers, and textures, as well as code to draw triangle strips and lists of points using these resources. When initializing your Atmosphere object, pass in CUSTOM_RENDERER as the renderer type.
The SampleCode/CustomerRendererExample folder contains a project that illustrates how to tie SilverLining into your own renderer - in this case, it ties it back into DirectX9 directly. It's similar to our DX9 sample code, but implements its own DirectX9 renderer that you could swap out with any other renderer instead. Licensed users may also examine the source of our native OpenGL, DirectX9, DirectX10, and DirectX11 renderers as further examples. If you're evaluating SilverLining and need one of these other renderers as a starting point for your own integration, drop a note to support@sundog-soft.com and we'll be glad to help.
Don't be intimidated by the amount of code in our sample DirectX9 renderer - a good portion of the functions actually aren't required for SilverLining to run (see the comments in SilverLiningDLLCommon.h for guidance,) and you can likely implement the required functions in far fewer lines of code when you're coding against a rendering engine that abstracts away the complexity of DirectX or OpenGL.
By default, SilverLining will allocate memory using new, delete, malloc, and free. However, you may redirect SilverLining's memory management into your own memory manager if you wish.
The SilverLining::Allocator class is defined in the public header MemAlloc.h. If you extend this class, you may implement your own Allocator::alloc() and Allocator::dealloc() methods to manage memory however you wish. Pass an instantiation of your Allocator-derived class into Allocator::SetAllocator() prior to creating any SilverLining objects, and the SilverLining library and rendering DLL's will use your own allocation scheme.
The Allocator will capture all calls to new, delete, malloc, and free within SilverLining. Every object in SilverLining derives from a SilverLining::MemObject class that overloads the new and delete operators, rather than overloading new and delete globally. Our STL objects also use a custom allocator that routes through SilverLining::Allocator. The macros SL_VECTOR, SL_MAP, SL_LIST, and SL_STRING are used as a convenience for STL objects using our allocator.
By default, SilverLining will load its texture, data, and shader resources directly from disk relative to the path to the resources directory you specify in Atmosphere::Initialize(). However, all disk access in SilverLining is abstracted by a ResourceLoader class, so you have the ability to hook in any resource management scheme you wish.
See the documentation for ResourceLoader for more details. For example, if you wanted to include all of SilverLining's data, textures, and shaders within your own pack files, you could extend the ResourceLoader class and hook it into your own resource manager. Then, pass a pointer to your derived ResourceLoader into Atmosphere::SetResourceLoader() prior to calling Atmosphere::Initialize(), and all disk access will be routed through your own resource management.
It's important to note that the renderer DLL's for SilverLining also live inside our resource folder, and Windows must load these DLL's directly from disk. So, although you can move all of the other resources in SilverLining to a virtual file system, you do need to retain a resources folder containing the renderer DLL's at least. If you want to avoid this, you can rebuild the SilverLining libraries to statically link in the renderer you want, and eliminate the DLL dependencies altogether. Licensed customers will find build targets in the SilverLining project file to statically link the OpenGL or various DirectX renderers directly into the SilverLining library.
Take care when using stratocumulus cloud layers in your scene - they are rendered using GPU ray-casting and depend on a sophisticated fragment program. Unlike other cloud layers that will run on pretty much any hardware, stratocumulus clouds do require a system that support Shader Model 3.0, and a newer GPU that has a fast fragment processor. If you're running a simulation on known, modern graphics hardware, stratocumulus cloud layers will reward you with per-fragment lighting and extremely dense clouds with constant rendering time. However, for consumer applications where the system requirements are less controlled, you may want to stick with cumulus congestus clouds instead.
Silverlining should integrate easily into any rendering engine built on top of OpenGL or DirectX, but we get the most questions about integrating with OpenSceneGraph. We've included some sample code for OpenSceneGraph 2.4 and up with the SDK to get you started; it's a modified osgviewer application that integrates the sky, clouds, and lighting.
A more limited example for OSG 1.0 is also provided.
Studying or extending this sample code is the easiest way to get started, but if you want to start from scratch, here are the things you need to know about OpenSceneGraph integration:
Note that our sample code doesn't handle the case of multiple contexts. If you're running OSG with multiple monitors, you'll probably want to develop in single-monitor mode to get started. If you do have multiple windows in your OSG application, you can instantiate an Atmosphere object for each window and attach them to each window's camera, as illustrated in the sample code. Be sure to read the section on multiple context support below; you'll need to take care that your Atmosphere and CloudLayer objects are initialized at just the right time. In particular, adding new cloud layers after initialization should be done within the SkyDrawable::drawImplementation method AFTER the call to Atmosphere::DrawSky() in a multi-context application.
The sample code directory of the SDK includes source code that illustrates Ogre3D integration. In the style of the Ogre3D demo applications, most of the implementation is in the header file (SilverLiningDemo.h).
Integration with Ogre3D is fairly straightforward. The trick is to implement a RenderQueueListener in order to get the hooks you need for the beginning and end of frame. The sample code also illustrates how to obtain the pointer to the Direct3D device from Ogre3D, which you'll need if you're using Direct3D instead of OpenGL. Another Direct3D-specific gotcha: you need to tell SilverLining to render its lighting pass offscreen, since Ogre3D's backbuffer is not readable. The sample code shows you how to call Atmosphere::SetConfigOption() to achieve this.
An official partner integration of SilverLining with Gamebryo Lightspeed is available separately from http://www.sundog-soft.com or from http://pulse.emergent.net. You'll need to have both the SilverLining SDK and Lightspeed installed before installing the integration package. The integration includes a core runtime library (which may be adapted for use with earlier versions of Gamebryo,) integration with Lightspeed's rapid iteration game framework, and a plug-in for World Builder. Full documentation for Gamebryo integration is included with the integration package.
Sample code for integrating SilverLining into a 3D runtime scene powered by the Carmenta geospatial engine is also provided with the SDK.
The main challenge when integrating with Carmenta is that the modelview and projection matrices are not set when the "beforeUpdate" callback is executed. This means the view matrix needs to be constructed from scratch from the 3D view object's properties, and passed to SilverLining. Refer to the sample Carmenta integration code to see how to construct this matrix correctly.
We also provide a sample integration with NVidia's SceniX scene engine. You'll find a modified "simple viewer" sample application in the SDK, which uses the SilverLiningManager class included in the sample to wrap your scene's root node with SilverLining's rendering methods, and to keep a directional light object in sync with the simulated conditions.
Examine the SimpleViewerView.cpp file for an example of configuring the atmospheric conditions.
SilverLining has successfully been integrated into "whole-Earth" applications, using coordinate systems where the origin is at the center of the Earth, with major axes pointing through the North Pole and through latitude, longitude (0,0). Wrapping your head around how to configure SilverLining's basis vectors and how to position cloud layers in this sort of environment isn't easy, so here are some tips.
What you want to do is set the up and right vectors each frame such that "up" is the normalized vector from the origin (center of the earth) to your location, and "right" points East from your location. The best way to do this is to construct your "up" vector by normalizing the vector from the center of the Earth to your viewpoint. Construct a "north" vector pointing in the direction of your north pole (ie, 0,0,1) and compute the cross product north X up. Normalize the resulting cross product to create your "right" vector pointing east.
If your Earth model is in a system where Y is North instead of Z, you'll need to set the configuration setting geocentric-z-is-up to "no" in the file resources/silverlining.config. Otherwise, the position of the sun, moon, and stars will be incorrect relative to your skies and your Earth.
When positioning a cloud layer, set its base altitude to your desired altitude of the bottom of the cloud layer plus the radius of the Earth at your location.
The way SetLayerPosition() works on a cloud layer is that it sets the position of the layer as (eastCoord, baseAltitude, southCoord), then transforms that position by the basis defined by the up and right vectors you set. So, think of it as defining the position of the cloud layer relative to the North Pole in geocentric coords, and then it gets rotated over the position defined by the up vector, which points from the center of the Earth to where you are.
An even simpler way to approach it is to set the up and right vectors for whatever location you want the clouds over, set the cloud layer's base altitude to the desired altitude + the Earth's radius, and then set the "east" and "south" offsets to zero.
Here is some sample code that might make it easier to understand, which creates a cloud layer over the camera position in geocentric space:
// cameraPosition is a Vector3 in ECEF coordinates, relative to the // center of the Earth. Vector3 up = cameraPosition; up.Normalize(); Vector3 north(0, 1, 0); // We assume Y is up Vector3 east = north.Cross(up); east.Normalize(); atm->SetUpVector(up.x, up.y, up.z); atm->SetRightVector(east.x, east.y, east.z); // Note - be sure to also update AtmosphericConditions::SetLocation // if you position changes // Creates a cumulus congestus cloud layer at 2500 meters. Note // that SetBaseAltitude adds in the distance from the center of the // Earth to the ground, and we call SetLayerPosition with (0, 0) cumulusCongestusLayer = CloudLayerFactory::Create(CUMULUS_CONGESTUS); cumulusCongestusLayer->SetBaseAltitude(2500 + cameraPosition.Length()); cumulusCongestusLayer->SetThickness(100); cumulusCongestusLayer->SetBaseLength(80000); cumulusCongestusLayer->SetBaseWidth(80000); cumulusCongestusLayer->SetDensity(0.4); cumulusCongestusLayer->SetLayerPosition(0, 0); cumulusCongestusLayer->SeedClouds(*atm); atm->GetConditions()->AddCloudLayer(cumulusCongestusLayer);
If you intend to render the Earth from space, be sure to also look at the geocentric parameter of Atmosphere::DrawSky(). At very high altitudes, drawing the sun, moon, and stars using horizon coordinates will break down; this parameter will cause everything to be rendered in geographic coordinates instead. Also check out the enable-atmosphere-from-space setting in resources/SilverLining.config. With this on, SilverLining will render a ring around the Earth representing the atmosphere as you leave it - it assumes that a unit in your coordinate system represents one meter, and you're rendering the Earth itself with a realistic size. Be sure that your near and far clip planes take this ring into account, or you may end up clipping it out - see the projection matrix callback class included in the OpenSceneGraph sample code for an example of how to account for the atmosphere's geometry when setting the near and far clip planes.
SilverLining includes particle systems that will simulate any amount of rain, sleet, or snowfall that's likely to occur in nature. It will also pass back fog settings that will let you accurately simulate the reduction in visiblity due to the precipitation. SilverLining takes the precipitation rate you specify, and calculates the distribution of particle sizes, particle velocities, and visiblity using research from the meteorology community based on real-world observations.
Precipitation may be attached to cloud layers, so as you pass underneath a cloud, the precipitation will automatically start and stop.
Although it's sophisticated, it's easy to use. All you need to do is call CloudLayer::SetPrecipitation() on a stratus or cumulus CloudLayer object. You can do this when you're initializing your CloudLayers, or change it any any time thereafter.
CloudLayer::SetPrecipitation only takes two values - the precipitation type (CloudLayer::RAIN , CloudLayer::SLEET, or CloudLayer::SNOW), and the precipitation rate. The rate is specified in millimeters per hour - in the case of snow, this is the liquid equivalent precipitation rate, and not the accumulation rate. Reasonable values for the rate would range from 1.0 to 30.0.
You may simulate mixed precipitation by calling CloudLayer::SetPrecipitation consecutively with different precipitation types. For example, to simulate a 50/50 blend of sleet and snow at an overall rate of 20 mm/hr, you could call SetPrecipitation(CloudLayer::SLEET, 10.0); SetPrecipitation(CloudLayer::WET_SNOW, 10.0);
If you want to turn precipitation off on a CloudLayer, just call CloudLayer::SetPrecipitation() with a precipitation type of CloudLayer::NONE. Doing this will remove all precipitation effects you set previously on this CloudLayer.
If you want precipitation effects to be applied globally, independently of CloudLayers, you may instead use the AtmosphericConditions::SetPrecipitation() method. This allows you to use SilverLining's precipitation effects without using SilverLining's clouds, if you so desire.
To take advantage of the visibility reduction effects, be sure to implement the code described in "Fog Effects with SilverLining" below.
By default, SilverLining will simulate the time you specify with AtmosphericConditions::SetTime(). Clouds will move with the wind over time, but the sun, moon, stars, and appearance of the sky won't change until you call SetTime() again. This is adequate for most applications, and it ensures good performance.
Some applications need to control the passage of time. You might need to go backwards in time in order to replay a scene. Or you might want to accelerate time for a time-lapse photography sort of effect. Or, you may simply want to filter the time between frames to ensure smooth animation. SilverLining allows you to replace its own internal millisecond timer with your own, to accomplish such effects.
To do so, implement a MillisecondTimer class of your own, and pass it to AtmosphericConditions::SetMillisecondTimer(). Here's an example of a MillisecondTimer that speeds up the passage of time 10X:
class MyMillisecondTimer : public MillisecondTimer { public: virtual unsigned long GetMilliseconds() const { return timeGetTime() * 10; } };
To use this timer, pass it into your AtmosphericConditions class, like this:
MyMillisecondTimer *timer = new MyMillisecondTimer();
atm->GetConditions()->SetMillisecondTimer(timer);
You are responsible for deleting your timer class at shutdown. To restore the default timer, call SetMillisecondTimer(NULL).
Your new timer will influence the rate at which clouds move, but by default, that's all that millisecond timers influence. You may also set the sun, moon, stars, and appearance of the sky to change dynamically over time. To do so, call AtmosphericConditions::EnableTimePassage(). For example:
atm->GetConditions()->EnableTimePassage(true, 20000);
The first parameter enables dynamic passage of the simulated time. The second specifies the minimum time interval, in milliseconds (as defined by your own MillisecondTimer if you're using one) between cloud deck relighting passes. Relighting a cloud deck is a relatively expensive operation, and you may not want to relight during an interactive scene. You'll at least want to do so infrequently, as the above example does. If you want the sun, moon, stars, and sky to change smoothly over time but don't want to incur the costs of cloud relighting at all, pass -1 for the second parameter.
For truly smooth time lapse effects with continuous relighting of the clouds every frame, a "quick and dirty" lighting mode is available. It is a simpler lighting model than the default one; it simply makes cloud puffs darker the deeper they are inside the cloud's bounding ellipsoid from the light source. It won't generate a shadow map for the cloud layer, and it won't result in clouds shadowing each other. But if you don't need shadows from the clouds, it looks almost as good and it's fast enough for relighting every frame. To enable this mode, edit the SilverLining.config file under the resources folder with any text editor. Search for the setting "cumulus-lighting-quick-and-dirty" and set it to "yes". You may also want to experiment with the cumulus-lighting-quick-and-dirty-attenuation setting, which will affect the brightness and contrast of the clouds in your scene. With "quick and dirty" lighting enabled, you can set the second parameter to EnableTimePassage to zero and still maintain real-time performance. Resulting scenes of the sun setting behind a deck of cumulus congestus clouds can be stunning.
One thing to watch out for when accelerating time - if you have cumulonimbus clouds in your scene, this might result in lightning being rendered much more frequently, which can both look strange and affect performance. To counteract this, increase the config setting lightning-max-discharge-period accordingly. Alternately, lightning may be disabled altogether by setting cumulonimbus-lightning-density to 0.
Many training simulator applications require a consistent scene drawn across several systems, or "channels", together. SilverLining provides two approaches to ensuring the clouds are consistent across multiple computers.
The simplest solution is to seed the standard library's random number generator consistently on each channel, using srand(). Cloud layers generated in the same order with the same parameters will then be consistent.
Alternately, you may use the CloudLayer::Save() method to pre-generate cloud layers for your scene, and save them to disk. Then, you can distribute these saved cloud layers to each channel, and load them at runtime using CloudLayer::Restore(). This approach also has the benefit of speeding up your application's initialization a bit.
Some applications also require drawing multiple scenes on a single channel, across multiple windows or graphics contexts. Other applications render multiple viewports within a single context. Using SilverLining across multiple contexts or viewports in the same application is supported, but requires some care.
Each viewport must have its own SilverLining::Atmosphere object associated with it. This means that for each viewport, you must instantiate and initialize a separate Atmosphere object, and use the correct Atmosphere for the context you are currently rendering to. The Atmosphere must be initialized while its associated graphics context is currently active, and any cloud layers added to the scene must also be added to each Atmosphere while the associated context is active.
Internally, SilverLining maintains a concept of a "current atmosphere" that is updated whenever Atmosphere::Initialize() or Atmosphere::DrawSky() is called. Any operations to the Atmosphere, such as adding or removing cloud layers, must happen following one of these calls.
So, to recap: if you're rendering across multiple viewports, make sure you associate an Atmosphere object for each viewport. You should only add or remove cloud layers following a call to Atmosphere::Initialize() or Atmosphere::DrawObjects() in this situation, and the Atmosphere and cloud layers must be initialized and drawn while the correct window or graphics context is currently active. Generally, the easiest way to accomplish this is by initializing and modifying the Atmosphere object within your application's drawing method.
For many applications, allowing SilverLining to draw its own clouds is a simple approach that will get you up and running quickly with good results. Other applications may prefer to manage their own cloud drawing, in order to ensure that clouds are sorted properly with respect to other translucent objects in the scene. SilverLining provides you with access to the underlying translucent objects it normally renders in Atmosphere::DrawObjects() for this purpose.
If you call Atmosphere::DrawObjects(false), this tells SilverLining that it should build up a list of translucent objects to draw within the DrawObjects call, but not to actually draw them. After calling DrawObjects(false), you may then obtain a list of translucent objects with the Atmosphere::GetObjects() call.
Translucent objects are generally rendered last in a scene, in back-to-front order with respect to the current viewpoint. Use the Atmosphere::GetObjectDistance() method to obtain the distance from a given viewpoint to each object for sorting purposes. Once your translucent objects are sorted and you're ready to draw them, you may draw a SilverLining ObjectHandle using the Atmosphere::DrawObject() method. Note, you must have blending enabled prior to calling DrawObject().
Here's an example of manually drawing clouds following a call to Atmosphere::DrawObjects(false):
static bool comp(ObjectHandle c1, ObjectHandle c2) { double d1 = atm->GetObjectDistance(c1, c2, camX, camY, camZ); double d2 = atm->GetObjectDistance(c2, c1, camX, camY, camZ); return (d1 > d2); } void DrawClouds() { SL_VECTOR(ObjectHandle>& objs = atm-)GetObjects(); sort(objs.begin(), objs.end(), comp); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthMask(0); glEnable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); glDisable(GL_FOG); SL_VECTOR(ObjectHandle)::iterator it; for (it = objs.begin(); it != objs.end(); it++) { atm->DrawObject(*it); } glDepthMask(1); }
If you instead call DrawObjects(true), this code isn't necessary, and DrawObjects() will draw the cloud objects on its own.
A by-product of how SilverLining lights its clouds is an image that may be used as a shadow map for your scene. Using this, clouds from SilverLining may cast shadows on the objects in your world.
You must set "cumulus-lighting-quick-and-dirty" to "no" in the configuration file resources/SilverLining.config for shadow maps to be rendered. Shadow maps are not supported under DirectX 10 or 11. Under DirectX 9, they are only supported if the "render-offscreen" setting is also "yes".
You may instruct a CloudLayer object to preserve its shadow map for your use by calling CloudLayer::GenerateShadowMaps(). This method will only work if the cloud layer supports shadow maps (cirrus and stratus decks do not.) You may find out if a cloud layer can generate shadow maps by calling CloudLayer::SupportsShadowMaps(). For example:
CloudLayer *shadowMapLayer;
...
if (shadowMapLayer->SupportsShadowMaps())
{
shadowMapLayer->GenerateShadowMaps(true);
}
Infinite cloud layers (set via CloudLayer::SetIsInfinite()) should not be used with shadow maps unless you have a fixed camera position. Since there is no set point to render the shadow map from for an infinite cloud layer, the shadow map ends up getting generated every frame on infinite cloud layers, which results in poor performance. Restrict your shadow maps to localized cloud layers.
Once you have a CloudLayer configured to generate shadow maps, you may use the CloudLayer::BindShadowMap method to bind the shadow map to a given texture stage, and to retrieve the projection * view matrix of the light source that generated the shadow map. Using this information, you can enable texture coordinate generation or use your own shader to apply the shadow map to the objects in your scene. Here's an example in OpenGL on how to configure the pipeline to do shadow mapping using SilverLining's shadow maps:
double m[16]; if (shadowMapLayer->BindShadowMap(1, m)) { Matrix4 bias (0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f); Matrix4 lightProjView(m); Matrix4 textureMatrix = bias * lightProjView; //Set up texture coordinate generation. glActiveTexture(GL_TEXTURE1); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGendv(GL_S, GL_EYE_PLANE, textureMatrix.GetRow(0)); glEnable(GL_TEXTURE_GEN_S); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGendv(GL_T, GL_EYE_PLANE, textureMatrix.GetRow(1)); glEnable(GL_TEXTURE_GEN_T); glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGendv(GL_R, GL_EYE_PLANE, textureMatrix.GetRow(2)); glEnable(GL_TEXTURE_GEN_R); glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGendv(GL_Q, GL_EYE_PLANE, textureMatrix.GetRow(3)); glEnable(GL_TEXTURE_GEN_Q); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glEnable(GL_TEXTURE_2D); }
Once the vertex pipeline has been configured to generate the correct texture coordinates for the shadow map's texture stage as above, you also need to shade your fragments correctly to blend the shadow map with your primary texture. Here's an example on a way to configure this in OpenGL:
// Stage 1 = prev * shadow glActiveTexture(GL_TEXTURE1); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glEnable(GL_TEXTURE_2D); // Stage 0 = tex * primary color glActiveTexture(GL_TEXTURE0); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); glEnable(GL_TEXTURE_2D);
This technique is used to create the cloud shadow effects in the SilverLining Demo application, available at our website at http://www.sundog-soft.com/.
You may also render your own shadow maps by setting your view to a location in the sun's direction, setting your projection matrix to cover the cloud layer, and calling Atmosphere::DrawSky() / Atmosphere::DrawObjects() while your shadow map texture is the current render target.
Under the hood, all of SilverLining's lighting is physically simulated and done in units of kilo-candelas per square meter - we then tone-map these raw physical values down to a displayable range.
This tone-mapping may be disabled by calling Atmosphere::EnableHDR(). This may be used in conjunction with a floating-point render target to receive the sky and clouds rendered in units of kilo-candelas per square meter, instead of [0,1.0]. The user is responsible for performing their own tone mapping on the entire scene in this case, as the floating point render target is transferred to a displayable surface.
When HDR is enabled, lighting values returned such as Atmosphere::GetSunOrMoonColor() will also no longer be tone mapped or clamped.
HDR support requires shader model 3.0 support (since pixel shader inputs will be clamped otherwise.)
If the rest of the scene is not rendered in units of kCD/m^2, you can scale the HDR output to better match the rest of your scene. In the resources/silverlining.config file, look for settings such as sun-transmission-scale and sun-scattered-scale which will multiply our internal lighting values by the given scale factor, thereby brightening or darkening the scene. You may also influence the brightness of the clouds (as opposed to the sky) independently with the setting sun-luminance-scale, which will further modulate the lighting values applied to the clouds.
As part of disabling tone-mapping, gamma correction will also be disabled in HDR mode. Be sure to account for gamma correction in your own tone-mapping operator to avoid skies that look too dark.
SilverLining is highly configurable. Its default settings attempt to accurately simulate the sky for the conditions prescribed, together with tone-mapping to account for human perception and how your eyes adjust to low light levels. But, if you don't like the appearance of the sky, clouds, or precipitation under a given set of circumstances, odds are you can adjust it to your liking. Most of the "knobs and dials" for SilverLining may be found in the resources/SilverLining.config file. Open it up in your development environment and examine the available settings; most are well-documented within the file.
To change a setting, save your changes into the SilverLining.config file, and restart your application. Some changes may be configurable at runtime by using SilverLining::Atmosphere::SetConfigOption().
The config file consists of the following main sections:
For example, if you want to increase the brightness of the clouds overall, adjust the setting ambient-scattering up. For deeper shadows within the clouds, adjust cumulus-lighting-quick-and-dirty-attenuation down.
The overall brightness of the sky and light may be influenced with the "brightness" setting.
For richer colors in the atmosphere, experiment with the other air-mass-model settings (ICQ or NREL.) You can also adjust the air mass directly using the air-mass-multiplier setting; higher air masses result in more atmospheric scattering.
The color of sunsets and sunrises may also be directly adjusted, using the settings sunset-X-boost, sunset-Y-boost, sunset-Z-boost, and sunset-boost-exponent. To make the sunsets redder, increase the sunset-X-boost a bit. These settings represent a boost applied to the scattered sunlight color in XYZ color space. The exponent controls how these boosts are applied as the sun's angle from the horizon changes. Higher integers for sunset-boost-exponent will limit the effect of the boost to be closer to the horizon; lower values will make the boosts more pervasive.
For more visible rain particles, increase rain-streak-brightness and rain-streak-width-multiplier.
The color and density of the fog inside stratus clouds may be adjusted using the stratus-fog-* settings.
Want a bigger sun or moon? Increase sun-width-degrees or moon-width-degrees.
Many other tweaks are possible; examine the config file for more options, and feel free to contact us at support@sundog-soft.com if you have any questions about them.
1.6.1