Cycles: Add Support for IES files as textures for light strength
authorLukas Stockner <lukas.stockner@freenet.de>
Sat, 26 May 2018 22:46:37 +0000 (00:46 +0200)
committerLukas Stockner <lukas.stockner@freenet.de>
Sat, 26 May 2018 23:24:57 +0000 (01:24 +0200)
This patch adds support for IES files, a file format that is commonly used to store the directional intensity distribution of light sources.
The new IES node is supposed to be plugged into the Strength input of the Emission node of the lamp.

Since people generating IES files do not really seem to care about the standard, the parser is flexible enough to accept all test files I have tried.
Some common weirdnesses are distributing values over multiple lines that should go into one line, using commas instead of spaces as delimiters and adding various useless stuff at the end of the file.

The user interface of the node is similar to the script node, the user can either select an internal Text or load a file.
Internally, IES files are handled similar to Image textures: They are stored in slots by the LightManager and each unique IES is assigned to one slot.

The local coordinate system of the lamp is used, so that the direction of the light can be changed. For UI reasons, it's usually best to add an area light,
rotate it and then change its type, since especially the point light does not immediately show its local coordinate system in the viewport.

Reviewers: #cycles, dingto, sergey, brecht

Reviewed By: #cycles, dingto, brecht

Subscribers: OgDEV, crazyrobinhood, secundar, cardboard, pisuke, intrah, swerner, micah_denn, harvester, gottfried, disnel, campbellbarton, duarteframos, Lapineige, brecht, juicyfruit, dingto, marek, rickyblender, bliblubli, lockal, sergey

Differential Revision: https://developer.blender.org/D1543

39 files changed:
intern/cycles/blender/blender_shader.cpp
intern/cycles/blender/blender_util.h
intern/cycles/graph/node_type.h
intern/cycles/kernel/CMakeLists.txt
intern/cycles/kernel/kernel_light.h
intern/cycles/kernel/kernel_textures.h
intern/cycles/kernel/osl/osl_services.cpp
intern/cycles/kernel/shaders/CMakeLists.txt
intern/cycles/kernel/shaders/node_ies_light.osl [new file with mode: 0644]
intern/cycles/kernel/svm/svm.h
intern/cycles/kernel/svm/svm_ies.h [new file with mode: 0644]
intern/cycles/kernel/svm/svm_types.h
intern/cycles/render/graph.cpp
intern/cycles/render/light.cpp
intern/cycles/render/light.h
intern/cycles/render/nodes.cpp
intern/cycles/render/nodes.h
intern/cycles/render/osl.cpp
intern/cycles/render/osl.h
intern/cycles/render/scene.cpp
intern/cycles/render/scene.h
intern/cycles/render/svm.cpp
intern/cycles/render/svm.h
intern/cycles/util/CMakeLists.txt
intern/cycles/util/util_ies.cpp [new file with mode: 0644]
intern/cycles/util/util_ies.h [new file with mode: 0644]
intern/cycles/util/util_math.h
release/scripts/startup/nodeitems_builtins.py
source/blender/blenkernel/BKE_node.h
source/blender/blenkernel/intern/bpath.c
source/blender/blenkernel/intern/node.c
source/blender/editors/space_node/drawnode.c
source/blender/makesdna/DNA_node_types.h
source/blender/makesrna/RNA_access.h
source/blender/makesrna/intern/rna_nodetree.c
source/blender/nodes/CMakeLists.txt
source/blender/nodes/NOD_shader.h
source/blender/nodes/NOD_static_types.h
source/blender/nodes/shader/nodes/node_shader_ies_light.c [new file with mode: 0644]

index eb9968a85c206e28af80ea3ca51aae01bf31650f..c6144cef1bf846b9048f424a8607b9aeda4ae42a 100644 (file)
@@ -818,6 +818,19 @@ static ShaderNode *add_node(Scene *scene,
                get_tex_mapping(&sky->tex_mapping, b_texture_mapping);
                node = sky;
        }
+       else if(b_node.is_a(&RNA_ShaderNodeTexIES)) {
+               BL::ShaderNodeTexIES b_ies_node(b_node);
+               IESLightNode *ies = new IESLightNode();
+               switch(b_ies_node.mode()) {
+                       case BL::ShaderNodeTexIES::mode_EXTERNAL:
+                               ies->filename = blender_absolute_path(b_data, b_ntree, b_ies_node.filepath());
+                               break;
+                       case BL::ShaderNodeTexIES::mode_INTERNAL:
+                               ies->ies = get_text_datablock_content(b_ies_node.ies().ptr);
+                               break;
+               }
+               node = ies;
+       }
        else if(b_node.is_a(&RNA_ShaderNodeNormalMap)) {
                BL::ShaderNodeNormalMap b_normal_map_node(b_node);
                NormalMapNode *nmap = new NormalMapNode();
index c418b19a6373dfbcfcd57d498da28675fc960ccc..87d6c7eba8abbf9aeae7c16d8a354b0263ed84c0 100644 (file)
@@ -468,6 +468,21 @@ static inline string blender_absolute_path(BL::BlendData& b_data,
        return path;
 }
 
+static inline string get_text_datablock_content(const PointerRNA&& ptr)
+{
+       if(ptr.data == NULL) {
+               return "";
+       }
+
+       string content;
+       BL::Text::lines_iterator iter;
+       for(iter.begin(ptr); iter; ++iter) {
+               content += iter->body() + "\n";
+       }
+
+       return content;
+}
+
 /* Texture Space */
 
 static inline void mesh_texture_space(BL::Mesh& b_mesh,
index 7d46e31ce24d83f1d5188dfa62862c6f917809c8..15d34a79bb8445f7234094f42043f1ad3fb68e83 100644 (file)
@@ -73,12 +73,13 @@ struct SocketType
                INTERNAL               = (1 << 2) | (1 << 3),
 
                LINK_TEXTURE_GENERATED = (1 << 4),
-               LINK_TEXTURE_UV        = (1 << 5),
-               LINK_INCOMING          = (1 << 6),
-               LINK_NORMAL            = (1 << 7),
-               LINK_POSITION          = (1 << 8),
-               LINK_TANGENT           = (1 << 9),
-               DEFAULT_LINK_MASK      = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9)
+               LINK_TEXTURE_NORMAL    = (1 << 5),
+               LINK_TEXTURE_UV        = (1 << 6),
+               LINK_INCOMING          = (1 << 7),
+               LINK_NORMAL            = (1 << 8),
+               LINK_POSITION          = (1 << 9),
+               LINK_TANGENT           = (1 << 10),
+               DEFAULT_LINK_MASK      = (1 << 4) | (1 << 5) | (1 << 6) | (1 << 7) | (1 << 8) | (1 << 9) | (1 << 10)
        };
 
        ustring name;
index 9b7f4e00084e62c5e44f7eafa86801b89e6cf924..457602ae4e7a692f4de1f3b4d1cabcbf1cf833b7 100644 (file)
@@ -178,6 +178,7 @@ set(SRC_SVM_HEADERS
        svm/svm_geometry.h
        svm/svm_gradient.h
        svm/svm_hsv.h
+       svm/svm_ies.h
        svm/svm_image.h
        svm/svm_invert.h
        svm/svm_light_path.h
index efab69ee37dfdf29bd39f2da64051ba14e58b1ea..ec7203d36eb1a570a23bc8202e2e7d57a741dda1 100644 (file)
@@ -170,7 +170,7 @@ float3 background_map_sample(KernelGlobals *kg, float randu, float randv, float
        float2 cdf_last_v = kernel_tex_fetch(__light_background_marginal_cdf, res);
 
        /* importance-sampled V direction */
-       float dv = (randv - cdf_v.y) / (cdf_next_v.y - cdf_v.y);
+       float dv = inverse_lerp(cdf_v.y, cdf_next_v.y, randv);
        float v = (index_v + dv) / res;
 
        /* this is basically std::lower_bound as used by pbrt */
@@ -196,7 +196,7 @@ float3 background_map_sample(KernelGlobals *kg, float randu, float randv, float
        float2 cdf_last_u = kernel_tex_fetch(__light_background_conditional_cdf, index_v * cdf_count + res);
 
        /* importance-sampled U direction */
-       float du = (randu - cdf_u.y) / (cdf_next_u.y - cdf_u.y);
+       float du = inverse_lerp(cdf_u.y, cdf_next_u.y, randu);
        float u = (index_u + du) / res;
 
        /* compute pdf */
index 9047b93a0b2281cb5751b840541657f88e818776..a7b8c492ee96be978ac3b44f76fbf66000fad98b 100644 (file)
@@ -81,5 +81,8 @@ KERNEL_TEX(uint, __sobol_directions)
 /* image textures */
 KERNEL_TEX(TextureInfo, __texture_info)
 
+/* ies lights */
+KERNEL_TEX(float, __ies)
+
 #undef KERNEL_TEX
 
index 0c5e5e30e479168a3fc366fe9c4033a12f088451..32d86b7192a01b758031f5fb6bb0ba4b98a597cf 100644 (file)
@@ -956,9 +956,15 @@ bool OSLRenderServices::texture(ustring filename,
                                status = true;
                        }
                }
+               else if(filename[1] == 'l') {
+                       /* IES light. */
+                       int slot = atoi(filename.c_str() + 2);
+                       result[0] = kernel_ies_interp(kg, slot, s, t);
+                       status = true;
+               }
                else {
                        /* Packed texture. */
-                       int slot = atoi(filename.c_str() + 1);
+                       int slot = atoi(filename.c_str() + 2);
                        float4 rgba = kernel_tex_image_interp(kg, slot, s, 1.0f - t);
 
                        result[0] = rgba[0];
index 6ec651a96d88c11c3972e8c579594149d28170e3..b28d017c1c25a2c3aa615f832bb68e600ae66736 100644 (file)
@@ -39,6 +39,7 @@ set(SRC_OSL
        node_principled_volume.osl
        node_holdout.osl
        node_hsv.osl
+       node_ies_light.osl
        node_image_texture.osl
        node_invert.osl
        node_layer_weight.osl
diff --git a/intern/cycles/kernel/shaders/node_ies_light.osl b/intern/cycles/kernel/shaders/node_ies_light.osl
new file mode 100644 (file)
index 0000000..a0954e3
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2011-2015 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "stdosl.h"
+#include "node_texture.h"
+
+/* IES Light */
+
+shader node_ies_light(
+       int use_mapping = 0,
+       matrix mapping = matrix(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
+       int slot = 0,
+       float Strength = 1.0,
+       point Vector = I,
+       output float Fac = 0.0)
+{
+       point p = Vector;
+
+       if (use_mapping) {
+               p = transform(mapping, p);
+       }
+
+       p = normalize(p);
+
+       float v_angle = acos(-p[2]);
+       float h_angle = atan2(p[0], p[1]) + M_PI;
+
+       Fac = Strength * texture(format("@l%d", slot), h_angle, v_angle);
+}
index 39cd5da7b12df2b1c274f4fabbec432dbb6178af..bfa146f2d93ef53adccd32d99e98732786c5ba09 100644 (file)
@@ -157,6 +157,7 @@ CCL_NAMESPACE_END
 #include "kernel/svm/svm_camera.h"
 #include "kernel/svm/svm_geometry.h"
 #include "kernel/svm/svm_hsv.h"
+#include "kernel/svm/svm_ies.h"
 #include "kernel/svm/svm_image.h"
 #include "kernel/svm/svm_gamma.h"
 #include "kernel/svm/svm_brightness.h"
@@ -421,6 +422,9 @@ ccl_device_noinline void svm_eval_nodes(KernelGlobals *kg, ShaderData *sd, ccl_a
                        case NODE_LIGHT_FALLOFF:
                                svm_node_light_falloff(sd, stack, node);
                                break;
+                       case NODE_IES:
+                               svm_node_ies(kg, sd, stack, node, &offset);
+                               break;
 #  endif  /* __EXTRA_NODES__ */
 #endif  /* NODES_GROUP(NODE_GROUP_LEVEL_2) */
 
diff --git a/intern/cycles/kernel/svm/svm_ies.h b/intern/cycles/kernel/svm/svm_ies.h
new file mode 100644 (file)
index 0000000..6130c33
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011-2013 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+CCL_NAMESPACE_BEGIN
+
+/* IES Light */
+
+ccl_device_inline float interpolate_ies_vertical(KernelGlobals *kg, int ofs, int v, int v_num, float v_frac, int h)
+{
+       /* Since lookups are performed in spherical coordinates, clamping the coordinates at the low end of v
+        * (corresponding to the north pole) would result in artifacts.
+        * The proper way of dealing with this would be to lookup the corresponding value on the other side of the pole,
+        * but since the horizontal coordinates might be nonuniform, this would require yet another interpolation.
+        * Therefore, the assumtion is made that the light is going to be symmetrical, which means that we can just take
+        * the corresponding value at the current horizontal coordinate. */
+
+#define IES_LOOKUP(v) kernel_tex_fetch(__ies, ofs+h*v_num+(v))
+       /* If v is zero, assume symmetry and read at v=1 instead of v=-1. */
+       float a = IES_LOOKUP((v == 0)? 1 : v-1);
+       float b = IES_LOOKUP(v);
+       float c = IES_LOOKUP(v+1);
+       float d = IES_LOOKUP(min(v+2, v_num-1));
+#undef IES_LOOKUP
+
+       return cubic_interp(a, b, c, d, v_frac);
+}
+
+ccl_device_inline float kernel_ies_interp(KernelGlobals *kg, int slot, float h_angle, float v_angle)
+{
+       /* Find offset of the IES data in the table. */
+       int ofs = __float_as_int(kernel_tex_fetch(__ies, slot));
+       if(ofs == -1) {
+               return 100.0f;
+       }
+
+       int h_num = __float_as_int(kernel_tex_fetch(__ies, ofs++));
+       int v_num = __float_as_int(kernel_tex_fetch(__ies, ofs++));
+
+#define IES_LOOKUP_ANGLE_H(h) kernel_tex_fetch(__ies, ofs+(h))
+#define IES_LOOKUP_ANGLE_V(v) kernel_tex_fetch(__ies, ofs+h_num+(v))
+
+       /* Check whether the angle is within the bounds of the IES texture. */
+       if(v_angle >= IES_LOOKUP_ANGLE_V(v_num-1)) {
+               return 0.0f;
+       }
+       kernel_assert(v_angle >= IES_LOOKUP_ANGLE_V(0));
+       kernel_assert(h_angle >= IES_LOOKUP_ANGLE_H(0));
+       kernel_assert(h_angle <= IES_LOOKUP_ANGLE_H(h_num-1));
+
+       /* Lookup the angles to find the table position. */
+       int h_i, v_i;
+       /* TODO(lukas): Consider using bisection. Probably not worth it for the vast majority of IES files. */
+       for(h_i = 0; IES_LOOKUP_ANGLE_H(h_i+1) < h_angle; h_i++);
+       for(v_i = 0; IES_LOOKUP_ANGLE_V(v_i+1) < v_angle; v_i++);
+
+       float h_frac = inverse_lerp(IES_LOOKUP_ANGLE_H(h_i), IES_LOOKUP_ANGLE_H(h_i+1), h_angle);
+       float v_frac = inverse_lerp(IES_LOOKUP_ANGLE_V(v_i), IES_LOOKUP_ANGLE_V(v_i+1), v_angle);
+
+#undef IES_LOOKUP_ANGLE_H
+#undef IES_LOOKUP_ANGLE_V
+
+       /* Skip forward to the actual intensity data. */
+       ofs += h_num+v_num;
+
+       /* Perform cubic interpolation along the horizontal coordinate to get the intensity value.
+        * If h_i is zero, just wrap around since the horizontal angles always go over the full circle.
+        * However, the last entry (360°) equals the first one, so we need to wrap around to the one before that. */
+       float a = interpolate_ies_vertical(kg, ofs, v_i, v_num, v_frac, (h_i == 0)? h_num-2 : h_i-1);
+       float b = interpolate_ies_vertical(kg, ofs, v_i, v_num, v_frac, h_i);
+       float c = interpolate_ies_vertical(kg, ofs, v_i, v_num, v_frac, h_i+1);
+       /* Same logic here, wrap around to the second element if necessary. */
+       float d = interpolate_ies_vertical(kg, ofs, v_i, v_num, v_frac, (h_i+2 == h_num)? 1 : h_i+2);
+
+       /* Cubic interpolation can result in negative values, so get rid of them. */
+       return max(cubic_interp(a, b, c, d, h_frac), 0.0f);
+}
+
+ccl_device void svm_node_ies(KernelGlobals *kg, ShaderData *sd, float *stack, uint4 node, int *offset)
+{
+       uint vector_offset, strength_offset, fac_offset, dummy, slot = node.z;
+       decode_node_uchar4(node.y, &strength_offset, &vector_offset, &fac_offset, &dummy);
+
+       float3 vector = stack_load_float3(stack, vector_offset);
+       float strength = stack_load_float_default(stack, strength_offset, node.w);
+
+       vector = normalize(vector);
+       float v_angle = safe_acosf(-vector.z);
+       float h_angle = atan2f(vector.x, vector.y) + M_PI_F;
+
+       float fac = strength * kernel_ies_interp(kg, slot, h_angle, v_angle);
+
+       if(stack_valid(fac_offset)) {
+               stack_store_float(stack, fac_offset, fac);
+       }
+}
+
+CCL_NAMESPACE_END
index dc62e25b3401254eb746e83cb940c36f5564f0eb..ac24d23ecd21273fa6e8f26a87061e2d0252eb7b 100644 (file)
@@ -136,6 +136,7 @@ typedef enum ShaderNodeType {
        NODE_DISPLACEMENT,
        NODE_VECTOR_DISPLACEMENT,
        NODE_PRINCIPLED_VOLUME,
+       NODE_IES,
 } ShaderNodeType;
 
 typedef enum NodeAttributeType {
index 096de878e514f3807e5c772d57d393373c94b6cf..59e1a12c3a1a2d76014e6bb18b5c6e4bdcb22786 100644 (file)
@@ -774,6 +774,12 @@ void ShaderGraph::default_inputs(bool do_osl)
 
                                        connect(texco->output("Generated"), input);
                                }
+                               if(input->flags() & SocketType::LINK_TEXTURE_NORMAL) {
+                                       if(!texco)
+                                               texco = new TextureCoordinateNode();
+
+                                       connect(texco->output("Normal"), input);
+                               }
                                else if(input->flags() & SocketType::LINK_TEXTURE_UV) {
                                        if(!texco)
                                                texco = new TextureCoordinateNode();
index 8dec7e4ea643b78f28aefd5907a6fcb35e1b538e..f0824ef4319b51c4832b542bf42725b9c8a5e332 100644 (file)
@@ -25,6 +25,8 @@
 #include "render/shader.h"
 
 #include "util/util_foreach.h"
+#include "util/util_hash.h"
+#include "util/util_path.h"
 #include "util/util_progress.h"
 #include "util/util_logging.h"
 
@@ -175,6 +177,9 @@ LightManager::LightManager()
 
 LightManager::~LightManager()
 {
+       foreach(IESSlot *slot, ies_slots) {
+               delete slot;
+       }
 }
 
 bool LightManager::has_background_light(Scene *scene)
@@ -858,6 +863,9 @@ void LightManager::device_update(Device *device, DeviceScene *dscene, Scene *sce
        device_update_background(device, dscene, scene, progress);
        if(progress.get_cancel()) return;
 
+       device_update_ies(dscene);
+       if(progress.get_cancel()) return;
+
        if(use_light_visibility != scene->film->use_light_visibility) {
                scene->film->use_light_visibility = use_light_visibility;
                scene->film->tag_update(scene);
@@ -872,6 +880,7 @@ void LightManager::device_free(Device *, DeviceScene *dscene)
        dscene->lights.free();
        dscene->light_background_marginal_cdf.free();
        dscene->light_background_conditional_cdf.free();
+       dscene->ies_lights.free();
 }
 
 void LightManager::tag_update(Scene * /*scene*/)
@@ -879,5 +888,118 @@ void LightManager::tag_update(Scene * /*scene*/)
        need_update = true;
 }
 
+int LightManager::add_ies_from_file(ustring filename)
+{
+       string content;
+       /* If the file can't be opened, call with an empty string */
+       path_read_text(filename.c_str(), content);
+
+       return add_ies(ustring(content));
+}
+
+int LightManager::add_ies(ustring content)
+{
+       uint hash = hash_string(content.c_str());
+
+       thread_scoped_lock ies_lock(ies_mutex);
+
+       /* Check whether this IES already has a slot. */
+       size_t slot;
+       for(slot = 0; slot < ies_slots.size(); slot++) {
+               if(ies_slots[slot]->hash == hash) {
+                       ies_slots[slot]->users++;
+                       return slot;
+               }
+       }
+
+       /* Try to find an empty slot for the new IES. */
+       for(slot = 0; slot < ies_slots.size(); slot++) {
+               if(ies_slots[slot]->users == 0 && ies_slots[slot]->hash == 0) {
+                       break;
+               }
+       }
+
+       /* If there's no free slot, add one. */
+       if(slot == ies_slots.size()) {
+               ies_slots.push_back(new IESSlot());
+       }
+
+       ies_slots[slot]->ies.load(content);
+       ies_slots[slot]->users = 1;
+       ies_slots[slot]->hash = hash;
+
+       need_update = true;
+
+       return slot;
+}
+
+void LightManager::remove_ies(int slot)
+{
+       thread_scoped_lock ies_lock(ies_mutex);
+
+       if(slot < 0 || slot >= ies_slots.size()) {
+               assert(false);
+               return;
+       }
+
+       assert(ies_slots[slot]->users > 0);
+       ies_slots[slot]->users--;
+
+       /* If the slot has no more users, update the device to remove it. */
+       need_update |= (ies_slots[slot]->users == 0);
+}
+
+void LightManager::device_update_ies(DeviceScene *dscene)
+{
+       /* Clear empty slots. */
+       foreach(IESSlot *slot, ies_slots) {
+               if(slot->users == 0) {
+                       slot->hash = 0;
+                       slot->ies.clear();
+               }
+       }
+
+       /* Shrink the slot table by removing empty slots at the end. */
+       int slot_end;
+       for(slot_end = ies_slots.size(); slot_end; slot_end--) {
+               if(ies_slots[slot_end-1]->users > 0) {
+                       /* If the preceding slot has users, we found the new end of the table. */
+                       break;
+               }
+               else {
+                       /* The slot will be past the new end of the table, so free it. */
+                       delete ies_slots[slot_end-1];
+               }
+       }
+       ies_slots.resize(slot_end);
+
+       if(ies_slots.size() > 0) {
+               int packed_size = 0;
+               foreach(IESSlot *slot, ies_slots) {
+                       packed_size += slot->ies.packed_size();
+               }
+
+               /* ies_lights starts with an offset table that contains the offset of every slot,
+                * or -1 if the slot is invalid.
+                * Following that table, the packed valid IES lights are stored. */
+               float *data = dscene->ies_lights.alloc(ies_slots.size() + packed_size);
+
+               int offset = ies_slots.size();
+               for(int i = 0; i < ies_slots.size(); i++) {
+                       int size = ies_slots[i]->ies.packed_size();
+                       if(size > 0) {
+                               data[i] = __int_as_float(offset);
+                               ies_slots[i]->ies.pack(data + offset);
+                               offset += size;
+                       }
+                       else {
+                               data[i] = __int_as_float(-1);
+                       }
+               }
+
+               dscene->ies_lights.copy_to_device();
+       }
+}
+
 CCL_NAMESPACE_END
 
index 97b7b971c732e52853179dd5a571ebfb9922fefb..5f8677ee2f24ffb484a001e4d2ef8185a33f20b8 100644 (file)
@@ -21,6 +21,8 @@
 
 #include "graph/node.h"
 
+#include "util/util_ies.h"
+#include "util/util_thread.h"
 #include "util/util_types.h"
 #include "util/util_vector.h"
 
@@ -86,6 +88,11 @@ public:
        LightManager();
        ~LightManager();
 
+       /* IES texture management */
+       int add_ies(ustring ies);
+       int add_ies_from_file(ustring filename);
+       void remove_ies(int slot);
+
        void device_update(Device *device,
                           DeviceScene *dscene,
                           Scene *scene,
@@ -115,9 +122,19 @@ protected:
                                      DeviceScene *dscene,
                                      Scene *scene,
                                      Progress& progress);
+       void device_update_ies(DeviceScene *dscene);
 
        /* Check whether light manager can use the object as a light-emissive. */
        bool object_usable_as_light(Object *object);
+
+       struct IESSlot {
+               IESFile ies;
+               uint hash;
+               int users;
+       };
+
+       vector<IESSlot*> ies_slots;
+       thread_mutex ies_mutex;
 };
 
 CCL_NAMESPACE_END
index c468924fa663fa741c6cef6b9cb180886bcd658f..3dad4d1a34627d77df1621c32540ef2780a6e932 100644 (file)
@@ -16,6 +16,7 @@
 
 #include "render/image.h"
 #include "render/integrator.h"
+#include "render/light.h"
 #include "render/nodes.h"
 #include "render/scene.h"
 #include "render/svm.h"
@@ -384,10 +385,10 @@ void ImageTextureNode::compile(OSLCompiler& compiler)
                /* TODO(sergey): It's not so simple to pass custom attribute
                 * to the texture() function in order to make builtin images
                 * support more clear. So we use special file name which is
-                * "@<slot_number>" and check whether file name matches this
+                * "@i<slot_number>" and check whether file name matches this
                 * mask in the OSLRenderServices::texture().
                 */
-               compiler.parameter("filename", string_printf("@%d", slot).c_str());
+               compiler.parameter("filename", string_printf("@i%d", slot).c_str());
        }
        if(is_linear || color_space != NODE_COLOR_SPACE_COLOR)
                compiler.parameter("color_space", "linear");
@@ -567,7 +568,7 @@ void EnvironmentTextureNode::compile(OSLCompiler& compiler)
                compiler.parameter(this, "filename");
        }
        else {
-               compiler.parameter("filename", string_printf("@%d", slot).c_str());
+               compiler.parameter("filename", string_printf("@i%d", slot).c_str());
        }
        compiler.parameter(this, "projection");
        if(is_linear || color_space != NODE_COLOR_SPACE_COLOR)
@@ -954,6 +955,97 @@ void VoronoiTextureNode::compile(OSLCompiler& compiler)
        compiler.add(this, "node_voronoi_texture");
 }
 
+/* IES Light */
+
+NODE_DEFINE(IESLightNode)
+{
+       NodeType* type = NodeType::add("ies_light", create, NodeType::SHADER);
+
+       TEXTURE_MAPPING_DEFINE(IESLightNode);
+
+       SOCKET_STRING(ies, "IES", ustring());
+       SOCKET_STRING(filename, "File Name", ustring());
+
+       SOCKET_IN_FLOAT(strength, "Strength", 1.0f);
+       SOCKET_IN_POINT(vector, "Vector", make_float3(0.0f, 0.0f, 0.0f), SocketType::LINK_TEXTURE_NORMAL);
+
+       SOCKET_OUT_FLOAT(fac, "Fac");
+
+       return type;
+}
+
+IESLightNode::IESLightNode()
+: TextureNode(node_type)
+{
+       light_manager = NULL;
+       slot = -1;
+}
+
+ShaderNode *IESLightNode::clone() const
+{
+       IESLightNode *node = new IESLightNode(*this);
+
+       node->light_manager = NULL;
+       node->slot = -1;
+
+       return node;
+}
+
+IESLightNode::~IESLightNode()
+{
+       if(light_manager) {
+               light_manager->remove_ies(slot);
+       }
+}
+
+void IESLightNode::get_slot()
+{
+       assert(light_manager);
+
+       if(slot == -1) {
+               if(ies.empty()) {
+                       slot = light_manager->add_ies_from_file(filename);
+               }
+               else {
+                       slot = light_manager->add_ies(ies);
+               }
+       }
+}
+
+void IESLightNode::compile(SVMCompiler& compiler)
+{
+       light_manager = compiler.light_manager;
+       get_slot();
+
+       ShaderInput *strength_in = input("Strength");
+       ShaderInput *vector_in = input("Vector");
+       ShaderOutput *fac_out = output("Fac");
+
+       int vector_offset = tex_mapping.compile_begin(compiler, vector_in);
+
+       compiler.add_node(NODE_IES,
+               compiler.encode_uchar4(
+                       compiler.stack_assign_if_linked(strength_in),
+                       vector_offset,
+                       compiler.stack_assign(fac_out),
+                       0),
+               slot,
+               __float_as_int(strength));
+
+       tex_mapping.compile_end(compiler, vector_in, vector_offset);
+}
+
+void IESLightNode::compile(OSLCompiler& compiler)
+{
+       light_manager = compiler.light_manager;
+       get_slot();
+
+       tex_mapping.compile(compiler);
+
+       compiler.parameter("slot", slot);
+       compiler.add(this, "node_ies_light");
+}
+
 /* Musgrave Texture */
 
 NODE_DEFINE(MusgraveTextureNode)
@@ -1470,7 +1562,7 @@ void PointDensityTextureNode::compile(OSLCompiler& compiler)
                }
 
                if(slot != -1) {
-                       compiler.parameter("filename", string_printf("@%d", slot).c_str());
+                       compiler.parameter("filename", string_printf("@i%d", slot).c_str());
                }
                if(space == NODE_TEX_VOXEL_SPACE_WORLD) {
                        compiler.parameter("mapping", tfm);
index 58c3d472cd31fb4b264efd6dd9ddab7507bfbb5c..35a7df690c39b005886580f9a7d1538633955f0f 100644 (file)
@@ -25,6 +25,7 @@
 CCL_NAMESPACE_BEGIN
 
 class ImageManager;
+class LightManager;
 class Scene;
 class Shader;
 
@@ -281,6 +282,27 @@ public:
        }
 };
 
+class IESLightNode : public TextureNode {
+public:
+       SHADER_NODE_NO_CLONE_CLASS(IESLightNode)
+
+       ~IESLightNode();
+       ShaderNode *clone() const;
+       virtual int get_group() { return NODE_GROUP_LEVEL_2; }
+
+       ustring filename;
+       ustring ies;
+
+       float strength;
+       float3 vector;
+
+private:
+       LightManager *light_manager;
+       int slot;
+
+       void get_slot();
+};
+
 class MappingNode : public ShaderNode {
 public:
        SHADER_NODE_CLASS(MappingNode)
index f1a22350060b547735508edf0e4a552d508bbf86..dde622bff8abf7e619e2bef56a5e5d8197cdc782 100644 (file)
@@ -99,7 +99,9 @@ void OSLShaderManager::device_update(Device *device, DeviceScene *dscene, Scene
                 * compile shaders alternating */
                thread_scoped_lock lock(ss_mutex);
 
-               OSLCompiler compiler((void*)this, (void*)ss, scene->image_manager);
+               OSLCompiler compiler((void*)this, (void*)ss,
+                                    scene->image_manager,
+                                    scene->light_manager);
                compiler.background = (shader == scene->default_background);
                compiler.compile(scene, og, shader);
 
@@ -546,11 +548,14 @@ OSLNode *OSLShaderManager::osl_node(const std::string& filepath,
 
 /* Graph Compiler */
 
-OSLCompiler::OSLCompiler(void *manager_, void *shadingsys_, ImageManager *image_manager_)
+OSLCompiler::OSLCompiler(void *manager_, void *shadingsys_,
+                         ImageManager *image_manager_,
+                         LightManager *light_manager_)
 {
        manager = manager_;
        shadingsys = shadingsys_;
        image_manager = image_manager_;
+       light_manager = light_manager_;
        current_type = SHADER_TYPE_SURFACE;
        current_shader = NULL;
        background = false;
index 95e35dd857ba8e9cc34ae93ad4f9c4dca69da20a..7a3208c402a22c9c2e87b50810e43f7666337e66 100644 (file)
@@ -120,7 +120,9 @@ protected:
 
 class OSLCompiler {
 public:
-       OSLCompiler(void *manager, void *shadingsys, ImageManager *image_manager);
+       OSLCompiler(void *manager, void *shadingsys,
+                   ImageManager *image_manager,
+                   LightManager *light_manager);
        void compile(Scene *scene, OSLGlobals *og, Shader *shader);
 
        void add(ShaderNode *node, const char *name, bool isfilepath = false);
@@ -146,6 +148,7 @@ public:
 
        bool background;
        ImageManager *image_manager;
+       LightManager *light_manager;
 
 private:
 #ifdef WITH_OSL
index ba47e3ab6f81dfa9eb65799ee9767d58724ff2ce..b35cdbd3db55415ea2b02025857413c09ad13816 100644 (file)
@@ -76,7 +76,8 @@ DeviceScene::DeviceScene(Device *device)
   svm_nodes(device, "__svm_nodes", MEM_TEXTURE),
   shaders(device, "__shaders", MEM_TEXTURE),
   lookup_table(device, "__lookup_table", MEM_TEXTURE),
-  sobol_directions(device, "__sobol_directions", MEM_TEXTURE)
+  sobol_directions(device, "__sobol_directions", MEM_TEXTURE),
+  ies_lights(device, "__ies", MEM_TEXTURE)
 {
        memset(&data, 0, sizeof(data));
 }
index 04bd4735a86b8251ad8830fb47c7cacdd7b2cea6..6c67433c9fcbd2b208a48aadf5aff05b4023bf20 100644 (file)
@@ -119,6 +119,9 @@ public:
        /* integrator */
        device_vector<uint> sobol_directions;
 
+       /* ies lights */
+       device_vector<float> ies_lights;
+
        KernelData data;
 
        DeviceScene(Device *device);
index c5b4060d5c3c2f707809a78a085376d9eb492a9a..eb8a35a271fd3532d48ca1da01615760630876d2 100644 (file)
@@ -58,7 +58,7 @@ void SVMShaderManager::device_update_shader(Scene *scene,
        svm_nodes.push_back_slow(make_int4(NODE_SHADER_JUMP, 0, 0, 0));
 
        SVMCompiler::Summary summary;
-       SVMCompiler compiler(scene->shader_manager, scene->image_manager);
+       SVMCompiler compiler(scene->shader_manager, scene->image_manager, scene->light_manager);
        compiler.background = (shader == scene->default_background);
        compiler.compile(scene, shader, svm_nodes, 0, &summary);
 
@@ -154,10 +154,13 @@ void SVMShaderManager::device_free(Device *device, DeviceScene *dscene, Scene *s
 
 /* Graph Compiler */
 
-SVMCompiler::SVMCompiler(ShaderManager *shader_manager_, ImageManager *image_manager_)
+SVMCompiler::SVMCompiler(ShaderManager *shader_manager_,
+                         ImageManager *image_manager_,
+                         LightManager *light_manager_)
 {
        shader_manager = shader_manager_;
        image_manager = image_manager_;
+       light_manager = light_manager_;
        max_stack_use = 0;
        current_type = SHADER_TYPE_SURFACE;
        current_shader = NULL;
index 18be0fa9a223239cd2551b23b6538eac377544f4..7cf1e4ad79148c65cc26471039cb6af2ed91b773 100644 (file)
@@ -95,7 +95,9 @@ public:
                string full_report() const;
        };
 
-       SVMCompiler(ShaderManager *shader_manager, ImageManager *image_manager);
+       SVMCompiler(ShaderManager *shader_manager,
+                   ImageManager *image_manager,
+                   LightManager *light_manager);
        void compile(Scene *scene,
                     Shader *shader,
                     array<int4>& svm_nodes,
@@ -125,6 +127,7 @@ public:
 
        ImageManager *image_manager;
        ShaderManager *shader_manager;
+       LightManager *light_manager;
        bool background;
 
 protected:
index 24043e2231bc6c121c55126da62d6d3549c45bc9..3b690860d53b074dc4aa5bb67581232b90563df0 100644 (file)
@@ -11,6 +11,7 @@ set(INC_SYS
 set(SRC
        util_aligned_malloc.cpp
        util_debug.cpp
+       util_ies.cpp
        util_logging.cpp
        util_math_cdf.cpp
        util_md5.cpp
@@ -45,6 +46,7 @@ set(SRC_HEADERS
        util_guarded_allocator.h
        util_half.h
        util_hash.h
+       util_ies.h
        util_image.h
        util_image_impl.h
        util_list.h
diff --git a/intern/cycles/util/util_ies.cpp b/intern/cycles/util/util_ies.cpp
new file mode 100644 (file)
index 0000000..4824c88
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * Copyright 2011-2018 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "util/util_foreach.h"
+#include "util/util_ies.h"
+#include "util/util_math.h"
+#include "util/util_string.h"
+
+CCL_NAMESPACE_BEGIN
+
+bool IESFile::load(ustring ies)
+{
+       clear();
+       if(!parse(ies) || !process()) {
+               clear();
+               return false;
+       }
+       return true;
+}
+
+void IESFile::clear()
+{
+       intensity.clear();
+       v_angles.clear();
+       h_angles.clear();
+}
+
+int IESFile::packed_size()
+{
+       if(v_angles.size() && h_angles.size() > 0) {
+               return 2 + h_angles.size() + v_angles.size() + h_angles.size()*v_angles.size();
+       }
+       return 0;
+}
+
+void IESFile::pack(float *data)
+{
+       if(v_angles.size() && h_angles.size()) {
+               *(data++) = __int_as_float(h_angles.size());
+               *(data++) = __int_as_float(v_angles.size());
+
+               memcpy(data, &h_angles[0], h_angles.size()*sizeof(float));
+               data += h_angles.size();
+               memcpy(data, &v_angles[0], v_angles.size()*sizeof(float));
+               data += v_angles.size();
+
+               for(int h = 0; h < intensity.size(); h++) {
+                       memcpy(data, &intensity[h][0], v_angles.size()*sizeof(float));
+                       data += v_angles.size();
+               }
+       }
+}
+
+class IESTextParser {
+public:
+       vector<char> text;
+       char *data;
+
+       IESTextParser(ustring str)
+        : text(str.begin(), str.end())
+       {
+               std::replace(text.begin(), text.end(), ',', ' ');
+               data = strstr(&text[0], "\nTILT=");
+       }
+
+       bool eof() {
+               return (data == NULL) || (data[0] == '\0');
+       }
+
+       double get_double() {
+               if(eof()) {
+                       return 0.0;
+               }
+               char *old_data = data;
+               double val = strtod(data, &data);
+               if(data == old_data) {
+                       data = NULL;
+                       return 0.0;
+               }
+               return val;
+       }
+
+       long get_long() {
+               if(eof()) {
+                       return 0;
+               }
+               char *old_data = data;
+               long val = strtol(data, &data, 10);
+               if(data == old_data) {
+                       data = NULL;
+                       return 0;
+               }
+               return val;
+       }
+};
+
+bool IESFile::parse(ustring ies)
+{
+       IESTextParser parser(ies);
+       if(parser.eof()) {
+               return false;
+       }
+
+       /* Handle the tilt data block. */
+       if(strncmp(parser.data, "\nTILT=INCLUDE", 13) == 0) {
+               parser.data += 13;
+               parser.get_double(); /* Lamp to Luminaire geometry */
+               int num_tilt = parser.get_long(); /* Amount of tilt angles and factors */
+               /* Skip over angles and factors. */
+               for(int i = 0; i < 2*num_tilt; i++) {
+                       parser.get_double();
+               }
+       }
+       else {
+               /* Skip to next line. */
+               parser.data = strstr(parser.data+1, "\n");
+       }
+
+       if(parser.eof()) {
+               return false;
+       }
+       parser.data++;
+
+       parser.get_long(); /* Number of lamps */
+       parser.get_double(); /* Lumens per lamp */
+       double factor = parser.get_double(); /* Candela multiplier */
+       int v_angles_num = parser.get_long(); /* Number of vertical angles */
+       int h_angles_num = parser.get_long(); /* Number of horizontal angles */
+       type = (IESType) parser.get_long(); /* Photometric type */
+
+       /* TODO(lukas): Test whether the current type B processing can also deal with type A files.
+        * In theory the only difference should be orientation which we ignore anyways, but with IES you never know...
+        */
+       if(type != TYPE_B && type != TYPE_C) {
+               return false;
+       }
+
+       parser.get_long(); /* Unit of the geometry data */
+       parser.get_double(); /* Width */
+       parser.get_double(); /* Length */
+       parser.get_double(); /* Height */
+       factor *= parser.get_double(); /* Ballast factor */
+       factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */
+       parser.get_double(); /* Input Watts */
+
+       /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity.
+        * Cycles expects radiometric quantities, though, which requires a conversion.
+        * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution
+        * of the light source since lumens take human perception into account.
+        * Since this spectral distribution is not known from the IES file, a typical one must be assumed.
+        * The D65 standard illuminant has a Luminous efficacy of 177.83, which is used here to convert to Watt/sr.
+        * A more advanced approach would be to add a Blackbody Temperature input to the node and numerically
+        * integrate the Luminous efficacy from the resulting spectral distribution.
+        * Also, the Watt/sr value must be multiplied by 4*pi to get the Watt value that Cycles expects
+        * for lamp strength. Therefore, the conversion here uses 4*pi/177.83 as a Candela to Watt factor.
+        */
+       factor *= 0.0706650768394;
+
+       v_angles.reserve(v_angles_num);
+       for(int i = 0; i < v_angles_num; i++) {
+               v_angles.push_back((float) parser.get_double());
+       }
+
+       h_angles.reserve(h_angles_num);
+       for(int i = 0; i < h_angles_num; i++) {
+               h_angles.push_back((float) parser.get_double());
+       }
+
+       intensity.resize(h_angles_num);
+       for(int i = 0; i < h_angles_num; i++) {
+               intensity[i].reserve(v_angles_num);
+               for(int j = 0; j < v_angles_num; j++) {
+                       intensity[i].push_back((float) (factor * parser.get_double()));
+               }
+       }
+
+       return !parser.eof();
+}
+
+bool IESFile::process_type_b()
+{
+       vector<vector<float> > newintensity;
+       newintensity.resize(v_angles.size());
+       for(int i = 0; i < v_angles.size(); i++) {
+               newintensity[i].reserve(h_angles.size());
+               for(int j = 0; j < h_angles.size(); j++) {
+                       newintensity[i].push_back(intensity[j][i]);
+               }
+       }
+       intensity.swap(newintensity);
+       h_angles.swap(v_angles);
+
+       float h_first = h_angles[0], h_last = h_angles[h_angles.size()-1];
+       if(h_last != 90.0f) {
+               return false;
+       }
+
+       if(h_first == 0.0f) {
+               /* The range in the file corresponds to 90°-180°, we need to mirror that to get the
+                * full 180° range. */
+               vector<float> new_h_angles;
+               vector<vector<float> > new_intensity;
+               int hnum = h_angles.size();
+               new_h_angles.reserve(2*hnum-1);
+               new_intensity.reserve(2*hnum-1);
+               for(int i = hnum-1; i > 0; i--) {
+                       new_h_angles.push_back(90.0f - h_angles[i]);
+                       new_intensity.push_back(intensity[i]);
+               }
+               for(int i = 0; i < hnum; i++) {
+                       new_h_angles.push_back(90.0f + h_angles[i]);
+                       new_intensity.push_back(intensity[i]);
+               }
+               h_angles.swap(new_h_angles);
+               intensity.swap(new_intensity);
+       }
+       else if(h_first == -90.0f) {
+               /* We have full 180° coverage, so just shift to match the angle range convention. */
+               for(int i = 0; i < h_angles.size(); i++) {
+                       h_angles[i] += 90.0f;
+               }
+       }
+       /* To get correct results with the cubic interpolation in the kernel, the horizontal range
+        * has to cover all 360°. Therefore, we copy the 0° entry to 360° to ensure full coverage
+        * and seamless interpolation. */
+       h_angles.push_back(360.0f);
+       intensity.push_back(intensity[0]);
+
+       float v_first = v_angles[0], v_last = v_angles[v_angles.size()-1];
+       if(v_last != 90.0f) {
+               return false;
+       }
+
+       if(v_first == 0.0f) {
+               /* The range in the file corresponds to 90°-180°, we need to mirror that to get the
+                * full 180° range. */
+               vector<float> new_v_angles;
+               int hnum = h_angles.size();
+               int vnum = v_angles.size();
+               new_v_angles.reserve(2*vnum-1);
+               for(int i = vnum-1; i > 0; i--) {
+                       new_v_angles.push_back(90.0f - v_angles[i]);
+               }
+               for(int i = 0; i < vnum; i++) {
+                       new_v_angles.push_back(90.0f + v_angles[i]);
+               }
+               for(int i = 0; i < hnum; i++) {
+                       vector<float> new_intensity;
+                       new_intensity.reserve(2*vnum-1);
+                       for(int j = vnum-2; j >= 0; j--) {
+                               new_intensity.push_back(intensity[i][j]);
+                       }
+                       new_intensity.insert(new_intensity.end(), intensity[i].begin(), intensity[i].end());
+                       intensity[i].swap(new_intensity);
+               }
+               v_angles.swap(new_v_angles);
+       }
+       else if(v_first == -90.0f) {
+               /* We have full 180° coverage, so just shift to match the angle range convention. */
+               for(int i = 0; i < v_angles.size(); i++) {
+                       v_angles[i] += 90.0f;
+               }
+       }
+
+       return true;
+}
+
+bool IESFile::process_type_c()
+{
+       if(h_angles[0] == 90.0f) {
+               /* Some files are stored from 90° to 270°, so we just rotate them to the regular 0°-180° range here. */
+               for(int i = 0; i < v_angles.size(); i++) {
+                       h_angles[i] -= 90.0f;
+               }
+       }
+
+       if(h_angles[0] != 0.0f) {
+               return false;
+       }
+
+       if(h_angles.size() == 1) {
+               h_angles.push_back(360.0f);
+               intensity.push_back(intensity[0]);
+       }
+
+       if(h_angles[h_angles.size()-1] == 90.0f) {
+               /* Only one quadrant is defined, so we need to mirror twice (from one to two, then to four).
+                * Since the two->four mirroring step might also be required if we get an input of two quadrants,
+                * we only do the first mirror here and later do the second mirror in either case. */
+               int hnum = h_angles.size();
+               for(int i = hnum-2; i >= 0; i--) {
+                       h_angles.push_back(180.0f - h_angles[i]);
+                       intensity.push_back(intensity[i]);
+               }
+       }
+
+       if(h_angles[h_angles.size()-1] == 180.0f) {
+               /* Mirror half to the full range. */
+               int hnum = h_angles.size();
+               for(int i = hnum-2; i >= 0; i--) {
+                       h_angles.push_back(360.0f - h_angles[i]);
+                       intensity.push_back(intensity[i]);
+               }
+       }
+
+       /* Some files skip the 360° entry (contrary to standard) because it's supposed to be identical to the 0° entry.
+        * If the file has a discernible order in its spacing, just fix this. */
+       if(h_angles[h_angles.size()-1] != 360.0f) {
+               int hnum = h_angles.size();
+               float last_step = h_angles[hnum-1]-h_angles[hnum-2];
+               float first_step = h_angles[1]-h_angles[0];
+               float difference = 360.0f - h_angles[hnum-1];
+               if(last_step == difference || first_step == difference) {
+                       h_angles.push_back(360.0f);
+                       intensity.push_back(intensity[0]);
+               }
+               else {
+                       return false;
+               }
+       }
+
+       float v_first = v_angles[0], v_last = v_angles[v_angles.size()-1];
+       if(v_first == 90.0f) {
+               if(v_last == 180.0f) {
+                       /* Flip to ensure that vertical angles always start at 0°. */
+                       for(int i = 0; i < v_angles.size(); i++) {
+                               v_angles[i] = 180.0f - v_angles[i];
+                       }
+               }
+               else {
+                       return false;
+               }
+       }
+       else if(v_first != 0.0f) {
+               return false;
+       }
+
+       return true;
+}
+
+bool IESFile::process()
+{
+       if(h_angles.size() == 0 || v_angles.size() == 0) {
+               return false;
+       }
+
+       if(type == TYPE_B) {
+               if(!process_type_b()) {
+                       return false;
+               }
+       }
+       else {
+               assert(type == TYPE_C);
+               if(!process_type_c()) {
+                       return false;
+               }
+       }
+
+       assert(v_angles[0] == 0.0f);
+       assert(h_angles[0] == 0.0f);
+       assert(h_angles[h_angles.size()-1] == 360.0f);
+
+       /* Convert from deg to rad. */
+       for(int i = 0; i < v_angles.size(); i++) {
+               v_angles[i] *= M_PI_F / 180.f;
+       }
+       for(int i = 0; i < h_angles.size(); i++) {
+               h_angles[i] *= M_PI_F / 180.f;
+       }
+
+       return true;
+}
+
+IESFile::~IESFile()
+{
+       clear();
+}
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/util/util_ies.h b/intern/cycles/util/util_ies.h
new file mode 100644 (file)
index 0000000..5933cb3
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2011-2018 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __UTIL_IES_H__
+#define __UTIL_IES_H__
+
+#include "util/util_param.h"
+#include "util/util_vector.h"
+
+CCL_NAMESPACE_BEGIN
+
+class IESFile {
+public:
+       IESFile() {}
+       ~IESFile();
+
+       int packed_size();
+       void pack(float *data);
+
+       bool load(ustring ies);
+       void clear();
+
+protected:
+       bool parse(ustring ies);
+       bool process();
+       bool process_type_b();
+       bool process_type_c();
+
+       /* The brightness distribution is stored in spherical coordinates.
+        * The horizontal angles correspond to to theta in the regular notation
+        * and always span the full range from 0° to 360°.
+        * The vertical angles correspond to phi and always start at 0°. */
+       vector<float> v_angles, h_angles;
+       /* The actual values are stored here, with every entry storing the values
+        * of one horizontal segment. */
+       vector<vector<float> > intensity;
+
+       /* Types of angle representation in IES files. Currently, only B and C are supported. */
+       enum IESType {
+               TYPE_A = 3,
+               TYPE_B = 2,
+               TYPE_C = 1
+       } type;
+};
+
+CCL_NAMESPACE_END
+
+#endif /* __UTIL_IES_H__ */
index d0e91a2a1c9f2875015dec114bdb2200539e4154..fd3199f209f53787503f6921fc7e844fd01944dc 100644 (file)
@@ -310,6 +310,17 @@ ccl_device_inline float4 float3_to_float4(const float3 a)
        return make_float4(a.x, a.y, a.z, 1.0f);
 }
 
+ccl_device_inline float inverse_lerp(float a, float b, float x)
+{
+       return (x - a) / (b - a);
+}
+
+/* Cubic interpolation between b and c, a and d are the previous and next point. */
+ccl_device_inline float cubic_interp(float a, float b, float c, float d, float x)
+{
+       return 0.5f*(((d + 3.0f*(b-c) - a)*x + (2.0f*a - 5.0f*b + 4.0f*c - d))*x + (c - a))*x + b;
+}
+
 CCL_NAMESPACE_END
 
 #include "util/util_math_int2.h"
index 7089f691ff2e237eef006efae2990c938665d616..60a8471ca3b3ae8ee260377cf266a12adf26931c 100644 (file)
@@ -259,6 +259,7 @@ shader_node_categories = [
         NodeItem("ShaderNodeTexChecker"),
         NodeItem("ShaderNodeTexBrick"),
         NodeItem("ShaderNodeTexPointDensity"),
+        NodeItem("ShaderNodeTexIES"),
         ]),
     ShaderNewNodeCategory("SH_NEW_OP_COLOR", "Color", items=[
         NodeItem("ShaderNodeMixRGB"),
index efad8e48e3dd7f6e430862f655d903b45d5ff4f1..bd3b191c96c9c25cbcce170389a2dfb69513dbd5 100644 (file)
@@ -789,6 +789,7 @@ struct ShadeResult;
 #define SH_NODE_UVALONGSTROKE                  191
 #define SH_NODE_TEX_POINTDENSITY               192
 #define SH_NODE_BSDF_PRINCIPLED         193
+#define SH_NODE_TEX_IES                 195
 #define SH_NODE_BEVEL                   197
 #define SH_NODE_DISPLACEMENT            198
 #define SH_NODE_VECTOR_DISPLACEMENT     199
index e9a8de4469d06341eff54b61e747e82a51795d9a..cc4c28e8016666d1f1c1a8df3cccf9ffdc5a2eee 100644 (file)
@@ -560,6 +560,10 @@ void BKE_bpath_traverse_id(Main *bmain, ID *id, BPathVisitor visit_cb, const int
                                                NodeShaderScript *nss = (NodeShaderScript *)node->storage;
                                                rewrite_path_fixed(nss->filepath, visit_cb, absbase, bpath_user_data);
                                        }
+                                       else if (node->type == SH_NODE_TEX_IES) {
+                                               NodeShaderTexIES *ies = (NodeShaderTexIES *)node->storage;
+                                               rewrite_path_fixed(ies->filepath, visit_cb, absbase, bpath_user_data);
+                                       }
                                }
                        }
                        break;
@@ -576,6 +580,10 @@ void BKE_bpath_traverse_id(Main *bmain, ID *id, BPathVisitor visit_cb, const int
                                                NodeShaderScript *nss = (NodeShaderScript *)node->storage;
                                                rewrite_path_fixed(nss->filepath, visit_cb, absbase, bpath_user_data);
                                        }
+                                       else if (node->type == SH_NODE_TEX_IES) {
+                                               NodeShaderTexIES *ies = (NodeShaderTexIES *)node->storage;
+                                               rewrite_path_fixed(ies->filepath, visit_cb, absbase, bpath_user_data);
+                                       }
                                }
                        }
                        break;
index 5d4b1ae817093213bd2f4e645108ce7f7a3f177c..521874bb14f3d84c5ec1aaf7a5b39cc8524b865f 100644 (file)
@@ -3640,6 +3640,7 @@ static void registerShaderNodes(void)
        register_node_type_sh_tex_checker();
        register_node_type_sh_tex_brick();
        register_node_type_sh_tex_pointdensity();
+       register_node_type_sh_tex_ies();
 }
 
 static void registerTextureNodes(void)
index 5665a5cff2dbd6e92061335754503918b039d262..ea8d4b36d3b5a73f26c68e85027af509f43a75d2 100644 (file)
@@ -1109,6 +1109,21 @@ static void node_shader_buts_hair(uiLayout *layout, bContext *UNUSED(C), Pointer
        uiItemR(layout, ptr, "component", 0, "", ICON_NONE);
 }
 
+static void node_shader_buts_ies(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
+{
+       uiLayout *row;
+
+       row = uiLayoutRow(layout, false);
+       uiItemR(row, ptr, "mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
+
+       row = uiLayoutRow(layout, true);
+
+       if (RNA_enum_get(ptr, "mode") == NODE_IES_INTERNAL)
+               uiItemR(row, ptr, "ies", 0, "", ICON_NONE);
+       else
+               uiItemR(row, ptr, "filepath", 0, "", ICON_NONE);
+}
+
 static void node_shader_buts_script(uiLayout *layout, bContext *UNUSED(C), PointerRNA *ptr)
 {
        uiLayout *row;
@@ -1290,6 +1305,9 @@ static void node_shader_set_butfunc(bNodeType *ntype)
                case SH_NODE_OUTPUT_LINESTYLE:
                        ntype->draw_buttons = node_buts_output_linestyle;
                        break;
+               case SH_NODE_TEX_IES:
+                       ntype->draw_buttons = node_shader_buts_ies;
+                       break;
                case SH_NODE_BEVEL:
                        ntype->draw_buttons = node_shader_buts_bevel;
                        break;
index 8d3ab29a2fb7e820e1021944b80351459454dd91..95101ac1e510afaee6e12b5e0bda8eb6f43b011d 100644 (file)
@@ -893,6 +893,12 @@ typedef struct NodeShaderUVMap {
        char uv_map[64];
 } NodeShaderUVMap;
 
+typedef struct NodeShaderTexIES {
+       int mode;
+
+       char filepath[1024]; /* 1024 = FILE_MAX */
+} NodeShaderTexIES;
+
 typedef struct NodeSunBeams {
        float source[2];
 
@@ -906,6 +912,9 @@ typedef struct NodeSunBeams {
 /* script node flag */
 #define NODE_SCRIPT_AUTO_UPDATE                1
 
+/* ies node mode */
+#define NODE_IES_INTERNAL              0
+#define NODE_IES_EXTERNAL              1
 
 /* frame node flags */
 #define NODE_FRAME_SHRINK              1       /* keep the bounding box minimal */
index e512aebfa71cada823ffdb30291e284922d8796f..73beee51a8b7863009c957fce84f7269ec29d42b 100644 (file)
@@ -529,6 +529,7 @@ extern StructRNA RNA_ShaderNodeCombineRGB;
 extern StructRNA RNA_ShaderNodeExtendedMaterial;
 extern StructRNA RNA_ShaderNodeGeometry;
 extern StructRNA RNA_ShaderNodeHueSaturation;
+extern StructRNA RNA_ShaderNodeIESLight;
 extern StructRNA RNA_ShaderNodeInvert;
 extern StructRNA RNA_ShaderNodeLampData;
 extern StructRNA RNA_ShaderNodeMapping;
index f5c464c0758b2f86e5416c8de68cee6f5107e10a..8dbb133a8ac1dc729a50f44ff35172478cf2dc8f 100644 (file)
@@ -2946,6 +2946,30 @@ static bNodeSocket *rna_NodeOutputFile_slots_new(ID *id, bNode *node, bContext *
        return sock;
 }
 
+static void rna_ShaderNodeTexIES_mode_set(PointerRNA *ptr, int value)
+{
+       bNode *node = (bNode *)ptr->data;
+       NodeShaderTexIES *nss = node->storage;
+
+       if (nss->mode != value) {
+               nss->mode = value;
+               nss->filepath[0] = '\0';
+
+               /* replace text datablock by filepath */
+               if (node->id) {
+                       Text *text = (Text *)node->id;
+
+                       if (value == NODE_IES_EXTERNAL && text->name) {
+                               BLI_strncpy(nss->filepath, text->name, sizeof(nss->filepath));
+                               BLI_path_rel(nss->filepath, G.main->name);
+                       }
+
+                       id_us_min(node->id);
+                       node->id = NULL;
+               }
+       }
+}
+
 static void rna_ShaderNodeScript_mode_set(PointerRNA *ptr, int value)
 {
        bNode *node = (bNode *)ptr->data;
@@ -3293,6 +3317,12 @@ static const EnumPropertyItem node_script_mode_items[] = {
        {0, NULL, 0, NULL, NULL}
 };
 
+static EnumPropertyItem node_ies_mode_items[] = {
+       {NODE_IES_INTERNAL, "INTERNAL", 0, "Internal", "Use internal text datablock"},
+       {NODE_IES_EXTERNAL, "EXTERNAL", 0, "External", "Use external .ies file"},
+       {0, NULL, 0, NULL, NULL}
+};
+
 static const EnumPropertyItem node_principled_distribution_items[] = {
        {SHD_GLOSSY_GGX, "GGX", 0, "GGX", ""},
        {SHD_GLOSSY_MULTI_GGX, "MULTI_GGX", 0, "Multiscatter GGX", ""},
@@ -4471,6 +4501,32 @@ static void def_sh_subsurface(StructRNA *srna)
        RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_ShaderNodeSubsurface_update");
 }
 
+static void def_sh_tex_ies(StructRNA *srna)
+{
+       PropertyRNA *prop;
+
+       prop = RNA_def_property(srna, "ies", PROP_POINTER, PROP_NONE);
+       RNA_def_property_pointer_sdna(prop, NULL, "id");
+       RNA_def_property_struct_type(prop, "Text");
+       RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
+       RNA_def_property_ui_text(prop, "IES Text", "Internal IES file");
+       RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+
+       RNA_def_struct_sdna_from(srna, "NodeShaderTexIES", "storage");
+
+       prop = RNA_def_property(srna, "filepath", PROP_STRING, PROP_FILEPATH);
+       RNA_def_property_ui_text(prop, "File Path", "IES light path");
+       RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+
+       prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
+       RNA_def_property_enum_funcs(prop, NULL, "rna_ShaderNodeTexIES_mode_set", NULL);
+       RNA_def_property_enum_items(prop, node_ies_mode_items);
+       RNA_def_property_ui_text(prop, "Source", "Whether the IES file is loaded from disk or from a Text datablock");
+       RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update");
+
+       RNA_def_struct_sdna_from(srna, "bNode", NULL);
+}
+
 static void def_sh_script(StructRNA *srna)
 {
        PropertyRNA *prop;
index 5247599bdf778360bc889cea4277b1b932a0baba..cc0bef300477b6c7daec15b3e8fb4bafbb78b616 100644 (file)
@@ -177,6 +177,7 @@ set(SRC
        shader/nodes/node_shader_fresnel.c
        shader/nodes/node_shader_geometry.c
        shader/nodes/node_shader_holdout.c
+       shader/nodes/node_shader_ies_light.c
        shader/nodes/node_shader_layer_weight.c
        shader/nodes/node_shader_light_falloff.c
        shader/nodes/node_shader_light_path.c
index b00307ed7fbae7938de2027fa4327c755d63fd06..7cb52cda55055cf0cdb6ad3ef001a8229b85bf0c 100644 (file)
@@ -138,6 +138,7 @@ void register_node_type_sh_tex_musgrave(void);
 void register_node_type_sh_tex_noise(void);
 void register_node_type_sh_tex_checker(void);
 void register_node_type_sh_bump(void);
+void register_node_type_sh_tex_ies(void);
 
 #endif
 
index bc90e33ed039992398262b3ff121c0a115455ef7..5864b3deb39554f62fc48ce022120f125e79474b 100644 (file)
@@ -130,6 +130,7 @@ DefNode( ShaderNode,     SH_NODE_COMBXYZ,            0,                      "CO
 DefNode( ShaderNode,     SH_NODE_BEVEL,              def_sh_bevel,           "BEVEL",              Bevel,            "Bevel",             ""       )
 DefNode( ShaderNode,     SH_NODE_DISPLACEMENT,       def_sh_displacement,    "DISPLACEMENT",       Displacement,     "Displacement",      ""       )
 DefNode( ShaderNode,     SH_NODE_VECTOR_DISPLACEMENT,def_sh_vector_displacement,"VECTOR_DISPLACEMENT",VectorDisplacement,"Vector Displacement",""  )
+DefNode( ShaderNode,     SH_NODE_TEX_IES,            def_sh_tex_ies,         "TEX_IES",            TexIES,           "IES Texture",       ""       )
 
 DefNode( CompositorNode, CMP_NODE_VIEWER,         def_cmp_viewer,         "VIEWER",         Viewer,           "Viewer",            ""              )
 DefNode( CompositorNode, CMP_NODE_RGB,            0,                      "RGB",            RGB,              "RGB",               ""              )
diff --git a/source/blender/nodes/shader/nodes/node_shader_ies_light.c b/source/blender/nodes/shader/nodes/node_shader_ies_light.c
new file mode 100644 (file)
index 0000000..8084f44
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2018 Blender Foundation.
+ * All rights reserved.
+ *
+ * The Original Code is: all of this file.
+ *
+ * Contributor(s): none yet.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+#include "../node_shader_util.h"
+
+/* **************** IES Light ******************** */
+
+static bNodeSocketTemplate sh_node_tex_ies_in[] = {
+       {       SOCK_VECTOR, 1, N_("Vector"),           0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, PROP_NONE, SOCK_HIDE_VALUE},
+       {       SOCK_FLOAT, 1, N_("Strength"),          1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1000000.0f, PROP_NONE},
+       {       -1, 0, ""       }
+};
+
+static bNodeSocketTemplate sh_node_tex_ies_out[] = {
+       {       SOCK_FLOAT, 0, N_("Fac")},
+       {       -1, 0, ""       }
+};
+
+static void node_shader_init_tex_ies(bNodeTree *UNUSED(ntree), bNode *node)
+{
+       NodeShaderTexIES *tex = MEM_callocN(sizeof(NodeShaderTexIES), "NodeShaderIESLight");
+       node->storage = tex;
+}
+
+/* node type definition */
+void register_node_type_sh_tex_ies(void)
+{
+       static bNodeType ntype;
+
+       sh_node_type_base(&ntype, SH_NODE_TEX_IES, "IES Texture", NODE_CLASS_TEXTURE, 0);
+       node_type_compatibility(&ntype, NODE_NEW_SHADING);
+       node_type_socket_templates(&ntype, sh_node_tex_ies_in, sh_node_tex_ies_out);
+       node_type_init(&ntype, node_shader_init_tex_ies);
+       node_type_storage(&ntype, "NodeShaderTexIES", node_free_standard_storage, node_copy_standard_storage);
+
+       nodeRegisterType(&ntype);
+}