Non-Uniform Lighting Functions

Example of nonuniform lighting distribution functions in OSL:

I first started working on this when I heard about IES light-specification support being added to Cycles a few years ago, and I wondered what all the fuss was about, when you could already use nonuniform intensity functions on Cycles lights?

All the variation in brightness and colour that you see comes from the lights, not the materials. There is one lamp in the middle of the cylinder, another in the middle of the bowl. The one on the left uses spherical harmonics (plus a colour ramp for variety), while the one on the right is based on what I call “spherical lobes”—products of axial and radial trigonometric components.

The shaders were generated with osl-gen-spherical-harmonics and osl-gen-spherical-lobes, both part of my osl_useful script collection.

2 Likes

For those who are interested, here is the node setup for the spherical harmonics lamp:

The source for the OSL shader node is

shader harmonics
  (
    float Wm6 = 0,
    float Pm6 = 0,
    float Wm5 = 0,
    float Pm5 = 0,
    float Wm4 = 0,
    float Pm4 = 0,
    float Wm3 = 0,
    float Pm3 = 0,
    float Wm2 = 0,
    float Pm2 = 0,
    float Wm1 = 0,
    float Pm1 = 0,
    float W0 = 0,
    float Wp1 = 0,
    float Pp1 = 0,
    float Wp2 = 0,
    float Pp2 = 0,
    float Wp3 = 0,
    float Pp3 = 0,
    float Wp4 = 0,
    float Pp4 = 0,
    float Wp5 = 0,
    float Pp5 = 0,
    float Wp6 = 0,
    float Pp6 = 0,
    normal Direction = transform("world", "object", N),
    output float Result = 1
  )
  /* generated by osl-gen-spherical-harmonics. */
  {
    float len_xy = hypot(Direction[1], Direction[0]);
    float len_xyz = hypot(Direction[2], len_xy);
    float sin_theta = len_xy / len_xyz;
    float cos_theta = Direction[2] / len_xyz;
    float phi = atan2(Direction[1], Direction[0]);
    Result = max
      (
            W0 * 0.0635692 * (- 5 *  + 105 * cos_theta * cos_theta - 315 * cos_theta * cos_theta * cos_theta * cos_theta + 231 * cos_theta * cos_theta * cos_theta * cos_theta * cos_theta * cos_theta)
        +
            Wp1 * -0.4119755 * (cos(Pp1 * 2 * M_PI) * cos(phi) - sin(Pp1 * 2 * M_PI) * sin(phi)) * sin_theta * (+ 5 * cos_theta - 30 * cos_theta * cos_theta * cos_theta + 33 * cos_theta * cos_theta * cos_theta * cos_theta * cos_theta)
        +
            Wm1 * 0.4119755 * (cos(Pm1 * 2 * M_PI) * cos(phi) - sin(Pm1 * 2 * M_PI) * sin(phi)) * sin_theta * (+ 5 * cos_theta - 30 * cos_theta * cos_theta * cos_theta + 33 * cos_theta * cos_theta * cos_theta * cos_theta * cos_theta)
        +
            Wp2 * 0.3256952 * (cos(Pp2 * 2 * M_PI) * cos(2 * phi) - sin(Pp2 * 2 * M_PI) * sin(2 * phi)) * sin_theta * sin_theta * (+ 1 - 18 * cos_theta * cos_theta + 33 * cos_theta * cos_theta * cos_theta * cos_theta)
        +
            Wm2 * 0.3256952 * (cos(Pm2 * 2 * M_PI) * cos(2 * phi) - sin(Pm2 * 2 * M_PI) * sin(2 * phi)) * sin_theta * sin_theta * (+ 1 - 18 * cos_theta * cos_theta + 33 * cos_theta * cos_theta * cos_theta * cos_theta)
        +
            Wp3 * -0.6513905 * (cos(Pp3 * 2 * M_PI) * cos(3 * phi) - sin(Pp3 * 2 * M_PI) * sin(3 * phi)) * sin_theta * sin_theta * sin_theta * (- 3 * cos_theta + 11 * cos_theta * cos_theta * cos_theta)
        +
            Wm3 * 0.6513905 * (cos(Pm3 * 2 * M_PI) * cos(3 * phi) - sin(Pm3 * 2 * M_PI) * sin(3 * phi)) * sin_theta * sin_theta * sin_theta * (- 3 * cos_theta + 11 * cos_theta * cos_theta * cos_theta)
        +
            Wp4 * 0.3567813 * (cos(Pp4 * 2 * M_PI) * cos(4 * phi) - sin(Pp4 * 2 * M_PI) * sin(4 * phi)) * sin_theta * sin_theta * sin_theta * sin_theta * (- 1 + 11 * cos_theta * cos_theta)
        +
            Wm4 * 0.3567813 * (cos(Pm4 * 2 * M_PI) * cos(4 * phi) - sin(Pm4 * 2 * M_PI) * sin(4 * phi)) * sin_theta * sin_theta * sin_theta * sin_theta * (- 1 + 11 * cos_theta * cos_theta)
        +
            Wp5 * -1.673452 * (cos(Pp5 * 2 * M_PI) * cos(5 * phi) - sin(Pp5 * 2 * M_PI) * sin(5 * phi)) * sin_theta * sin_theta * sin_theta * sin_theta * sin_theta * cos_theta
        +
            Wm5 * 1.673452 * (cos(Pm5 * 2 * M_PI) * cos(5 * phi) - sin(Pm5 * 2 * M_PI) * sin(5 * phi)) * sin_theta * sin_theta * sin_theta * sin_theta * sin_theta * cos_theta
        +
            Wp6 * 0.4830841 * (cos(Pp6 * 2 * M_PI) * cos(6 * phi) - sin(Pp6 * 2 * M_PI) * sin(6 * phi)) * sin_theta * sin_theta * sin_theta * sin_theta * sin_theta * sin_theta
        +
            Wm6 * 0.4830841 * (cos(Pm6 * 2 * M_PI) * cos(6 * phi) - sin(Pm6 * 2 * M_PI) * sin(6 * phi)) * sin_theta * sin_theta * sin_theta * sin_theta * sin_theta * sin_theta,
        0
      );
  } /*harmonics*/

which was generated with the command

osl-gen-spherical-harmonics --name=harmonics --clamp-positive 6

using one of my osl_useful scripts.

And here is the setup for the spherical-lobes lamp:

The source for the OSL shader being

shader lobes
  (
    float Angle_axial_1 = 0,
    float Freq_axial_1 = 1,
    float Intens_axial_1 = 0,
    float Power_axial_1 = 1,
    float Angle_axial_2 = 0.5,
    float Freq_axial_2 = 1,
    float Intens_axial_2 = 0,
    float Power_axial_2 = 1,
    float Angle_radial_1 = 0,
    float Freq_radial_1 = 1,
    float Intens_radial_1 = 0,
    float Power_radial_1 = 1,
    float Angle_radial_2 = 0.5,
    float Freq_radial_2 = 1,
    float Intens_radial_2 = 0,
    float Power_radial_2 = 1,
    normal Direction = transform("world", "object", N),
    output float Result = 1
  )
  /* generated by osl-gen-spherical-lobes. */
  {
    float phi = atan2(Direction[1], Direction[0]);
    float theta = atan2(hypot(Direction[1], Direction[0]), Direction[2]);
    Result =
            (
                Intens_axial_1 * pow((cos(theta * Freq_axial_1 - Angle_axial_1 * M_PI) + 1) / 2, Power_axial_1)
            +
                Intens_axial_2 * pow((cos(theta * Freq_axial_2 - Angle_axial_2 * M_PI) + 1) / 2, Power_axial_2)
            )
        *
            (
                Intens_radial_1 * pow((cos(phi * Freq_radial_1 - Angle_radial_1 * 2 * M_PI) + 1) / 2, Power_radial_1)
            +
                Intens_radial_2 * pow((cos(phi * Freq_radial_2 - Angle_radial_2 * 2 * M_PI) + 1) / 2, Power_radial_2)
            );
  } /*lobes*/

generated with

osl-gen-spherical-lobes --name=lobes 2 2