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): class PsychopathLight(bpy.types.PropertyGroup):
color_type = EnumProperty( color_type = EnumProperty(
name="Color Type", description="", 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" default="Rec709"
) )
color_blackbody_temp = FloatProperty( color_blackbody_temp = FloatProperty(
name="Temperature", description="Blackbody temperature in kelvin", 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 # Custom Mesh properties
@ -107,7 +111,11 @@ class PsychopathMaterial(bpy.types.PropertyGroup):
color_type = EnumProperty( color_type = EnumProperty(
name="Color Type", description="", 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" default="Rec709"
) )
@ -120,7 +128,7 @@ class PsychopathMaterial(bpy.types.PropertyGroup):
color_blackbody_temp = FloatProperty( color_blackbody_temp = FloatProperty(
name="Temperature", description="Blackbody temperature in kelvin", 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( roughness = FloatProperty(

View File

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

View File

@ -130,7 +130,7 @@ class DATA_PT_psychopath_lamp(PsychopathPanel, bpy.types.Panel):
if ob.data.psychopath.color_type == 'Rec709': if ob.data.psychopath.color_type == 'Rec709':
col.prop(ob.data, "color") 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.psychopath, "color_blackbody_temp")
col.prop(ob.data, "energy") col.prop(ob.data, "energy")
@ -254,7 +254,7 @@ class MATERIAL_PT_psychopath_surface(PsychopathPanel, bpy.types.Panel):
col.prop(mat.psychopath, "color_type") col.prop(mat.psychopath, "color_type")
if mat.psychopath.color_type == 'Rec709': if mat.psychopath.color_type == 'Rec709':
col.prop(mat.psychopath, "color") 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") col.prop(mat.psychopath, "color_blackbody_temp")
if mat.psychopath.surface_shader_type == 'GTR': 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)] self.time_col += [('Rec709', self.ob.data.color * self.ob.data.energy)]
elif self.ob.data.psychopath.color_type == 'Blackbody': elif self.ob.data.psychopath.color_type == 'Blackbody':
self.time_col += [('Blackbody', self.ob.data.psychopath.color_blackbody_temp, self.ob.data.energy)] 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] 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])) w.write("Color [rec709, %f %f %f]\n" % (col[1][0], col[1][1], col[1][2]))
elif col[0] == 'Blackbody': elif col[0] == 'Blackbody':
w.write("Color [blackbody, %f %f]\n" % (col[1], col[2])) 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: for rad in self.time_rad:
w.write("Radius [%f]\n" % rad) w.write("Radius [%f]\n" % rad)

View File

@ -46,6 +46,12 @@ pub enum Color {
temperature: f32, // In kelvin temperature: f32, // In kelvin
factor: f32, // Brightness multiplier 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 { 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 { pub fn to_spectral_sample(self, hero_wavelength: f32) -> SpectralSample {
let wls = wavelengths(hero_wavelength); let wls = wavelengths(hero_wavelength);
match self { match self {
@ -84,6 +98,21 @@ impl Color {
hero_wavelength, 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_. /// Note: this really is very _approximate_.
pub fn approximate_energy(self) -> f32 { pub fn approximate_energy(self) -> f32 {
// TODO: better approximation for Blackbody. // TODO: better approximation for Blackbody and Temperature.
match self { match self {
Color::XYZ(_, y, _) => y, 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 { fn mul(self, rhs: f32) -> Self {
match self { match self {
Color::XYZ(x, y, z) => Color::XYZ(x * rhs, y * rhs, z * rhs), Color::XYZ(x, y, z) => Color::XYZ(x * rhs, y * rhs, z * rhs),
Color::Blackbody { Color::Blackbody {
temperature, temperature,
factor, factor,
@ -112,6 +151,14 @@ impl Mul<f32> for Color {
temperature: temperature, temperature: temperature,
factor: factor * rhs, 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), (y1 * inv_alpha) + (y2 * alpha),
(z1 * inv_alpha) + (z2 * alpha), (z1 * inv_alpha) + (z2 * alpha),
), ),
( (
Color::Blackbody { Color::Blackbody {
temperature: tmp1, temperature: tmp1,
@ -153,6 +201,21 @@ impl Lerp for Color {
temperature: (tmp1 * inv_alpha) + (tmp2 * alpha), temperature: (tmp1 * inv_alpha) + (tmp2 * alpha),
factor: (fac1 * inv_alpha) + (fac2 * 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."), _ => 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) (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)] #[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)), _ => return Err(PsyParseError::UnknownError(0)),
} }
} }