Permalink
Browse files

properly implemented dispersion support (allowing textured IOR)

  • Loading branch information...
Theverat committed Jan 3, 2018
1 parent 94913e4 commit d3fed046ab62e18226e410b42a16ca1bccefb530
Showing with 69 additions and 25 deletions.
  1. +2 −1 bin/.gitignore
  2. +47 −22 nodes/materials/glass.py
  3. +12 −1 nodes/sockets.py
  4. +6 −0 utils/node.py
  5. +2 −1 utils/render.py
@@ -1,3 +1,4 @@
*.so*
*.dll
*.pyd
*.pyd
luxcoreui
@@ -2,6 +2,8 @@
from bpy.props import FloatProperty, BoolProperty
from .. import LuxCoreNodeMaterial, Roughness
from ..output import get_active_output
from .. import utils
from ...utils import node as utils_node
class LuxCoreNodeMatGlass(LuxCoreNodeMaterial):
@@ -19,14 +21,12 @@ class LuxCoreNodeMatGlass(LuxCoreNodeMaterial):
architectural = BoolProperty(name="Architectural",
description="Skips refraction during transmission, propagates alpha and shadow rays",
default=False)
ior = FloatProperty(name="IOR", default=1.5, min=1, soft_max=6, description="Index of refraction")
dispersion = FloatProperty(name="Dispersion", default=0, min=0, soft_max=0.1, step=0.1, precision=3)
def init(self, context):
self.add_input("LuxCoreSocketColor", "Transmission Color", (1, 1, 1))
self.add_input("LuxCoreSocketColor", "Reflection Color", (1, 1, 1))
# We use a property instead to disallow textured IOR for now
# self.add_input("LuxCoreSocketIOR", "IOR", 1.5)
self.add_input("LuxCoreSocketIOR", "IOR", 1.5)
Roughness.init(self, default=0.05, init_enabled=False)
self.add_common_inputs()
@@ -46,12 +46,10 @@ def draw_buttons(self, context, layout):
row.enabled = not self.rough
row.prop(self, "architectural")
if self._has_interior_volume():
layout.prop(self, "dispersion")
if self.has_interior_volume():
layout.label("Using IOR of interior volume", icon="INFO")
else:
col = layout.column(align=True)
col.prop(self, "ior")
col.prop(self, "dispersion")
def export(self, props, luxcore_name=None):
if self.rough:
@@ -65,32 +63,59 @@ def export(self, props, luxcore_name=None):
"type": type,
"kt": self.inputs["Transmission Color"].export(props),
"kr": self.inputs["Reflection Color"].export(props),
"dispersion": self.dispersion,
"dispersion": self.dispersion > 0,
}
# Use IOR and dispersion only if there is no interior volume linked to the output
if not self._has_interior_volume():
if self.dispersion > 0:
# Use an RGB IOR: three different refractive indices for red, green and blue
interior_vol = self.has_interior_volume()
if interior_vol:
out = get_active_output(interior_vol, "LuxCoreNodeVolOutput")
vol_node = utils_node.get_linked_node(out.inputs["Volume"])
if vol_node:
ior = vol_node.inputs["IOR"].export(props)
is_textured_ior = vol_node.inputs["IOR"].is_linked
else:
raise Exception("ERROR in glass node export: can't find interior volume IOR")
else:
ior = self.inputs["IOR"].export(props)
is_textured_ior = self.inputs["IOR"].is_linked
if self.dispersion > 0:
# TODO: maybe we should scale the spread with IOR? (higher IOR, higher spread - and at IOR 1 a spread of 0)
if is_textured_ior:
# Create helper texture with unique name
node_tree = self.id_data
name_parts = ["dispersion", node_tree.name, self.name]
helper_name = utils.to_luxcore_name("_".join(name_parts))
helper_prefix = "scene.textures." + helper_name + "."
helper_defs = {
"type": "add",
"texture1": ior,
"texture2": [-self.dispersion, 0, self.dispersion]
}
props.Set(utils.create_props(helper_prefix, helper_defs))
definitions["interiorior"] = helper_name
else:
# IOR is not textured, just a simple value
# Prevent IOR below 1 (would lead to weird results)
# TODO: maybe we should scale the spread with IOR? (higher IOR, higher spread - and at IOR 1 a spread of 0)
red = max(self.ior - self.dispersion, 1.000001)
green = self.ior
blue = self.ior + self.dispersion
red = max(ior - self.dispersion, 1.000001)

This comment has been minimized.

@neo2068

neo2068 Jan 3, 2018

Member

I think, the three IOR values need to be for defined wavelengths, i.e. 645, 510, 440 nm (https://github.com/LuxCoreRender/LuxCore/blob/709b3787197bb0928f5cf79f9181d6f61f15b93b/include/slg/materials/materialdefs_funcs_glass.cl#L100). We should define the dispersion with a cauchy coefficient (or sellmeier) and calculate the IOR for these wavelengths.

This comment has been minimized.

@Theverat

Theverat Jan 3, 2018

Member

If you ask me, I think this should be handled internally by LuxCore.
LuxCore should expose a dispersion or cauchy or sellmeier property.

This comment has been minimized.

@Dade916

Dade916 Jan 3, 2018

Member

It is not possible to have both (without an additional option):

  • or I support the current one, reading the IOR at 645, 510, 440 nm from texture R, G and B values;

  • or I support the cauchy's coefficient (https://en.wikipedia.org/wiki/Cauchy%27s_equation), reading the base IOR from R and the cauchy's coefficient from G (B channel would be useless in this case).

I guess I will have to add an additional glass properties to define how the RGB texture have to be interpreted.

This comment has been minimized.

@Theverat

Theverat Jan 3, 2018

Member

Couldn't you add two properties cauchy_b and cauchy_c in addition to interiorior and use them to replace interiorior if they are specified (or when dispersion == True)?

This comment has been minimized.

@Dade916

Dade916 Jan 3, 2018

Member

IORs are also textures of the inner and outer volumes so I would have to add the new parameters to Volumes too. It would be a bit annoying.

The best solution would be to have the sampled wave length as parameter of a (special type of) textures so we could have a cauchy texture, artistic dispersion texture and IORs dedicated textures in general. We already have a special subclass of textures: fresnel textures.
But this i solution requires a not trivial amount of work (OpenCL version, as usual, is the real pain for this kind of stuff).

This comment has been minimized.

@Theverat

Theverat Jan 3, 2018

Member

Considering the probably low amount of users that are going to need a correct physically based cauchy dispersion, I think it's best to create an issue for this topic as Todo and then leave it at that for now.

This comment has been minimized.

@Dade916

Dade916 Jan 3, 2018

Member

I'm inclined to agree but I will try first this solution:

  • glass.dispersion is removed;

  • glass.cauchyc is a new float texture parameter. Dispersion is enabled only if it is defined. Cauchy's C parameter is defined by glass.cauchyc texture while the base IOR (cauchy's B parameter) is coming from the volume or material IOR as was before.

if it doesn't feel user friendly, we can go back to the previous solution (but cauchy's C parameter should feel quite similar to the current BlendLuxCore "dispersion strength" parameter).

This comment has been minimized.

@Theverat

Theverat Jan 3, 2018

Member

That sounds good. I wasn't sure if cauchy's B parameter really was the base IOR.

This comment has been minimized.

@neo2068

neo2068 Jan 3, 2018

Member

It isn't exactly the same. The IOR is measured at 589 nm (natrium D line) and B and C are the coefficients from the fitting of the equation IOR = B + C/lambda^2 on the measured data of the IOR curve, e.g. BK7 glass: IOR=1.5167, B=1.5046, C=0,00420. But the B coefficient can be computed from the IOR: B = IOR -C/(589/1000)^2.

green = ior
blue = ior + self.dispersion
definitions["interiorior"] = [red, green, blue]
definitions["dispersion"] = True
else:
definitions["interiorior"] = self.ior
definitions["dispersion"] = False
else:
definitions["interiorior"] = ior
if self.rough:
Roughness.export(self, props, definitions)
self.export_common_inputs(props, definitions)
return self.base_export(props, definitions, luxcore_name)
def _has_interior_volume(self):
def has_interior_volume(self):
node_tree = self.id_data
active_output = get_active_output(node_tree, "LuxCoreNodeMatOutput")
if active_output:
return active_output.interior_volume is not None
return active_output.interior_volume
return False
@@ -81,6 +81,7 @@ class LuxCoreSocketVolume(LuxCoreNodeSocket):
color = Color.volume
# no default value
class LuxCoreSocketFresnel(LuxCoreNodeSocket):
color = Color.fresnel_texture
# no default value
@@ -125,8 +126,10 @@ def export_default(self):
class LuxCoreSocketFloatPositive(LuxCoreSocketFloat):
default_value = FloatProperty(min=0)
class LuxCoreSocketValueAtDepth(LuxCoreSocketFloat):
default_value = FloatProperty(min=0,subtype='DISTANCE',unit='LENGTH',precision=5)
default_value = FloatProperty(min=0, subtype='DISTANCE', unit='LENGTH', precision=5)
class LuxCoreSocketFloat0to1(LuxCoreSocketFloat):
default_value = FloatProperty(min=0, max=1)
@@ -142,6 +145,14 @@ class LuxCoreSocketRoughness(LuxCoreSocketFloat):
class LuxCoreSocketIOR(LuxCoreSocketFloat):
default_value = FloatProperty(min=0, max=25, description=IOR_DESCRIPTION)
def draw(self, context, layout, node, text):
if hasattr(node, "has_interior_volume") and node.has_interior_volume():
# This socket is used on a glass node and is not exported because
# the settings of the attached interior volume are used instead.
layout.active = False
super().draw(context, layout, node, text)
class LuxCoreSocketFloatVector(LuxCoreSocketFloat):
default_value = FloatVectorProperty()
@@ -28,3 +28,9 @@ def export_material_input(input, props):
props.Set(pyluxcore.Property("scene.materials.%s.type" % luxcore_name, "matte"))
props.Set(pyluxcore.Property("scene.materials.%s.kd" % luxcore_name, [0, 0, 0]))
return luxcore_name
def get_linked_node(socket):
if not socket.is_linked:
return None
return socket.links[0].from_node
@@ -106,7 +106,8 @@ def get_pretty_stats(config, stats, halt):
# Engine + Sampler
engine = config.GetProperties().Get("renderengine.type").GetString()
sampler = config.GetProperties().Get("sampler.type").GetString()
pretty.append(engine_to_str[engine] + " + " + sampler_to_str[sampler])
if engine in engine_to_str and sampler in sampler_to_str:
pretty.append(engine_to_str[engine] + " + " + sampler_to_str[sampler])
# Triangle count
triangle_count = stats.Get("stats.dataset.trianglecount").GetInt()

0 comments on commit d3fed04

Please sign in to comment.