Light Modelling
Learn about the different aspects of light modeling in GroIMP
Spectral Light Modelling
These three core aspects of light simulation—global and local illumination models, and light sources—are the base for any light simulation. When it comes to spectral light simulations, specialized implementations of the aforementioned aspects are required, capable of simulating not only one or three light channels, as is typical for common light models, but also the entire light spectrum for different wavelengths.
Note: The hardware requirement for performing GPU-based ray tracing is a programmable graphics card with OpenCL support. For example, any Nvidia card will do well, whereas older versions of integrated Intel cards—as they are often used in laptops—are not suitable for this. GPUFlux supports multiple GPU units and CPUs working in parallel at the same time. The use of multiple devices as well as the use of the CPU needs to be activated within the Preferences of the Flux renderer; see image below.
Spectral light simulations now deal not only with the pure calculation of light distributions but also include aspects of the principal characteristics of light, i.e., light quality, quantity, and duration.
The main factor influencing the light quality is the light's spectral composition, commonly called colour. Thus, the compositions of different intensities of different wavelengths form the final light spectrum or colour. Below are the light spectra of typical sunlight, of common HPS lamps (high-pressure sodium lamps), as used for instance as additional light sources within greenhouses, and a red LED lamp.
Differences between the CPU and GPU light model
First steps on light modelling
This tutorial we show you the basics on how to do light modelling in GroIMP. For some more theoretical background pleas refer to the Introduction - A little bit of Theory page. For an advanced tutorial on spectral light modelling check out the Spectral light modelling tutorial.
GroIMP integrates two two main light model implementations, namely:
- Twilight, a CPU-based implementation
- GPUFlux, a GPU-based implementation
While they are both integrating various renderer and light model implementations, they differ in a few details we are not gonna be discuss here. Please refer to the Differences between the CPU and GPU light model page for details. The two main difference however are first that the GPU implementation is several times faster than the CPU implementation and second, that the GPU version is, beside the three channel RGB implementation, able to simulate the full visible light spectrum (spectral light modelling).
In the following, we focus on the three channel RGB versions of both the CPU and GPU implementations.
To set up a light model basically three steps are needed.
- Definition/Initialization of the light model
- Running the light model
- Checking the scene objects for their light absorption
In GroIMP/XL, this can be done as following:
For the twilight (CPU-based) implementation:
import de.grogra.ray.physics.Spectrum; //constants for the light model: number of rays and maximal recursion depth const int RAYS = 1000000; const int DEPTH = 10; //initialize the scene protected void init() { //create the actual 3D scene [ Axiom ==> Box(0.1,1,1).(setShader(BLACK)) M(2) RL(180) LightNode.(setLight(new SpotLight().(setPower(100),setInnerAngle(0.02),setOuterAngle(0.055)))); ] //make sure the changes on the graph are applied... {derive();} //so that we directly can continue and work on the graph // initialize the light model LightModel CPU_LM = new LightModel(RAYS, DEPTH); CPU_LM.compute(); // run the light model //check the scene objects for their light absorption Spectrum ms; [ x:Box::> { ms = GPU_LM.getAbsorbedPower(x); } ] print("absorbed = "+ms);println(""+ms.integrate()+" = "+ms, 0xff0000); }
For the GPUFlux (GPU-based) implementation:
import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; import de.grogra.gpuflux.scene.experiment.Measurement; //constants for the light model: number of rays and maximal recursion depth const int RAYS = 1000000; const int DEPTH = 10; //initialize the scene protected void init() { //create the actual 3D scene [ Axiom ==> Box(0.1,1,1).(setShader(BLACK)) M(2) RL(180) LightNode.(setLight(new SpotLight().(setPower(100),setInnerAngle(0.02),setOuterAngle(0.055)))); ] //make sure the changes on the graph are applied... {derive();} //so that we directly can continue and work on the graph // initialize the light model FluxLightModel GPU_LM = new FluxLightModel(RAYS, DEPTH); GPU_LM.setMeasureMode(MeasureMode.RGB); // user the Flux model in three channel 'RGB mode' GPU_LM.compute(); // run the light model //check the scene objects for their light absorption Measurement ms; [ x:Box::> { ms = GPU_LM.getAbsorbedPowerMeasurement(x); } ] print("absorbed = "+ms);println(""+ms.integrate()+" = "+ms, 0xff0000); }
To be continued…
General Introduction
Light modelling generally involves three aspects:
- Global illumination model
- Light sources
- Local illumination model
Whereas the Global illumination model handles the actual light computation, the Light sources are the light-emitting elements, and the Local illumination model defines the optical properties of the scene objects.
In each aspect, computer graphics offers plenty of alternatives.
Several of them are implemented in GroIMP as ready-to-use tools.
GroIMP integrates two main light model implementations, namely:
- Twilight, a CPU-based implementation
- GPUFlux, a GPU-based implementation
Both implementing different global illumination model for rendering and for light computation.
More on the different ways to implement a global illumination model can be found here: Ray tracer algorithm
The general settings of the can be changed within the GroIPM properties as described in the Ray tracer options section.
In the following, only light computation or light modelling will be discussed.
Regarding light sources, GroIMP provides a complete set of possible implementations. They all implement the Light and LightBase interfaces, which makes them easy to handle and exchange.
For the Local illumination model, which defines the optical properties of the scene objects such as values for absorption, transmission, and reflection, so-called shaders are used.
GroIMP provides a set of standard shader implementations, e.g., for Lambert and Phong shading. Whereas the Lambertian model supports only diffuse reflection, the Phong reflection model (Phong, 1973) combines ambient, diffuse, and specular light reflections.
Local illumination - Shader
To set the optical properties of an object, in computer graphics the so-called local illumination model is used. It defines so-called shaders, that are define the amount of absorption, reflection and transmission and how the light rays are scattered. The values for absorption are obtained as the 'remaining radiation', i.e., the difference between reflectance and transmission, when we subtract the reflectance and transmission from the total of incoming radiation: Absorption = Total - Reflectance - Transmission.
Note: there is no check of plausibility implemented within the Phong shader. The user needs to make sure that the sum of reflectance and transmission is not higher than the actual incoming radiation. You cannot reflect or transmit more than what was incoming; otherwise, the object would be a light source emitting light.
Computer graphics knows several implementations of local illumination models. The most common are:
- Labertian reflection, and
- Phong shader
Whereas the Lambertian reflection model supports only diffuse reflection, the Phong reflection model (B.T. Phong, 1973) combines ambient, diffuse, and specular light reflections.
Light Sources
Regarding light sources, GroIMP provides a complete set of possible implementations. They all implement the Light and LightBase interfaces, which makes them easy to handle and exchange. The standard light sources are: PointLight, SpotLight, and DirectionalLight.
The following code places the three light sources next to each other starting with a PointLight on the left, a SpotLight in the middle, and a DirectionalLight on the right.
protected void init () [ Axiom ==> [ Translate(0.0,0,0) LightNode.(setLight(new PointLight())) M(-0.6) TextLabel("PointLight")] [ Translate(0.75,0,0) LightNode.(setLight(new SpotLight())) M(-0.1) TextLabel("SpotLight")] [ Translate(1.5,0,0) LightNode.(setLight(new DirectionalLight())) M(-0.1) TextLabel("DirectionalLight")] ; ]
In the 3D View, they are visualized as a cone for a SpotLight, and as a single line for a DirectionalLight. The PointLight on the left has no visualization.
All light sources provide the functionality of visualizing the light rays emitted by them. To do so, the visualization just need to be activated. Additionally, the number of visualized light rays and their length can be set.
protected void init () [ Axiom ==> LightNode.(setLight(new PointLight().( setVisualize(true), setNumberofrays(400), setRaylength(0.5) ))); ]
The output of the light ray visualization of the three light sources is given below.
Visualizing light rays
Sometimes it is interesting to see the traces of light rays emitted by a light source when they travel through a scene. In GroIMP, this can be done using the integrated LightModelVisualizer.
To use it, all one needs to do is to add a LightModelVisualizer object into the scene. The two input parameter of the LightModelVisualizer class are defining the number of visualized light rays and the recursion depth the light rays are followed.
To calculate the light rays visualization, the LightModelVisualizer object needs to be selected, ether in the 3D View or 2D Graph Explorer.
protected void init () [ Axiom ==> LightModelVisualizer(175, 10); ]
When selected, go to the Attribute Editor and press 'compute'.
The following scene (given in OpenGL view on the left and Wireframe on the right) contains one spot light with a very slight opening angle, producing a thin beam of light rays that is reflected by several mirrors before finally hitting the end screen or the red sphere in the front.
Please pay attention to the changing colour of the initial white rays when they are reflected or transmitted. Also interesting to notice are the inner reflections within the green box and the red sphere. And keep in mind that only rays that hit an object are visualized.
Given below, is the code to generate the above scene.
/** * To visualize the light rays, select the yellow point at the origin, * go to the 'Attribute Editor' and press 'compute'. */ const Phong MIRROR = new Phong(); const Phong S1 = new Phong(); const Phong S2 = new Phong(); const Phong S3 = new Phong(); static { MIRROR.setSpecular(new Graytone(1));// 1 MIRROR.setDiffuse(new Graytone(0));// 0 MIRROR.setShininess(new Graytone(1));// 1 S1.setDiffuse(new RGBColor(1,0,1)); S1.setDiffuseTransparency(new RGBColor(0,1,0)); S2.setDiffuse(new Graytone(0.4)); S2.setDiffuseTransparency(new Graytone(0.6)); S3.setDiffuse(new RGBColor(1,0,0)); S3.setDiffuseTransparency(new Graytone(0.7)); } module Wall extends Parallelogram(0.6, 0.6); protected void init () [ Axiom ==> LightModelVisualizer(175, 10), ^ RU(90) RL(60) M(0.25) LightNode.(setLight(new SpotLight().(setPower(1000),setInnerAngle(0.0199),setOuterAngle(0.02)))) , ^ Translate(0.7,-1,-0.25) Wall.(setShader(MIRROR)) // mirror 1 , ^ Translate(1.75,1,-0.25) Wall.(setShader(MIRROR)) // mirror 2 , ^ Translate(2.3,0,-0.25) Wall.(setShader(S1)) // semi-transparent , ^ Translate(3.25,1.5,0) Sphere(0.5).(setShader(S3)) , ^ Translate(3.25,-1.25,-0.5) RH(-45) Box(1.5,0.25,2).(setShader(S2)) , ^ Translate(4.5,-2.5,-1.5) RH(45) Parallelogram(3,5).(setShader(BLACK)) ; ]
Sensor Grids and Areal Sensors
Light distributions, micro-light climate, light above canopy, light extinction coefficients, etc. are key parameters for any canopy simulations. To obtain these, areal or gird like sensor arrangements are required. Both can be generated within GroIMP by only a few lines of code.
In this tutorial we will see how to get started with a FluxModel in GroIMP.
Go here to see more about how the FluxModel is defined in GroIMP.
Light Model
First, we setup of the Flux Light Model:
import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; import de.grogra.gpuflux.scene.experiment.Measurement; const int RAYS = 10000000; const int DEPTH = 10; const FluxLightModel LM = new FluxLightModel(RAYS, DEPTH); protected void init () { LM.setMeasureMode(MeasureMode.FULL_SPECTRUM); LM.setSpectralBuckets(81); LM.setSpectralDomain(380, 780); }
This is:
- importing of the needed classes
- initializing the model with 10 million rays and a recursion depth of 10
- setting the parameters with 400nm divided into 80 buckets of 5nm.
Then, run the light model and determine the amount of sensed radiation or of absorbed power for an object-type.
public void run () [ { LM.compute(); } x:SensorNode ::> { Measurement spectrum = LM.getSensedIrradianceMeasurement(x); float absorbedPower = spectrum.integrate(); //... } x:Box ::> { Measurement spectrum = LM.getAbsorbedPowerMeasurement(x); float absorbedPower = spectrum.integrate(); //... } ]
This is:
- computing the model
- computing the integration of the whole absorbed spectrum for both
- sensor nodes
- box objects
Demonstration of the method for the determination of the amount of absorbed power per bucket or integration over a certain spectral range.
Measurement spectrum = LM.getAbsorbedPowerMeasurement(x); //absorbed power for the first bucket: 380-385 nm float ap380_385 = spectrum.data[0]; //accumulate absorbed power for the first four 50 nm buckets float b0 = 0, b1 = 0, b2 = 0, b3 = 0; for (int i:(0:10)) { b0 += spectrum.data[i]; b1 += spectrum.data[i + 10]; b2 += spectrum.data[i + 20]; b3 += spectrum.data[i + 30]; } //integrate the whole spectrum float ap = spectrum.integrate();
This is:
- Getting the absorbed spectrum
- Storing the first bucket in a variable
- building four integrals, each of 50 nm (10 buckets of 5 nm) and sum up the first 40 buckets.
- calculating the integral over the whole spectrum.