Blackbody colors are now parsed, and PsychoBlend has support as well.

This commit is contained in:
Nathan Vegdahl 2018-12-28 00:23:02 -08:00
parent caa4ea3e44
commit 53754b956c
7 changed files with 161 additions and 74 deletions

View File

@ -77,6 +77,19 @@ class PsychopathCamera(bpy.types.PropertyGroup):
min=0.0, max=10000.0, soft_min=0.0, soft_max=2.0, default=0.0 min=0.0, max=10000.0, soft_min=0.0, soft_max=2.0, default=0.0
) )
# Psychopath material
class PsychopathLight(bpy.types.PropertyGroup):
color_type = EnumProperty(
name="Color Type", description="",
items=[('Rec709', 'Rec709', ""), ('Blackbody', 'Blackbody', "")],
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
)
# Custom Mesh properties # Custom Mesh properties
class PsychopathMesh(bpy.types.PropertyGroup): class PsychopathMesh(bpy.types.PropertyGroup):
is_subdivision_surface = BoolProperty( is_subdivision_surface = BoolProperty(
@ -88,10 +101,16 @@ class PsychopathMesh(bpy.types.PropertyGroup):
class PsychopathMaterial(bpy.types.PropertyGroup): class PsychopathMaterial(bpy.types.PropertyGroup):
surface_shader_type = EnumProperty( surface_shader_type = EnumProperty(
name="Surface Shader Type", description="", name="Surface Shader Type", description="",
items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GTR', 'GTR', ""), ('GGX', 'GGX', "")], items=[('Emit', 'Emit', ""), ('Lambert', 'Lambert', ""), ('GGX', 'GGX', "")],
default="Lambert" default="Lambert"
) )
color_type = EnumProperty(
name="Color Type", description="",
items=[('Rec709', 'Rec709', ""), ('Blackbody', 'Blackbody', "")],
default="Rec709"
)
color = FloatVectorProperty( color = FloatVectorProperty(
name="Color", description="", name="Color", description="",
subtype='COLOR', subtype='COLOR',
@ -99,6 +118,11 @@ class PsychopathMaterial(bpy.types.PropertyGroup):
default=[0.8,0.8,0.8] default=[0.8,0.8,0.8]
) )
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
)
roughness = FloatProperty( roughness = FloatProperty(
name="Roughness", description="", name="Roughness", description="",
min=-1.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.1 min=-1.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.1
@ -135,10 +159,12 @@ def register():
bpy.utils.register_class(PsychopathPreferences) bpy.utils.register_class(PsychopathPreferences)
bpy.utils.register_class(RenderPsychopathSettingsScene) bpy.utils.register_class(RenderPsychopathSettingsScene)
bpy.utils.register_class(PsychopathCamera) bpy.utils.register_class(PsychopathCamera)
bpy.utils.register_class(PsychopathLight)
bpy.utils.register_class(PsychopathMesh) bpy.utils.register_class(PsychopathMesh)
bpy.utils.register_class(PsychopathMaterial) bpy.utils.register_class(PsychopathMaterial)
bpy.types.Scene.psychopath = PointerProperty(type=RenderPsychopathSettingsScene) bpy.types.Scene.psychopath = PointerProperty(type=RenderPsychopathSettingsScene)
bpy.types.Camera.psychopath = PointerProperty(type=PsychopathCamera) bpy.types.Camera.psychopath = PointerProperty(type=PsychopathCamera)
bpy.types.Lamp.psychopath = PointerProperty(type=PsychopathLight)
bpy.types.Mesh.psychopath = PointerProperty(type=PsychopathMesh) bpy.types.Mesh.psychopath = PointerProperty(type=PsychopathMesh)
bpy.types.Material.psychopath = PointerProperty(type=PsychopathMaterial) bpy.types.Material.psychopath = PointerProperty(type=PsychopathMaterial)
render.register() render.register()
@ -149,10 +175,12 @@ def unregister():
bpy.utils.unregister_class(PsychopathPreferences) bpy.utils.unregister_class(PsychopathPreferences)
bpy.utils.unregister_class(RenderPsychopathSettingsScene) bpy.utils.unregister_class(RenderPsychopathSettingsScene)
bpy.utils.unregister_class(PsychopathCamera) bpy.utils.unregister_class(PsychopathCamera)
bpy.utils.unregister_class(PsychopathLight)
bpy.utils.unregister_class(PsychopathMesh) bpy.utils.unregister_class(PsychopathMesh)
bpy.utils.unregister_class(PsychopathMaterial) bpy.utils.unregister_class(PsychopathMaterial)
del bpy.types.Scene.psychopath del bpy.types.Scene.psychopath
del bpy.types.Camera.psychopath del bpy.types.Camera.psychopath
del bpy.types.Lamp.psychopath
del bpy.types.Mesh.psychopath del bpy.types.Mesh.psychopath
del bpy.types.Material.psychopath del bpy.types.Material.psychopath
render.unregister() render.unregister()

View File

@ -213,7 +213,12 @@ class SphereLamp:
def take_sample(self, render_engine, scene, time): def take_sample(self, render_engine, scene, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time)) render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
self.time_col += [self.ob.data.color * self.ob.data.energy]
if self.ob.data.psychopath.color_type == 'Rec709':
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)]
self.time_rad += [self.ob.data.shadow_soft_size] self.time_rad += [self.ob.data.shadow_soft_size]
def cleanup(self): def cleanup(self):
@ -225,7 +230,10 @@ class SphereLamp:
w.write("SphereLight $%s {\n" % self.name) w.write("SphereLight $%s {\n" % self.name)
w.indent() w.indent()
for col in self.time_col: for col in self.time_col:
w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2])) if col[0] == 'Rec709':
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]))
for rad in self.time_rad: for rad in self.time_rad:
w.write("Radius [%f]\n" % rad) w.write("Radius [%f]\n" % rad)
@ -244,7 +252,12 @@ class RectLamp:
def take_sample(self, render_engine, scene, time): def take_sample(self, render_engine, scene, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time)) render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
self.time_col += [self.ob.data.color * self.ob.data.energy]
if self.ob.data.psychopath.color_type == 'Rec709':
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)]
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)]
else: else:
@ -259,7 +272,10 @@ class RectLamp:
w.write("RectangleLight $%s {\n" % self.name) w.write("RectangleLight $%s {\n" % self.name)
w.indent() w.indent()
for col in self.time_col: for col in self.time_col:
w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2])) if col[0] == 'Rec709':
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]))
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)
@ -314,23 +330,40 @@ class Material:
w.indent() w.indent()
if self.mat.psychopath.surface_shader_type == 'Emit': if self.mat.psychopath.surface_shader_type == 'Emit':
w.write("Type [Emit]\n") w.write("Type [Emit]\n")
color = self.mat.psychopath.color if self.mat.psychopath.color_type == 'Rec709':
w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) col = self.mat.psychopath.color
w.write("Color [rec709, %f %f %f]\n" % (
col[0], col[1], col[2],
))
elif self.mat.psychopath.color_type == 'Blackbody':
w.write("Color [blackbody, %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")
color = self.mat.psychopath.color if self.mat.psychopath.color_type == 'Rec709':
w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) col = self.mat.psychopath.color
elif self.mat.psychopath.surface_shader_type == 'GTR': w.write("Color [rec709, %f %f %f]\n" % (
w.write("Type [GTR]\n") col[0], col[1], col[2],
color = self.mat.psychopath.color ))
w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) elif self.mat.psychopath.color_type == 'Blackbody':
w.write("Roughness [%f]\n" % self.mat.psychopath.roughness) w.write("Color [blackbody, %f %f]\n" % (
w.write("TailShape [%f]\n" % self.mat.psychopath.tail_shape) self.mat.psychopath.color_blackbody_temp,
w.write("Fresnel [%f]\n" % self.mat.psychopath.fresnel) 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")
color = self.mat.psychopath.color if self.mat.psychopath.color_type == 'Rec709':
w.write("Color [%f %f %f]\n" % (color[0], color[1], color[2])) col = self.mat.psychopath.color
w.write("Color [rec709, %f %f %f]\n" % (
col[0], col[1], col[2],
))
elif self.mat.psychopath.color_type == 'Blackbody':
w.write("Color [blackbody, %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

@ -125,7 +125,14 @@ class DATA_PT_psychopath_lamp(PsychopathPanel, bpy.types.Panel):
if ob.data.type != 'HEMI' and ob.data.type != 'AREA': if ob.data.type != 'HEMI' and ob.data.type != 'AREA':
col.prop(ob.data, "shadow_soft_size") col.prop(ob.data, "shadow_soft_size")
col.prop(ob.data.psychopath, "color_type")
if ob.data.psychopath.color_type == 'Rec709':
col.prop(ob.data, "color") col.prop(ob.data, "color")
elif ob.data.psychopath.color_type == 'Blackbody':
col.prop(ob.data.psychopath, "color_blackbody_temp")
col.prop(ob.data, "energy") col.prop(ob.data, "energy")
@ -239,10 +246,16 @@ class MATERIAL_PT_psychopath_surface(PsychopathPanel, bpy.types.Panel):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
col = layout.column()
mat = context.material mat = context.material
layout.prop(mat.psychopath, "surface_shader_type") col.prop(mat.psychopath, "surface_shader_type")
layout.prop(mat.psychopath, "color")
col.prop(mat.psychopath, "color_type")
if mat.psychopath.color_type == 'Rec709':
col.prop(mat.psychopath, "color")
elif mat.psychopath.color_type == 'Blackbody':
col.prop(mat.psychopath, "color_blackbody_temp")
if mat.psychopath.surface_shader_type == 'GTR': if mat.psychopath.surface_shader_type == 'GTR':
layout.prop(mat.psychopath, "roughness") layout.prop(mat.psychopath, "roughness")

View File

@ -116,7 +116,7 @@ class BackgroundShader:
w.write("BackgroundShader {\n") w.write("BackgroundShader {\n")
w.indent(); w.indent();
w.write("Type [Color]\n") w.write("Type [Color]\n")
w.write("Color [%f %f %f]\n" % self.color) w.write("Color [rec709, %f %f %f]\n" % self.color)
w.unindent() w.unindent()
w.write("}\n") w.write("}\n")
@ -132,7 +132,12 @@ class DistantDiskLamp:
def take_sample(self, render_engine, scene, time): def take_sample(self, render_engine, scene, time):
render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time)) render_engine.update_stats("", "Psychopath: Collecting '{}' at time {}".format(self.ob.name, time))
self.time_dir += [tuple(self.ob.matrix_world.to_3x3() * Vector((0, 0, -1)))] self.time_dir += [tuple(self.ob.matrix_world.to_3x3() * Vector((0, 0, -1)))]
self.time_col += [self.ob.data.color * self.ob.data.energy]
if self.ob.data.psychopath.color_type == 'Rec709':
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)]
self.time_rad += [self.ob.data.shadow_soft_size] self.time_rad += [self.ob.data.shadow_soft_size]
def export(self, render_engine, w): def export(self, render_engine, w):
@ -142,7 +147,10 @@ class DistantDiskLamp:
for direc in self.time_dir: for direc in self.time_dir:
w.write("Direction [%f %f %f]\n" % (direc[0], direc[1], direc[2])) w.write("Direction [%f %f %f]\n" % (direc[0], direc[1], direc[2]))
for col in self.time_col: for col in self.time_col:
w.write("Color [%f %f %f]\n" % (col[0], col[1], col[2])) if col[0] == 'Rec709':
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]))
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

@ -499,12 +499,8 @@ fn parse_world<'a>(arena: &'a MemArena, tree: &'a DataTree) -> Result<World<'a>,
.. ..
}) = bgs.iter_children_with_type("Color").nth(0) }) = bgs.iter_children_with_type("Color").nth(0)
{ {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.trim().as_bytes()) background_color = color;
{
// TODO: proper color space management, not just assuming
// rec.709.
background_color = Color::new_xyz(rec709_e_to_xyz(color));
} else { } else {
return Err(PsyParseError::IncorrectLeafData( return Err(PsyParseError::IncorrectLeafData(
byte_offset, byte_offset,
@ -582,3 +578,34 @@ pub fn make_transform_format_error(byte_offset: usize) -> PsyParseError {
the form '[# # # # # # # # # # # # # # # #]'.", the form '[# # # # # # # # # # # # # # # #]'.",
) )
} }
pub fn parse_color(contents: &str) -> Result<Color, PsyParseError> {
let items: Vec<_> = contents.split(",").map(|s| s.trim()).collect();
if items.len() != 2 {
return Err(PsyParseError::UnknownError(0));
}
match items[0] {
"rec709" => {
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(items[1].as_bytes())
{
return Ok(Color::new_xyz(rec709_e_to_xyz(color)));
} else {
return Err(PsyParseError::UnknownError(0));
}
}
"blackbody" => {
if let IResult::Done(_, (temperature, factor)) =
closure!(tuple!(ws_f32, ws_f32))(items[1].as_bytes())
{
return Ok(Color::new_blackbody(temperature, factor));
} else {
return Err(PsyParseError::UnknownError(0));
}
}
_ => return Err(PsyParseError::UnknownError(0)),
}
}

View File

@ -12,7 +12,11 @@ use crate::{
math::Vector, math::Vector,
}; };
use super::{basics::ws_f32, psy::PsyParseError, DataTree}; use super::{
basics::ws_f32,
psy::{parse_color, PsyParseError},
DataTree,
};
pub fn parse_distant_disk_light<'a>( pub fn parse_distant_disk_light<'a>(
arena: &'a MemArena, arena: &'a MemArena,
@ -62,13 +66,8 @@ pub fn parse_distant_disk_light<'a>(
contents, contents,
byte_offset, byte_offset,
} if type_name == "Color" => { } if type_name == "Color" => {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) colors.push(color);
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -116,13 +115,8 @@ pub fn parse_sphere_light<'a>(
contents, contents,
byte_offset, byte_offset,
} if type_name == "Color" => { } if type_name == "Color" => {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) colors.push(color);
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -172,13 +166,8 @@ pub fn parse_rectangle_light<'a>(
contents, contents,
byte_offset, byte_offset,
} if type_name == "Color" => { } if type_name == "Color" => {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) colors.push(color);
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
colors.push(Color::new_xyz(rec709_e_to_xyz(color)));
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));

View File

@ -11,7 +11,11 @@ use crate::{
shading::{SimpleSurfaceShader, SurfaceShader}, shading::{SimpleSurfaceShader, SurfaceShader},
}; };
use super::{basics::ws_f32, psy::PsyParseError, DataTree}; use super::{
basics::ws_f32,
psy::{parse_color, PsyParseError},
DataTree,
};
// pub struct TriangleMesh { // pub struct TriangleMesh {
// time_samples: usize, // time_samples: usize,
@ -38,13 +42,8 @@ pub fn parse_surface_shader<'a>(
let color = if let Some((_, contents, byte_offset)) = let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0) tree.iter_leaf_children_with_type("Color").nth(0)
{ {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) color
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -64,13 +63,8 @@ pub fn parse_surface_shader<'a>(
let color = if let Some((_, contents, byte_offset)) = let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0) tree.iter_leaf_children_with_type("Color").nth(0)
{ {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) color
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));
@ -125,13 +119,8 @@ pub fn parse_surface_shader<'a>(
let color = if let Some((_, contents, byte_offset)) = let color = if let Some((_, contents, byte_offset)) =
tree.iter_leaf_children_with_type("Color").nth(0) tree.iter_leaf_children_with_type("Color").nth(0)
{ {
if let IResult::Done(_, color) = if let Ok(color) = parse_color(contents) {
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) color
{
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
Color::new_xyz(rec709_e_to_xyz(color))
} else { } else {
// Found color, but its contents is not in the right format // Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError(byte_offset)); return Err(PsyParseError::UnknownError(byte_offset));