|
| 1 | +**Redo tutorial using a tiled floor, we should be able to see spatial distribution better** |
| 2 | + |
| 3 | + |
| 4 | +# Constructing light sources |
| 5 | + |
| 6 | +Alejandro Morales |
| 7 | + |
| 8 | +Centre for Crop Systems Analysis - Wageningen University |
| 9 | + |
| 10 | +## What makes a light source? |
| 11 | + |
| 12 | +In VPL, a light (or radiation) source is responsible for generating rays that will be |
| 13 | +traced throughout a 3D scene as they interact with surfaces. A ray is composed of three |
| 14 | +components: a direction, an origin, and a vector of radiant power. |
| 15 | + |
| 16 | +Both the origin and the direction of a ray are generated randomly by the light source |
| 17 | +according to specific distributions, whereas the radiant power per ray and the total number |
| 18 | +of rays is determined by the user during the simulation. Therefore, defining a new type of |
| 19 | +light source consists of defining the distributions for the origin and direction of the rays. |
| 20 | + |
| 21 | +The origin of the rain is determined by the geometry component of a light source (field |
| 22 | +`geom` of the type `Source`). VPL currently supports the following geometry components: |
| 23 | + |
| 24 | +- `PointSource`: A single point in space from which all rays are generated. |
| 25 | + |
| 26 | +- `LineSource`: A line segment in space from which all rays are sampled randomly. |
| 27 | + |
| 28 | +- `AreaSource`: A 3D mesh representing a surface from which all rays are sampled randomly. |
| 29 | + |
| 30 | +- `DirectionalSource`: A special case for directional light sources that should cover the entire scene (assuming one is using a grid cloner). |
| 31 | + |
| 32 | +The direction of the rays is determined by the direction component of a light source (field |
| 33 | +`angle` of the type `Source`). VPL currently implements two types of direction components: |
| 34 | + |
| 35 | +- `FixedSource`: All rays have the same direction (generally combined with `DirectionalSource` to represent solar radiation). |
| 36 | + |
| 37 | +- `LambertianSource`: The direction of the rays is sampled from a Lambertian distribution. |
| 38 | + |
| 39 | +Users may want to defined their own light sources, often by specifying different distributions |
| 40 | +of directions to the ones provided by VPL in order to capture more realistic the light |
| 41 | +sources used in certain productive contexts (e.g., greenhouses and vertical farms). This |
| 42 | +guide illustrates how to define a new light source in VPL by specifying the direction |
| 43 | +component assuming a Gaussian distribution. |
| 44 | + |
| 45 | +## Creating your own direction component of a light source |
| 46 | + |
| 47 | +Creating a new direction component in VPL is straightforward. The user needs to define a |
| 48 | +new type that inherits from the abstract class `SourceAngle` and defined a method for the |
| 49 | +function `generate_direction` that takes as first argument the user-defined type and as |
| 50 | +second a random number generator. The method should return a 3D unit vector representing the |
| 51 | +direction of the ray. The user is given complete freedom as to how the direction is |
| 52 | +generated. |
| 53 | + |
| 54 | +Typically, the distribution of possible distributions is encoded in polar coordinates, that |
| 55 | +is, the azimuth ($Phi$, orientation of the ray) and zenith angles ($theta$, inclination of |
| 56 | +the ray) with respect to the plane that contains the light source. This requires defining a |
| 57 | +local coordinate system for the light source, that is, an XYZ coordinate system where the XY |
| 58 | +plane is the plane that contains the light source and the Z axis is perpendicular to the |
| 59 | +plane. |
| 60 | + |
| 61 | +Given the azimuth and zenith angles and the local coordinate system, the direction of the |
| 62 | +ray can be computed using the `polar_to_cartesian` function provided by the package |
| 63 | +PlantRayTracer within VPL. |
| 64 | + |
| 65 | +```julia |
| 66 | +import PlantRayTracer as PRT |
| 67 | +dir = PRT.polar_to_cartesian((e1 = X, e2 = Y, n = Z), θ, Φ) |
| 68 | +``` |
| 69 | + |
| 70 | +The function `polar_to_cartesian` will perform the necessary conversions and ensure that the |
| 71 | +resulting direction vector meets the expectations of the ray tracer within VPL. The user |
| 72 | +thus only needs to specify distributions for the azimuth and zenith angles and generate the |
| 73 | +samples accordingly. Let's illustrate this with a Gaussian light source. |
| 74 | + |
| 75 | +## Example: Gaussian light source |
| 76 | + |
| 77 | +A Gaussian light source is a light source where the azimuth angle follows an uniform |
| 78 | +distribution (from 0 to 2π radians) and the zenith angle follows a Gaussian distribution |
| 79 | +constrained within the interval [-π/2, π/2] radians centered at 0. This Gaussian distribution |
| 80 | +will be parameterized by a standard deviation $\sigma$. First we define the type for the |
| 81 | +Gaussian light source to store the axes of the local coordinate system and the standard |
| 82 | +deviation of the zenith angle. |
| 83 | + |
| 84 | +```julia |
| 85 | +using VirtualPlantLab |
| 86 | +import PlantRayTracer as PRT |
| 87 | +struct GaussianSource <: PRT.SourceAngle |
| 88 | + x::Vec{Float64} |
| 89 | + y::Vec{Float64} |
| 90 | + z::Vec{Float64} |
| 91 | + σ::Float64 |
| 92 | +end |
| 93 | +``` |
| 94 | + |
| 95 | +We will then use the Distributions.jl package to generate the samples for the zenith angle by |
| 96 | +defining a method to the function `generate_direction`: |
| 97 | + |
| 98 | +```julia |
| 99 | +using Distributions |
| 100 | +import ColorTypes: RGB |
| 101 | +function PRT.generate_direction(source::GaussianSource, rng) |
| 102 | + dist = Truncated(Normal(0, source.σ), -π/2, π/2) |
| 103 | + θ = rand(rng, dist) |
| 104 | + Φ = 2π*rand(rng) |
| 105 | + dir = PRT.polar_to_cartesian((e1 = source.x, e2 = source.y, n = source.z), θ, Φ) |
| 106 | + return dir |
| 107 | +end |
| 108 | +``` |
| 109 | + |
| 110 | +We also need to define the geometry of the light source. Let's assume that the light source |
| 111 | +is just a point in space at coordinates [0.5, 0.5, 1.0], that it is pointing downwards, the |
| 112 | +radiant power is 1 and we want to generate one million rays. We create the light source as |
| 113 | +follows: |
| 114 | + |
| 115 | +```julia |
| 116 | +source = Source(PointSource(Vec(0.5, 0.5, 1.0)), GaussianSource(X(), Y(), -Z(), 0.1), 1.0, 1_000_000) |
| 117 | +``` |
| 118 | + |
| 119 | +This light source is rather narrow as we use a standar deviation of 0.1. We can visualize the |
| 120 | +distribution of zenith angles as follows: |
| 121 | + |
| 122 | +```julia |
| 123 | +using Plots |
| 124 | +dist = Truncated(Normal(0, 0.1), -π/2, π/2) |
| 125 | +θ = rand(dist, 1_000_000) |
| 126 | +histogram(θ, label = "", xlabel = "Zenith angle", ylabel = "Prob. density", normalize=:pdf) |
| 127 | +vline!([-π/2, π/2], label = "") |
| 128 | +``` |
| 129 | + |
| 130 | +The light source is now ready to be used in a ray tracer. We will test this light source by |
| 131 | +creating multiple sensors below the light source and measuring the light intensity at each |
| 132 | +sensor. |
| 133 | + |
| 134 | +```julia |
| 135 | +scene = Scene() |
| 136 | +sensors = [Sensor(1) for _ in 1:400] |
| 137 | +c = 1 |
| 138 | +for i in 1:20 |
| 139 | + for j in 1:20 |
| 140 | + r = Rectangle(length = 0.05, width = 0.05) |
| 141 | + rotatey!(r, π/2) ## To put it in the XY plane |
| 142 | + VirtualPlantLab.translate!(r, Vec((i - 1)*0.05, (j - 1)*0.05 + 0.025, 0.0)) |
| 143 | + add!(scene, mesh = r, materials = sensors[c], colors = rand(RGB)) |
| 144 | + c += 1 |
| 145 | + end |
| 146 | +end |
| 147 | +``` |
| 148 | + |
| 149 | +```julia |
| 150 | +import GLMakie |
| 151 | +render(scene) |
| 152 | +``` |
| 153 | + |
| 154 | +We can now create a ray tracing object without a grid cloner (note that since all the |
| 155 | +surfaces are sensors we can just set `maxiter = 1`): |
| 156 | + |
| 157 | +```julia |
| 158 | +settings = RTSettings(pkill = 0.9, maxiter = 1, parallel = true) |
| 159 | +rtobj = RayTracer(scene, source, settings = settings); |
| 160 | +trace!(rtobj) |
| 161 | +``` |
| 162 | + |
| 163 | +We can now visualize the results by plotting the light intensity at each sensor: |
| 164 | + |
| 165 | +```julia |
| 166 | +p_narrow = [power(s)[1]/1_000_000 for s in sensors] |
| 167 | +coords = collect(0.025:0.05:0.975) |
| 168 | +heatmap(coords, coords, reshape(p_narrow,20,20)) |
| 169 | +``` |
| 170 | + |
| 171 | +We can see a straight line meaning that the light intensity is practical constant at each |
| 172 | +sensor since the light source is so narrow. Let's now test a wider light source by setting |
| 173 | +the standard deviation to 1.5. Let's first look at the histogram |
| 174 | + |
| 175 | +```julia |
| 176 | +dist = Truncated(Normal(0, 1.0), -π/2, π/2) |
| 177 | +θ = rand(dist, 1_000_000) |
| 178 | +histogram(θ, label = "", xlabel = "Zenith angle", ylabel = "Prob. density", normalize=:pdf) |
| 179 | +vline!([-π/2, π/2], label = "") |
| 180 | +``` |
| 181 | + |
| 182 | +Now we expect a lot of rays to escaped from the sides of the scene and not hit the sensors. |
| 183 | +Let's test this by creating a new light source and tracing the rays again. |
| 184 | + |
| 185 | +```julia |
| 186 | +source = Source(PointSource(Vec(0.5, 0.5, 1.0)), GaussianSource(X(), Y(), -Z(), 1.0), 1.0, 1_000_000) |
| 187 | +settings = RTSettings(pkill = 0.9, maxiter = 1, parallel = true) |
| 188 | +rtobj = RayTracer(scene, source, settings = settings); |
| 189 | +trace!(rtobj) |
| 190 | +``` |
| 191 | + |
| 192 | +If we plot the light intensity at each sensor we will see that the sensors receive less light |
| 193 | +than before but also the gradient of light intensity towards the shaded regions is less |
| 194 | +pronounced. |
| 195 | + |
| 196 | +```julia |
| 197 | +p_wide = [power(s)[1]/1_000_000 for s in sensors] |
| 198 | +heatmap(coords, coords, reshape(p_wide,20,20)) |
| 199 | +``` |
0 commit comments