tutorials:basic-spectral-light-modeling
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
tutorials:basic-spectral-light-modeling [2024/12/02 10:47] – MH | tutorials:basic-spectral-light-modeling [2025/06/07 06:21] (current) – MH | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Spectral Light Modelling ====== | ====== Spectral Light Modelling ====== | ||
- | ===== General Introduction ===== | + | 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, |
- | Light modelling generally involves three aspects: | + | Note: The hardware requirement for performing GPU-based |
- | + | ||
- | * Global illumination model | + | |
- | * Light sources | + | |
- | * Local illumination model | + | |
- | + | ||
- | {{ : | + | |
- | + | ||
- | Whereas the Global illumination model handles the actual light computation, | + | |
- | + | ||
- | In each aspect, computer graphics offers plenty of alternatives. | + | |
- | + | ||
- | {{ : | + | |
- | + | ||
- | Several of them are implemented in GroIMP as ready-to-use tools. | + | |
- | + | ||
- | GroIMP integrates two two main **light model implementations**, | + | |
- | + | ||
- | * Twilight, a CPU-based | + | |
- | * GPUFlux, | + | |
- | + | ||
- | Both implementing different global illumination model for rendering and for light computation. | + | |
- | + | ||
- | {{ : | + | |
- | + | ||
- | In the following, only light computation or light modelling | + | |
- | + | ||
- | + | ||
- | 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, | + | |
- | + | ||
- | {{ : | + | |
- | + | ||
- | GroIMP provides a set of standard shader implementations, | + | |
- | + | ||
- | {{ : | + | |
- | + | ||
- | + | ||
- | ===== Spectral light modelling ===== | + | |
- | + | ||
- | + | ||
- | These three core aspects | + | |
+ | {{ : | ||
- | Spectral light simulations now deal not only with the pure calculation of light distributions but also include aspects of the principal characteristics | + | Spectral light simulations now deal not only with the pure calculation of light distributions |
{{ : | {{ : | ||
- | The main factor influencing | + | The main factor influencing light quality is the light' |
{{ : | {{ : | ||
Line 62: | Line 19: | ||
In GroIMP, the GPUFlux model allows us to simulate spectral light between 380 and 720 nm (default values). | In GroIMP, the GPUFlux model allows us to simulate spectral light between 380 and 720 nm (default values). | ||
- | Note: The implementation essentially allows setting individual limits for min and maxLambda, extending the range of the visible light spectrum to include infra-red and ultra-violet. When the wavelength deviates significantly from visible light, ray optics is no longer the most adequate tool for describing the flow of electromagnetic radiation, as other effects become significant. Therefore, from a physical point of view, the results will no longer be meaningful. One reason for this choice—the default range of 380-720 nm—is that the model uses Smith' | + | Note: The implementation essentially allows setting individual limits for min and //maxLambda//, extending the range of the visible light spectrum to include infra-red and ultra-violet. When the wavelength deviates significantly from visible light, ray optics is no longer the most adequate tool for describing the flow of electromagnetic radiation, as other effects become significant. Therefore, from a physical point of view, the results will no longer be meaningful. One reason for this choice—the default range of 380-720 nm—is that the model uses Smith' |
- | The spectral range [minLambda, maxLambda] can be divided into a user-defined number of equally-sized so-called buckets—sub-channels. They can be one, taking the whole range as one channel—which, | + | The spectral range //[minLambda, maxLambda]// can be divided into a user-defined number of equally-sized so-called buckets—sub-channels. They can be one, taking the whole range as one channel—which, |
{{ : | {{ : | ||
Setting up the GPUFlux light model within GroIMP, or more accurately XL, follows the typical Java conventions of importing the required classes, and initializing and parameterizing the light model. | Setting up the GPUFlux light model within GroIMP, or more accurately XL, follows the typical Java conventions of importing the required classes, and initializing and parameterizing the light model. | ||
+ | |||
+ | Note: The examples require GroIMP version >=2.0 to run. With GroIMP version 2.0 some changes on the internal package structure are made. Formally, classes found in de.grogra.imp3d have been moved to de.grogra.gpuflux.imp3d to match the package name (Java 11 forbids package name split). So, if you are using objects, lights or shaders from gpuflux, they should be imported as de.grogra.gpuflux.imp3d.xxx. | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | |||
+ | // Light nodes need to be imported like this | ||
+ | import de.grogra.gpuflux.imp3d.objects.PhysicalLight; | ||
+ | </ | ||
<code java> | <code java> | ||
- | import de.grogra.imp3d.spectral.IrregularSpectralCurve; | ||
- | import de.grogra.ray.physics.Spectrum; | ||
import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; | import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; | ||
- | import de.grogra.gpuflux.scene.experiment.Measurement; | ||
... | ... | ||
const int RAYS = 10000000; //number of simulated light rays | const int RAYS = 10000000; //number of simulated light rays | ||
- | const int DEPTH = 10; //maxiaml | + | const int DEPTH = 10; //maximal |
const FluxLightModel LM = new FluxLightModel(RAYS, | const FluxLightModel LM = new FluxLightModel(RAYS, | ||
protected void calculateLight() { | protected void calculateLight() { | ||
- | + | | |
- | | + | |
LM.setSpectralBuckets(21); | LM.setSpectralBuckets(21); | ||
- | LM.setSpectralDomain(380, | + | LM.setSpectralDomain(380, |
+ | ... | ||
} | } | ||
</ | </ | ||
The GPUFlux light model supports three different modes of measuring spectral power: | The GPUFlux light model supports three different modes of measuring spectral power: | ||
- | * regular RGB, which simulates only three buckets approximating the three color channels | + | * regular RGB, which simulates only three buckets approximating the three colour |
* fully discretized spectral measurements, | * fully discretized spectral measurements, | ||
* weighted integration, | * weighted integration, | ||
Line 102: | Line 66: | ||
LM.setCutoffPower(0.01); | LM.setCutoffPower(0.01); | ||
- | // disables the simulation of sensor | + | // enable or disables the simulation of sensors |
- | LM.setEnableSensors(false); // default: | + | LM.setEnableSensors(true); // default: |
- | // sets the random seed for the random number generator; | + | // sets the random seed for the random number generator; |
LM.setRandomseed(123456); | LM.setRandomseed(123456); | ||
- | // disables | + | // enable |
- | LM.setDispersion(false); // default: | + | LM.setDispersion(true); // default: |
</ | </ | ||
- | After the light model is configured, it can be invoked by calling the compute() function as follows: | + | After the light model has been configurated, it can be invoked by calling the //compute()// function as follows: |
<code java> | <code java> | ||
LM.compute(); | LM.compute(); | ||
</ | </ | ||
- | To obtain the total amount of absorbed radiation of a node x, the getAbsorbedPowerMeasurement function of the light model needs to be called. The returned measurement object contains the results for the specific object x. By calling the integrate() function, the integral, or simply sum, will be calculated. | + | |
+ | To obtain the total amount of absorbed radiation of a node x, the //getAbsorbedPowerMeasurement()// function of the light model needs to be called. | ||
<code java> | <code java> | ||
+ | import de.grogra.gpuflux.scene.experiment.Measurement; | ||
+ | |||
Measurement spectrum = LM.getAbsorbedPowerMeasurement(x); | Measurement spectrum = LM.getAbsorbedPowerMeasurement(x); | ||
float absorbedPower = spectrum.integrate(); | float absorbedPower = spectrum.integrate(); | ||
</ | </ | ||
- | By doing this within a rule, the light absorption can be obtained for all objects of the specified type, such as a Box, as in this example: | + | Have in mind, the unit here is Watt. The output power of the light sources is set in Watt and the values for absorption, reflectance, |
+ | |||
+ | By doing this within a rule, the light absorption can be obtained for all objects of the specified type, such as a //Box//, as in this example: | ||
<code java> | <code java> | ||
Line 136: | Line 105: | ||
</ | </ | ||
- | Accessing the absorption values for each bucket can be done by accessing the data variable of the Measurement class. | + | Accessing the absorption values for each bucket can be done by accessing the data variable of the //Measurement// class. |
<code java> | <code java> | ||
Measurement spectrum = LM.getAbsorbedPowerMeasurement(x); | Measurement spectrum = LM.getAbsorbedPowerMeasurement(x); | ||
- | // absorbed power for the first bucket: 380 -397nm | + | // absorbed power for the first bucket: 380 - 397 nm |
float ap380_397 = spectrum.data[0]; | float ap380_397 = spectrum.data[0]; | ||
Line 155: | Line 124: | ||
==== Light sources ==== | ==== Light sources ==== | ||
- | After the light model is set up, the next step is to define the spectral light sources. The GPUFlux light model works with all basic light nodes, such as PointLight, SpotLight, or DirectionalLights, but to fully realize the potential of spectral light modelling, it is necessary to define the emitted spectrum of the light source. The emitted spectrum can be defined as power intensities per wavelength, specifying the amplitude for specific wavelengths. Using the following spectrum will result in a dark magenta | + | After the light model has been set up, the next step is to define the spectral light sources. The GPUFlux light model works with all basic light nodes, such as //PointLight//, //SpotLight//, or // |
<code java> | <code java> | ||
Line 165: | Line 134: | ||
- | Note: The step size does not have to be equal, and values in between are linearly interpolated. The unit of the amplitudes | + | Note: Step sizes do not have to be equal, and values in between are linearly interpolated. The unit of the amplitude |
- | A spectrum, given by an array of wavelengths and corresponding amplitudes, is called a spectral curve, and in computer graphics, it defines a spectral power distribution. In GroIMP, a spectral curve can be defined using the IrregularSpectralCurve class, which takes the wavelength array and the corresponding amplitudes as input. The IrregularSpectralCurve can be used as input to the ChannelSPD class so that it can later be used as input for the light node. | + | A spectrum, given by an array of wavelengths and corresponding amplitudes, is called a spectral curve, and in computer graphics, it defines a spectral power distribution. In GroIMP, a spectral curve can be defined using the //IrregularSpectralCurve// class, which takes the wavelength array and the corresponding amplitudes as input. The //IrregularSpectralCurve// can be used as input to the //ChannelSPD// class so that it can later be used as input for the light node. |
<code java> | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.spectral.SpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | |||
const float[] WAVELENGTHS = {380, 410, 420, 450, 465, 480, 490, 600, 620, 630, 640, 655, 660, 670, 690, 700, 720}; | const float[] WAVELENGTHS = {380, 410, 420, 450, 465, 480, 490, 600, 620, 630, 640, 655, 660, 670, 690, 700, 720}; | ||
- | const AMPLITUDES = {0.05, 0.1, 0.4, 0.63, 0.25, 0.15, 0.05, 0.01, 0.1, 0.3, 0.4, 0.85, 0.75, 0.95, 0.6, 0.25, 0.1}; | + | const float[] |
- | const ChannelSPD | + | const SpectralCurve |
</ | </ | ||
+ | |||
+ | **SPD** = **S**pectral **P**ower **D**istribution | ||
+ | |||
Besides user-defined spectral curves, GroIMP provides a set of spectral curves: | Besides user-defined spectral curves, GroIMP provides a set of spectral curves: | ||
Line 186: | Line 161: | ||
{{ : | {{ : | ||
- | Since these spectral curve classes all implement the same SpectralCurve interface, they can be used in the same way and therefore | + | Since these spectral curve classes all implement the same //SpectralCurve// interface, they can be used in the same way and are therefore |
<code java> | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.spectral.SpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.shading.ChannelSPD; | ||
+ | |||
+ | //user defined spectral curve, applied to an IrregularSpectralCurve | ||
float[] WAVELENGTHS = {380, 485, 490, 610, 615, 720}; | float[] WAVELENGTHS = {380, 485, 490, 610, 615, 720}; | ||
float[] AMPLITUDES = {0, | float[] AMPLITUDES = {0, | ||
ChannelSPD GREEN_SPD = new ChannelSPD(new IrregularSpectralCurve(WAVELENGTHS, | ChannelSPD GREEN_SPD = new ChannelSPD(new IrregularSpectralCurve(WAVELENGTHS, | ||
- | ChannelSPD GREEN_SPD = new ChannelSPD(new RGBSpectralCurve(1, | + | // |
+ | ChannelSPD GREEN_SPD = new ChannelSPD(new RGBSpectralCurve(0, | ||
+ | |||
+ | //a constant spectral curve of the intensity of 0.25 equally over the whole spectrum | ||
ChannelSPD CONST_SPD = new ChannelSPD(new ConstantSpectralCurve(0.25)); | ChannelSPD CONST_SPD = new ChannelSPD(new ConstantSpectralCurve(0.25)); | ||
+ | |||
+ | //a regular spectral curve will apply the given intensities across the specified range ([400,700]) | ||
ChannelSPD REG_SPD = new ChannelSPD(new | ChannelSPD REG_SPD = new ChannelSPD(new | ||
+ | |||
+ | //a CIE Norm D55 spectral curve - sun light | ||
ChannelSPD REG_SPD = new ChannelSPD(new CIENormSpectralCurve(Attributes.CIE_NORM_D55)); | ChannelSPD REG_SPD = new ChannelSPD(new CIENormSpectralCurve(Attributes.CIE_NORM_D55)); | ||
+ | |||
+ | //a black body spectral curve with a temperature of 5000K | ||
ChannelSPD REG_SPD = new ChannelSPD(new BlackbodySpectralCurve(5000)); | ChannelSPD REG_SPD = new ChannelSPD(new BlackbodySpectralCurve(5000)); | ||
</ | </ | ||
Line 202: | Line 191: | ||
{{ : | {{ : | ||
- | To use the spectral curve as input for a light source, a SpectralLight needs to be defined. | + | To use the spectral curve as input for a light source, a //SpectralLight// needs to be defined. In the example below, a // |
<code java> | <code java> | ||
- | const float[] WAVELENGTHS = {380,385,...}; | + | import de.grogra.gpuflux.imp3d.objects.SpectralLight; |
- | const float[] AMPLITUDES = {0.000967721, 0.000980455, ...}; | + | import de.grogra.gpuflux.imp3d.spectral.SpectralCurve; |
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | |||
+ | //user defined spectral curve, applied to an IrregularSpectralCurve | ||
+ | const float[] WAVELENGTHS = {380, 485, 490, 610, 615, 720}; | ||
+ | const float[] AMPLITUDES = {0,0,1,1,0,0}; | ||
+ | const SpectralCurve TEST_SPD = new IrregularSpectralCurve(WAVELENGTHS, | ||
module MyLamp extends LightNode() { | module MyLamp extends LightNode() { | ||
{ | { | ||
setLight( | setLight( | ||
- | new SpectralLight(new IrregularSpectralCurve(WAVELENGTHS, | + | new SpectralLight(TEST_SPD).( |
setPower(100), | setPower(100), | ||
setLight(new PointLight()) | setLight(new PointLight()) | ||
Line 218: | Line 214: | ||
} | } | ||
} | } | ||
+ | |||
+ | protected void init() [ | ||
+ | Axiom ==> MyLamp; | ||
+ | ] | ||
</ | </ | ||
+ | Note: // | ||
+ | |||
+ | Within the // | ||
+ | |||
+ | * at 1nm resolution [300, 780] | ||
+ | * A, D65 | ||
+ | * at 5nm resolution [300, 780] | ||
+ | * A, C, D50, D55, D65, D75 | ||
+ | * at 5nm resolution [380, 780] | ||
+ | * FL1-12, FL3_1-15, HP1-5 | ||
+ | |||
+ | For instance, to use the predefined CIE NORM D65 for typical sun light within a user defined light module, one could use the following code. | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.spectral.CIENormSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.objects.SpectralLight; | ||
+ | |||
+ | //define a light module | ||
+ | module MyLamp extends LightNode { | ||
+ | { | ||
+ | setLight( | ||
+ | new SpectralLight( new CIENormSpectralCurve(Attributes.CIE_NORM_D65) ).( | ||
+ | setPower(100), | ||
+ | setLight(new PointLight()) | ||
+ | ) //end SpectralLight | ||
+ | ); //end setLight | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | Using the SPD, we define the light ' | ||
+ | |||
+ | In the above example, a // | ||
- | To complete the definition of a light source, besides the spectral power distribution, | + | This is especially helpful or necessary for any definition of artificial light sources, such as those found in greenhouses, |
- | The physical light distribution can be defined as a polar distribution diagram (also called polar curve) showing the luminous intensity values with increasing angles from two imaginary axes of the lamp which is placed in the centre. Red: 0–180◦ plane, blue 90–270◦ plane. On the right of the Figure | + | The physical light distribution can be defined as a polar distribution diagram (also called polar curve) showing the luminous intensity values with increasing angles from two imaginary axes of the lamp, which is placed in the centre. Red: 0–180◦ plane, blue 90–270◦ plane. On the right of the figure |
{{ : | {{ : | ||
- | Within GroIMP, the PLD can be visualized for any light source, as illustrated below for a SpotLight. To activate the light ray visualization, | + | Within GroIMP, the PLD can be visualized for any light source, as illustrated below for a //SpotLight//. To activate the light ray visualization, |
<code java> | <code java> | ||
Line 243: | Line 277: | ||
</ | </ | ||
- | The result of the light ray visualization, | + | The result of the light ray visualization, |
{{ : | {{ : | ||
- | To see a more realistic light pattern, the scene needs to be rendered using one of the light models. Below is a rendered image of the LampDemo.gsz, | + | To see a more realistic light pattern, the scene needs to be rendered using one of the light models. Below is a rendered image of the //LampDemo.gsz//, as it can be found in the GroIMP internal example gallery. |
{{ : | {{ : | ||
- | Defining a PLD for a light source can be done in two ways: 1) ' | + | Defining a PLD for a light source can be done in two ways: 1) ' |
- | In any case, instead of one of the predefined light sources, | + | In any case, instead of one of the predefined light sources, |
<code java> | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.objects.PhysicalLight; | ||
+ | import de.grogra.gpuflux.imp3d.objects.SpectralLight; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | |||
// definition of the PLD | // definition of the PLD | ||
const double[][] DISTRIBUTION = { | const double[][] DISTRIBUTION = { | ||
Line 279: | Line 317: | ||
} | } | ||
</ | </ | ||
+ | |||
+ | |||
+ | A PLD distribution is defined as two-dimensional array, where the values of each row represent the intensities in one direction, starting at 90 degree (above the light source) going down to 270 degree (right below the light source), covering 180 degree. The values given are equally distributed of the 180 degree, e.g., when only three values are given, the first will be associated with 90 degree, the second with zero and the third with 270 degree. The rows on the other hand are equally distributed around the virtual z-axis, around the light source. If only one row is given, this distribution defined within the first row, will be used equally around the whole light source. If two rows are given, the first one will be used at zero degree and the second one at 180 degree. The values in-between are interpolated. The intensities are given in ether in absolute values (in Watt) or in percent in the range or zero to one or zero to 100. They are normalized internally again. | ||
==== Local illumination - Shader ==== | ==== Local illumination - Shader ==== | ||
+ | After the definition of the global illumination model and the light sources, the last missing part required for proper spectral light modelling is the definition of the local illumination model. In computer graphics, the tools used are called Shaders. A shader defines the local optical properties of an object, namely the values for reflection, absorption, and transmission. The Phong illumination model, or Phong shader for short, allows us to define all required aspects. | ||
+ | In the same way as the spectral curves are defined for the light sources, the spectrum for reflectance and transmittance needs to be defined for our spectral shader. The values for absorptance are obtained as the ' | ||
+ | |||
+ | Note: we employ the terms // | ||
+ | |||
+ | Note: there is no check of plausibility implemented within the Phong shader. The user needs to make sure that the sum of reflection and transmission is not higher than the actual incoming radiation. An object cannot reflect or transmit more light than it received; otherwise, it would itself be a light source emitting light. | ||
+ | |||
+ | A Phong shader can be defined as following: | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.shading.ChannelSPD; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.RGBSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.ConstantSpectralCurve; | ||
+ | |||
+ | static float[] WAVELENGTHS = {380, 485, 490, 610, 615, 720}; | ||
+ | static float[] AMPLITUDES = {0, | ||
+ | static ChannelSPD GREEN_SPD = new ChannelSPD(new IrregularSpectralCurve(WAVELENGTHS, | ||
+ | |||
+ | static ChannelSPD RED_SPD = new ChannelSPD(new RGBSpectralCurve(0.8, | ||
+ | static ChannelSPD CONST_SPD = new ChannelSPD(new ConstantSpectralCurve(0.25)); | ||
+ | |||
+ | //shader definition as global variable | ||
+ | Phong myShader0 = new Phong(); | ||
+ | static { | ||
+ | myShader0.setDiffuse(GREEN_SPD); | ||
+ | myShader0.setTransparency(RED_SPD); | ||
+ | } | ||
+ | |||
+ | //and use of the global shader within a module that is interpreted as Box | ||
+ | module TestBox ==> { | ||
+ | } Box(0.001, | ||
+ | |||
+ | |||
+ | //or define the shader within a module that extends a Box | ||
+ | module TestBox extends Box(0.001, | ||
+ | | ||
+ | //define a variable of type Phong and call it myShader | ||
+ | Phong myShader = new Phong(); | ||
+ | | ||
+ | //within the static constructor function - automatically called whenever a TestBox is generated | ||
+ | // the shader is parametrized and applied to the TestBox | ||
+ | { | ||
+ | //define the shader aspects | ||
+ | myShader.setDiffuse(GREEN_SPD); | ||
+ | myShader.setTransparency(RED_SPD); | ||
+ | myShader.setSpecular(CONST_SPD); | ||
+ | | ||
+ | //set the shader to the TestBox | ||
+ | setShader(myShader); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note: Do NOT mix common RGB shaders (like the // | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | ==== SPD and PLD files and references ==== | ||
+ | Beside defining the SPD and PLD as arrays within XL, GroIMP supports the import of common file formates for both. | ||
+ | |||
+ | Both can be imported in the same way by open the Panels Tab and go to Explorers first. For PLD, the ' | ||
+ | |||
+ | {{ : | ||
+ | {{ : | ||
+ | |||
+ | To access the imported light spectra and physical light distributions within XL, one needs to define a reference to the files in the following way: | ||
+ | |||
+ | <code java> | ||
+ | // | ||
+ | const LightDistributionRef DISTRIBUTION = light(”distri1”); | ||
+ | const SpectrumRef SPECTRUM = spectrum(”equal”); | ||
+ | |||
+ | set them via the constructor | ||
+ | module MyLamp extends LightNode { | ||
+ | { | ||
+ | setLight(new SpectralLight(new PhysicalLight(DISTRIBUTION), | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | GroIMP supports the inport of ' | ||
==== Example ==== | ==== Example ==== | ||
+ | In the following four minimal working examples are given to illustrate: the light model, the definition of light sources, adding a object and define a spectral shader, and on how to visualize the results. The four examples are building on each other, meaning with each example new parts will extend the previous code. | ||
+ | Note: The examples require GroIMP version >=2.0 to run. With GroIMP version 2.0 some changes on the internal package structure are made. formally classes found in // | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | |||
+ | // Light nodes need to be imported like this | ||
+ | import de.grogra.gpuflux.imp3d.objects.PhysicalLight; | ||
+ | </ | ||
+ | |||
+ | === Example 1 - Light Model === | ||
+ | This example just defines the GPUFlux light model and parameterizes it to simulate a spectrum from 300 to 800nm and measure the results in 30 buckets. | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; | ||
+ | |||
+ | //constants for the light model: number of rays and maximal recursion depth | ||
+ | const int RAYS = 1000000; | ||
+ | const int DEPTH = 10; | ||
+ | |||
+ | // | ||
+ | protected void init () { | ||
+ | // | ||
+ | println(" | ||
+ | FluxLightModel GPU_LM = new FluxLightModel(RAYS, | ||
+ | GPU_LM.setSeed(1234567890); | ||
+ | GPU_LM.setMeasureMode(MeasureMode.FULL_SPECTRUM); | ||
+ | GPU_LM.setSpectralDomain(300, | ||
+ | GPU_LM.setSpectralBuckets(31);// | ||
+ | GPU_LM.compute();// | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The model will run and directly when saved, creates an instance of the light model, set wanted parameters and run it. there will be no output (except the one form the light model itself, stating that it was executed and giving some statistics on the scene and further stating that no light sources could be found within the scene). | ||
+ | |||
+ | If you already get errors here, your system most probably does not support spectral light modelling. | ||
+ | |||
+ | |||
+ | === Example 2 - Light Sources === | ||
+ | This example defines a spectral light source with a user define physical light distribution (PLD) and a predefined CIE NORM D55 as spectral power distribution (SPD) (used to define typical sun light) and add the light source to the scene. | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.spectral.CIENormSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.objects.SpectralLight; | ||
+ | import de.grogra.gpuflux.imp3d.objects.PhysicalLight; | ||
+ | import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; | ||
+ | import de.grogra.gpuflux.scene.experiment.Measurement; | ||
+ | |||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | // | ||
+ | const double[][] DISTRIBUTION = { | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||
+ | }; | ||
+ | |||
+ | //use the predefined CIE NORM D65 for typical sun light | ||
+ | static const float[] WAVELENGTHS_S = CIENormSpectralCurve.NM_300_780_5; | ||
+ | static const float[] AMPLITUDES_S = CIENormSpectralCurve.D65; | ||
+ | |||
+ | //define a light node | ||
+ | module MyLamp extends LightNode { | ||
+ | { | ||
+ | setLight( | ||
+ | new SpectralLight( | ||
+ | new IrregularSpectralCurve(WAVELENGTHS_S, | ||
+ | ).( | ||
+ | setPower(7.8), | ||
+ | setLight( | ||
+ | new PhysicalLight(DISTRIBUTION).( | ||
+ | setVisualize(true), | ||
+ | setNumberofrays(500), | ||
+ | setRaylength(3.5) | ||
+ | ) | ||
+ | ) | ||
+ | ) //end SpectralLight | ||
+ | ); //end setLight | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //constants for the light model: number of rays and maximal recursion depth | ||
+ | const int RAYS = 1000000; | ||
+ | const int DEPTH = 10; | ||
+ | |||
+ | // | ||
+ | protected void init () { | ||
+ | //create the actual 3D scene | ||
+ | [ | ||
+ | Axiom ==> MyLamp; | ||
+ | ] | ||
+ | |||
+ | //make sure the changes on the graph are applied... | ||
+ | {derive(); | ||
+ | //so that we directly can continue and work on the graph | ||
+ | |||
+ | // | ||
+ | println(" | ||
+ | FluxLightModel GPU_LM = new FluxLightModel(RAYS, | ||
+ | GPU_LM.setSeed(1234567890); | ||
+ | GPU_LM.setMeasureMode(MeasureMode.FULL_SPECTRUM); | ||
+ | GPU_LM.setSpectralDomain(300, | ||
+ | GPU_LM.setSpectralBuckets(31);// | ||
+ | GPU_LM.compute();// | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Since the visualization of the light rays is turned on for the light source, we can see the light source in the 3D view window. | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | |||
+ | === Example 3 - Scene object and shader === | ||
+ | Here now we define a test object, s simple flat box of one square meter in dimension and apply a green spectral shader to it. The 3D view window should now show something similar to this: | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.spectral.CIENormSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.shading.ChannelSPD; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.objects.SpectralLight; | ||
+ | import de.grogra.gpuflux.imp3d.objects.PhysicalLight; | ||
+ | import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; | ||
+ | import de.grogra.gpuflux.scene.experiment.Measurement; | ||
+ | |||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | // | ||
+ | const double[][] DISTRIBUTION = { | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||
+ | }; | ||
+ | |||
+ | //use the predefined CIE NORM D65 for typical sun light | ||
+ | static const float[] WAVELENGTHS_S = CIENormSpectralCurve.NM_300_780_5; | ||
+ | static const float[] AMPLITUDES_S = CIENormSpectralCurve.D65; | ||
+ | |||
+ | //define a light node | ||
+ | module MyLamp extends LightNode { | ||
+ | { | ||
+ | setLight( | ||
+ | new SpectralLight( | ||
+ | new IrregularSpectralCurve(WAVELENGTHS_S, | ||
+ | ).( | ||
+ | setPower(7.8), | ||
+ | setLight( | ||
+ | new PhysicalLight(DISTRIBUTION).( | ||
+ | setVisualize(true), | ||
+ | setNumberofrays(500), | ||
+ | setRaylength(3.5) | ||
+ | ) | ||
+ | ) | ||
+ | ) //end SpectralLight | ||
+ | ); //end setLight | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | //define a green shader as user-defined irregular spectral curve | ||
+ | public const float[] WAVELENGTHS = {300, 525, 530, 575, 580, 800}; | ||
+ | public const float[] AMPLITUDES = {0, | ||
+ | const ChannelSPD GREEN_SPD = new ChannelSPD(new IrregularSpectralCurve(WAVELENGTHS, | ||
+ | |||
+ | //apply the shader to an object: a box of one square meter | ||
+ | module TestShader ==> { | ||
+ | Phong myShader = new Phong(); | ||
+ | // | ||
+ | myShader.setDiffuse(GREEN_SPD); | ||
+ | } Box(0.001, | ||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | |||
+ | //constants for the light model: number of rays and maximal recursion depth | ||
+ | const int RAYS = 1000000; | ||
+ | const int DEPTH = 10; | ||
+ | |||
+ | // | ||
+ | protected void init () { | ||
+ | clearConsole(); | ||
+ | |||
+ | //create the actual 3D scene | ||
+ | [ | ||
+ | Axiom ==> TestShader M(2) RL(180) MyLamp; | ||
+ | ] | ||
+ | |||
+ | //make sure the changes on the graph are applied... | ||
+ | {derive(); | ||
+ | //so that we directly can continue and work on the graph | ||
+ | |||
+ | // | ||
+ | println(" | ||
+ | FluxLightModel GPU_LM = new FluxLightModel(RAYS, | ||
+ | GPU_LM.setSeed(1234567890); | ||
+ | GPU_LM.setMeasureMode(MeasureMode.FULL_SPECTRUM); | ||
+ | GPU_LM.setSpectralDomain(300, | ||
+ | GPU_LM.setSpectralBuckets(31);// | ||
+ | GPU_LM.compute();// | ||
+ | |||
+ | //check the scene objects for their light absorption | ||
+ | Measurement ms; | ||
+ | [ | ||
+ | x: | ||
+ | ] | ||
+ | println("" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | To obtain the measurement results, one needs to first run the light model and second check each (wanted) scene object for its absorption values. A simple graph query can eb used to implement the second part, where here is searched for all TestShader instances within the graph and the light absorption is obtained. Afterwards the results a printed to the GroIMP console window. The output of the code is the integrated absorbed power and the array of the absorption values for each bucket. | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | |||
+ | === Example 4 - Output visualization === | ||
+ | In the final version, we are now going to add a charts to visualize the emitted spectrum and to plot it against the absorbed spectrum of the test object. | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | <code java> | ||
+ | import de.grogra.gpuflux.imp3d.spectral.CIENormSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.shading.ChannelSPD; | ||
+ | import de.grogra.gpuflux.imp3d.spectral.IrregularSpectralCurve; | ||
+ | import de.grogra.gpuflux.imp3d.objects.SpectralLight; | ||
+ | import de.grogra.gpuflux.imp3d.objects.PhysicalLight; | ||
+ | import de.grogra.gpuflux.tracer.FluxLightModelTracer.MeasureMode; | ||
+ | import de.grogra.gpuflux.scene.experiment.Measurement; | ||
+ | |||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | // | ||
+ | const double[][] DISTRIBUTION = { | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, | ||
+ | {1, 1, 1,1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} | ||
+ | }; | ||
+ | |||
+ | |||
+ | //use the predefined CIE NORM D65 for typical sun light | ||
+ | static const float[] WAVELENGTHS_S = CIENormSpectralCurve.NM_300_780_5; | ||
+ | static const float[] AMPLITUDES_S = CIENormSpectralCurve.D65; | ||
+ | |||
+ | //define a light node | ||
+ | module MyLamp extends LightNode { | ||
+ | { | ||
+ | setLight( | ||
+ | new SpectralLight( | ||
+ | new IrregularSpectralCurve(WAVELENGTHS_S, | ||
+ | ).( | ||
+ | setPower(7.8), | ||
+ | setLight( | ||
+ | new PhysicalLight(DISTRIBUTION).( | ||
+ | setVisualize(true), | ||
+ | setNumberofrays(500), | ||
+ | setRaylength(3.5) | ||
+ | ) | ||
+ | ) | ||
+ | ) //end SpectralLight | ||
+ | ); //end setLight | ||
+ | |||
+ | } | ||
+ | } | ||
+ | |||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | //define a green shader as user-defined irregular spectral curve | ||
+ | public const float[] WAVELENGTHS = {300, 525, 530, 575, 580, 700}; | ||
+ | public const float[] AMPLITUDES = {0, | ||
+ | const ChannelSPD GREEN_SPD = new ChannelSPD(new IrregularSpectralCurve(WAVELENGTHS, | ||
+ | |||
+ | //apply the shader to an object: a box of one square meter | ||
+ | module TestShader ==> { | ||
+ | Phong myShader = new Phong(); | ||
+ | // | ||
+ | myShader.setDiffuse(GREEN_SPD); | ||
+ | } Box(0.001, | ||
+ | //////////////////////////////////////////////////////////////////////////////// | ||
+ | |||
+ | //define the data sheet | ||
+ | const DatasetRef absorbedChart = new DatasetRef(" | ||
+ | |||
+ | //constants for the light model: number of rays and maximal recursion depth | ||
+ | const int RAYS = 1000000; | ||
+ | const int DEPTH = 10; | ||
+ | |||
+ | // | ||
+ | protected void init () { | ||
+ | clearConsole(); | ||
+ | |||
+ | // | ||
+ | absorbedChart.clear().setColumnKey(0," | ||
+ | chart(absorbedChart, | ||
+ | |||
+ | //plot the emitted spectral curve | ||
+ | float INTEGRAL = 0; | ||
+ | for(int i: | ||
+ | for(int i: | ||
+ | absorbedChart.addRow().(set(0, | ||
+ | } | ||
+ | |||
+ | //create the actual 3D scene | ||
+ | [ | ||
+ | Axiom ==> TestShader M(2) RL(180) MyLamp; | ||
+ | ] | ||
+ | |||
+ | //make sure the changes on the graph are applied... | ||
+ | {derive(); | ||
+ | //so that we directly can continue and work on the graph | ||
+ | |||
+ | // | ||
+ | println(" | ||
+ | FluxLightModel GPU_LM = new FluxLightModel(RAYS, | ||
+ | GPU_LM.setSeed(1234567890); | ||
+ | GPU_LM.setMeasureMode(MeasureMode.FULL_SPECTRUM); | ||
+ | GPU_LM.setSpectralDomain(300, | ||
+ | GPU_LM.setSpectralBuckets(31);// | ||
+ | GPU_LM.compute();// | ||
+ | |||
+ | //check the scene objects for their light absorption | ||
+ | Measurement ms; | ||
+ | [ | ||
+ | x: | ||
+ | ] | ||
+ | print(" | ||
+ | |||
+ | //plot the absorption spectrum | ||
+ | for(int i: | ||
+ | absorbedChart.addRow().(set(1, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | |||
+ | ==== Dispersion ==== | ||
+ | |||
+ | The GPUFlux light model also supports the simulation of dispersion effects – the phenomenon where light separates into its constituent colours due to variations in the reflective index with wavelengths. Different wavelengths of light refract (bend) at different angles when passing through a medium like glass, causing the colours to separate. Essentially, | ||
+ | |||
+ | As default the simulation of dispersion is turned off - to speed up calculations - but can be enabled in the preferences of the GPUFlux light model or within the code as following: | ||
+ | |||
+ | <code java> | ||
+ | FluxLightModel LM = new FluxLightModel(RAYS, | ||
+ | |||
+ | // enable dispersion | ||
+ | LM.setDispersion(true); | ||
+ | </ | ||
+ | |||
+ | |||
+ | When enabled and applied to a scene containing some ‘diamonds’ (imported OBJ objects), one can generate things like shown below (Henke and Buck-Sorlin 2018): | ||
+ | |||
+ | {{ : | ||
Line 293: | Line 778: | ||
Special thanks to Dietger van Antwerpen, who implemented the GPUFlux light model for GroIMP! | Special thanks to Dietger van Antwerpen, who implemented the GPUFlux light model for GroIMP! | ||
+ | |||
+ | |||
+ | ===== References ===== | ||
+ | |||
+ | * Henke M and Buck-Sorlin GH (2018) Using a full spectral raytracer for the modelling of light microclimate in a functional-structural plant model; Computing and Informatics, | ||
+ | * van Antwerpen, D.G. (2011) High Performance Spectral Light Transport Model for Agricultural Applications, | ||
+ | * van Antwerpen, D.G. (2011) Unbiased physically based rendering on the GPU, Master thesis, Delft University of Technology | ||
+ |
tutorials/basic-spectral-light-modeling.1733132834.txt.gz · Last modified: 2024/12/02 10:47 by MH