Added another color temperature based way of specifying color.

This uses a normalized version of blackbody radiation, so the
colors still vary but the brightness doesn't vary nearly as
wildly as with genuine blackbody radiation.
This commit is contained in:
Nathan Vegdahl 2018-12-28 01:27:08 -08:00
parent 53754b956c
commit 1cd5d28767
6 changed files with 124 additions and 8 deletions

View File

@ -81,13 +81,17 @@ class PsychopathCamera(bpy.types.PropertyGroup):
class PsychopathLight(bpy.types.PropertyGroup):
color_type = EnumProperty(
name="Color Type", description="",
items=[('Rec709', 'Rec709', ""), ('Blackbody', 'Blackbody', "")],
items=[
('Rec709', 'Rec709', ""),
('Blackbody', 'Blackbody', ""),
('ColorTemperature', 'ColorTemperature', "Same as Blackbody, except with brightness kept more even."),
],
default="Rec709"
)
color_blackbody_temp = FloatProperty(
name="Temperature", description="Blackbody temperature in kelvin",
min=0.0, max=32000.0, soft_min=800.0, soft_max=6500.0, default=1200.0
min=0.0, soft_min=800.0, soft_max=6500.0, default=1200.0
)
# Custom Mesh properties
@ -107,7 +111,11 @@ class PsychopathMaterial(bpy.types.PropertyGroup):
color_type = EnumProperty(
name="Color Type", description="",
items=[('Rec709', 'Rec709', ""), ('Blackbody', 'Blackbody', "")],
items=[
('Rec709', 'Rec709', ""),
('Blackbody', 'Blackbody', ""),
('ColorTemperature', 'ColorTemperature', "Same as Blackbody, except with brightness kept more even."),
],
default="Rec709"
)
@ -120,7 +128,7 @@ class PsychopathMaterial(bpy.types.PropertyGroup):
color_blackbody_temp = FloatProperty(
name="Temperature", description="Blackbody temperature in kelvin",
min=0.0, max=32000.0, soft_min=800.0, soft_max=6500.0, default=1200.0
min=0.0, soft_min=800.0, soft_max=6500.0, default=1200.0
)
roughness = FloatProperty(

View File

@ -218,6 +218,8 @@ class SphereLamp:
self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'ColorTemperature':
self.time_col += [('ColorTemperature', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
self.time_rad += [self.ob.data.shadow_soft_size]
@ -234,6 +236,8 @@ class SphereLamp:
w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2]))
elif col[0] == 'Blackbody':
w.write("Color [blackbody, %f %f]\n" % (col[1], col[2]))
elif col[0] == 'ColorTemperature':
w.write("Color [color_temperature, %f %f]\n" % (col[1], col[2]))
for rad in self.time_rad:
w.write("Radius [%f]\n" % rad)
@ -257,6 +261,8 @@ class RectLamp:
self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'ColorTemperature':
self.time_col += [('ColorTemperature', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
if self.ob.data.shape == 'RECTANGLE':
self.time_dim += [(self.ob.data.size, self.ob.data.size_y)]
@ -276,6 +282,8 @@ class RectLamp:
w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2]))
elif col[0] == 'Blackbody':
w.write("Color [blackbody, %f %f]\n" % (col[1], col[2]))
elif col[0] == 'ColorTemperature':
w.write("Color [color_temperature, %f %f]\n" % (col[1], col[2]))
for dim in self.time_dim:
w.write("Dimensions [%f %f]\n" % dim)
@ -340,6 +348,11 @@ class Material:
self.mat.psychopath.color_blackbody_temp,
1.0,
))
elif self.mat.psychopath.color_type == 'ColorTemperature':
w.write("Color [color_temperature, %f %f]\n" % (
self.mat.psychopath.color_blackbody_temp,
1.0,
))
elif self.mat.psychopath.surface_shader_type == 'Lambert':
w.write("Type [Lambert]\n")
if self.mat.psychopath.color_type == 'Rec709':
@ -352,6 +365,11 @@ class Material:
self.mat.psychopath.color_blackbody_temp,
1.0,
))
elif self.mat.psychopath.color_type == 'ColorTemperature':
w.write("Color [color_temperature, %f %f]\n" % (
self.mat.psychopath.color_blackbody_temp,
1.0,
))
elif self.mat.psychopath.surface_shader_type == 'GGX':
w.write("Type [GGX]\n")
if self.mat.psychopath.color_type == 'Rec709':
@ -364,6 +382,11 @@ class Material:
self.mat.psychopath.color_blackbody_temp,
1.0,
))
elif self.mat.psychopath.color_type == 'ColorTemperature':
w.write("Color [color_temperature, %f %f]\n" % (
self.mat.psychopath.color_blackbody_temp,
1.0,
))
w.write("Roughness [%f]\n" % self.mat.psychopath.roughness)
w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel)
else:

View File

@ -130,7 +130,7 @@ class DATA_PT_psychopath_lamp(PsychopathPanel, bpy.types.Panel):
if ob.data.psychopath.color_type == 'Rec709':
col.prop(ob.data, "color")
elif ob.data.psychopath.color_type == 'Blackbody':
elif ob.data.psychopath.color_type == 'Blackbody' or ob.data.psychopath.color_type == 'ColorTemperature':
col.prop(ob.data.psychopath, "color_blackbody_temp")
col.prop(ob.data, "energy")
@ -254,7 +254,7 @@ class MATERIAL_PT_psychopath_surface(PsychopathPanel, bpy.types.Panel):
col.prop(mat.psychopath, "color_type")
if mat.psychopath.color_type == 'Rec709':
col.prop(mat.psychopath, "color")
elif mat.psychopath.color_type == 'Blackbody':
elif mat.psychopath.color_type == 'Blackbody' or mat.psychopath.color_type == 'ColorTemperature':
col.prop(mat.psychopath, "color_blackbody_temp")
if mat.psychopath.surface_shader_type == 'GTR':

View File

@ -137,6 +137,8 @@ class DistantDiskLamp:
self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'ColorTemperature':
self.time_col += [('ColorTemperature', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)]
self.time_rad += [self.ob.data.shadow_soft_size]
@ -151,6 +153,8 @@ class DistantDiskLamp:
w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2]))
elif col[0] == 'Blackbody':
w.write("Color [blackbody, %f %f]\n" % (col[1], col[2]))
elif col[0] == 'ColorTemperature':
w.write("Color [color_temperature, %f %f]\n" % (col[1], col[2]))
for rad in self.time_rad:
w.write("Radius [%f]\n" % rad)

View File

@ -46,6 +46,12 @@ pub enum Color {
temperature: f32, // In kelvin
factor: f32, // Brightness multiplier
},
// Same as Blackbody except with the spectrum's energy roughly
// normalized.
Temperature {
temperature: f32, // In kelvin
factor: f32, // Brightness multiplier
},
}
impl Color {
@ -62,6 +68,14 @@ impl Color {
}
}
#[inline(always)]
pub fn new_temperature(temp: f32, fac: f32) -> Self {
Color::Temperature {
temperature: temp,
factor: fac,
}
}
pub fn to_spectral_sample(self, hero_wavelength: f32) -> SpectralSample {
let wls = wavelengths(hero_wavelength);
match self {
@ -84,6 +98,21 @@ impl Color {
hero_wavelength,
)
}
Color::Temperature {
temperature,
factor,
} => {
SpectralSample::from_parts(
// TODO: make this SIMD
Float4::new(
plancks_law_normalized(temperature, wls.get_0()) * factor,
plancks_law_normalized(temperature, wls.get_1()) * factor,
plancks_law_normalized(temperature, wls.get_2()) * factor,
plancks_law_normalized(temperature, wls.get_3()) * factor,
),
hero_wavelength,
)
}
}
}
@ -91,10 +120,19 @@ impl Color {
///
/// Note: this really is very _approximate_.
pub fn approximate_energy(self) -> f32 {
// TODO: better approximation for Blackbody.
// TODO: better approximation for Blackbody and Temperature.
match self {
Color::XYZ(_, y, _) => y,
Color::Blackbody { factor, .. } => factor,
Color::Blackbody {
temperature,
factor,
} => {
let t2 = temperature * temperature;
t2 * t2 * factor
}
Color::Temperature { factor, .. } => factor,
}
}
}
@ -105,6 +143,7 @@ impl Mul<f32> for Color {
fn mul(self, rhs: f32) -> Self {
match self {
Color::XYZ(x, y, z) => Color::XYZ(x * rhs, y * rhs, z * rhs),
Color::Blackbody {
temperature,
factor,
@ -112,6 +151,14 @@ impl Mul<f32> for Color {
temperature: temperature,
factor: factor * rhs,
},
Color::Temperature {
temperature,
factor,
} => Color::Temperature {
temperature: temperature,
factor: factor * rhs,
},
}
}
}
@ -140,6 +187,7 @@ impl Lerp for Color {
(y1 * inv_alpha) + (y2 * alpha),
(z1 * inv_alpha) + (z2 * alpha),
),
(
Color::Blackbody {
temperature: tmp1,
@ -153,6 +201,21 @@ impl Lerp for Color {
temperature: (tmp1 * inv_alpha) + (tmp2 * alpha),
factor: (fac1 * inv_alpha) + (fac2 * alpha),
},
(
Color::Temperature {
temperature: tmp1,
factor: fac1,
},
Color::Temperature {
temperature: tmp2,
factor: fac2,
},
) => Color::Temperature {
temperature: (tmp1 * inv_alpha) + (tmp2 * alpha),
factor: (fac1 * inv_alpha) + (fac2 * alpha),
},
_ => panic!("Cannot lerp colors with different representations."),
}
}
@ -199,6 +262,14 @@ fn plancks_law(temperature: f32, wavelength: f32) -> f32 {
(energy * 1.0e-6).max(0.0)
}
/// Same as above, except normalized to keep roughly equal spectral
/// energy across temperatures. This makes it easier to use for
/// choosing colors without making brightness explode.
fn plancks_law_normalized(temperature: f32, wavelength: f32) -> f32 {
let t2 = temperature * temperature;
plancks_law(temperature, wavelength) * 4.0e7 / (t2 * t2)
}
//----------------------------------------------------------------
#[derive(Copy, Clone, Debug)]

View File

@ -606,6 +606,16 @@ pub fn parse_color(contents: &str) -> Result<Color, PsyParseError> {
}
}
"color_temperature" => {
if let IResult::Done(_, (temperature, factor)) =
closure!(tuple!(ws_f32, ws_f32))(items[1].as_bytes())
{
return Ok(Color::new_temperature(temperature, factor));
} else {
return Err(PsyParseError::UnknownError(0));
}
}
_ => return Err(PsyParseError::UnknownError(0)),
}
}