Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Stockner <lukas.stockner@freenet.de>2018-05-27 01:46:37 +0300
committerLukas Stockner <lukas.stockner@freenet.de>2018-05-27 02:24:57 +0300
commit48155c210a3fd65f6fc2b5be3357b47a8af3711a (patch)
treea937261c81b959a79e8a2521a44b87a2d73fa749
parentef502854feb6b81119954206bff414d4507f4f3c (diff)
Cycles: Add Support for IES files as textures for light strength
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
-rw-r--r--intern/cycles/blender/blender_shader.cpp13
-rw-r--r--intern/cycles/blender/blender_util.h15
-rw-r--r--intern/cycles/graph/node_type.h13
-rw-r--r--intern/cycles/kernel/CMakeLists.txt1
-rw-r--r--intern/cycles/kernel/kernel_light.h4
-rw-r--r--intern/cycles/kernel/kernel_textures.h3
-rw-r--r--intern/cycles/kernel/osl/osl_services.cpp8
-rw-r--r--intern/cycles/kernel/shaders/CMakeLists.txt1
-rw-r--r--intern/cycles/kernel/shaders/node_ies_light.osl42
-rw-r--r--intern/cycles/kernel/svm/svm.h4
-rw-r--r--intern/cycles/kernel/svm/svm_ies.h110
-rw-r--r--intern/cycles/kernel/svm/svm_types.h1
-rw-r--r--intern/cycles/render/graph.cpp6
-rw-r--r--intern/cycles/render/light.cpp122
-rw-r--r--intern/cycles/render/light.h17
-rw-r--r--intern/cycles/render/nodes.cpp100
-rw-r--r--intern/cycles/render/nodes.h22
-rw-r--r--intern/cycles/render/osl.cpp9
-rw-r--r--intern/cycles/render/osl.h5
-rw-r--r--intern/cycles/render/scene.cpp3
-rw-r--r--intern/cycles/render/scene.h3
-rw-r--r--intern/cycles/render/svm.cpp7
-rw-r--r--intern/cycles/render/svm.h5
-rw-r--r--intern/cycles/util/CMakeLists.txt2
-rw-r--r--intern/cycles/util/util_ies.cpp392
-rw-r--r--intern/cycles/util/util_ies.h61
-rw-r--r--intern/cycles/util/util_math.h11
-rw-r--r--release/scripts/startup/nodeitems_builtins.py1
-rw-r--r--source/blender/blenkernel/BKE_node.h1
-rw-r--r--source/blender/blenkernel/intern/bpath.c8
-rw-r--r--source/blender/blenkernel/intern/node.c1
-rw-r--r--source/blender/editors/space_node/drawnode.c18
-rw-r--r--source/blender/makesdna/DNA_node_types.h9
-rw-r--r--source/blender/makesrna/RNA_access.h1
-rw-r--r--source/blender/makesrna/intern/rna_nodetree.c56
-rw-r--r--source/blender/nodes/CMakeLists.txt1
-rw-r--r--source/blender/nodes/NOD_shader.h1
-rw-r--r--source/blender/nodes/NOD_static_types.h1
-rw-r--r--source/blender/nodes/shader/nodes/node_shader_ies_light.c61
39 files changed, 1119 insertions, 20 deletions
diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp
index eb9968a85c2..c6144cef1bf 100644
--- a/intern/cycles/blender/blender_shader.cpp
+++ b/intern/cycles/blender/blender_shader.cpp
@@ -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();
diff --git a/intern/cycles/blender/blender_util.h b/intern/cycles/blender/blender_util.h
index c418b19a637..87d6c7eba8a 100644
--- a/intern/cycles/blender/blender_util.h
+++ b/intern/cycles/blender/blender_util.h
@@ -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,
diff --git a/intern/cycles/graph/node_type.h b/intern/cycles/graph/node_type.h
index 7d46e31ce24..15d34a79bb8 100644
--- a/intern/cycles/graph/node_type.h
+++ b/intern/cycles/graph/node_type.h
@@ -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;
diff --git a/intern/cycles/kernel/CMakeLists.txt b/intern/cycles/kernel/CMakeLists.txt
index 9b7f4e00084..457602ae4e7 100644
--- a/intern/cycles/kernel/CMakeLists.txt
+++ b/intern/cycles/kernel/CMakeLists.txt
@@ -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
diff --git a/intern/cycles/kernel/kernel_light.h b/intern/cycles/kernel/kernel_light.h
index efab69ee37d..ec7203d36eb 100644
--- a/intern/cycles/kernel/kernel_light.h
+++ b/intern/cycles/kernel/kernel_light.h
@@ -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 */
diff --git a/intern/cycles/kernel/kernel_textures.h b/intern/cycles/kernel/kernel_textures.h
index 9047b93a0b2..a7b8c492ee9 100644
--- a/intern/cycles/kernel/kernel_textures.h
+++ b/intern/cycles/kernel/kernel_textures.h
@@ -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
diff --git a/intern/cycles/kernel/osl/osl_services.cpp b/intern/cycles/kernel/osl/osl_services.cpp
index 0c5e5e30e47..32d86b7192a 100644
--- a/intern/cycles/kernel/osl/osl_services.cpp
+++ b/intern/cycles/kernel/osl/osl_services.cpp
@@ -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];
diff --git a/intern/cycles/kernel/shaders/CMakeLists.txt b/intern/cycles/kernel/shaders/CMakeLists.txt
index 6ec651a96d8..b28d017c1c2 100644
--- a/intern/cycles/kernel/shaders/CMakeLists.txt
+++ b/intern/cycles/kernel/shaders/CMakeLists.txt
@@ -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
index 00000000000..a0954e3a444
--- /dev/null
+++ b/intern/cycles/kernel/shaders/node_ies_light.osl
@@ -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);
+}
diff --git a/intern/cycles/kernel/svm/svm.h b/intern/cycles/kernel/svm/svm.h
index 39cd5da7b12..bfa146f2d93 100644
--- a/intern/cycles/kernel/svm/svm.h
+++ b/intern/cycles/kernel/svm/svm.h
@@ -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
index 00000000000..6130c3348b0
--- /dev/null
+++ b/intern/cycles/kernel/svm/svm_ies.h
@@ -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
diff --git a/intern/cycles/kernel/svm/svm_types.h b/intern/cycles/kernel/svm/svm_types.h
index dc62e25b340..ac24d23ecd2 100644
--- a/intern/cycles/kernel/svm/svm_types.h
+++ b/intern/cycles/kernel/svm/svm_types.h
@@ -136,6 +136,7 @@ typedef enum ShaderNodeType {
NODE_DISPLACEMENT,
NODE_VECTOR_DISPLACEMENT,
NODE_PRINCIPLED_VOLUME,
+ NODE_IES,
} ShaderNodeType;
typedef enum NodeAttributeType {
diff --git a/intern/cycles/render/graph.cpp b/intern/cycles/render/graph.cpp
index 096de878e51..59e1a12c3a1 100644
--- a/intern/cycles/render/graph.cpp
+++ b/intern/cycles/render/graph.cpp
@@ -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();
diff --git a/intern/cycles/render/light.cpp b/intern/cycles/render/light.cpp
index 8dec7e4ea64..f0824ef4319 100644
--- a/intern/cycles/render/light.cpp
+++ b/intern/cycles/render/light.cpp
@@ -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
diff --git a/intern/cycles/render/light.h b/intern/cycles/render/light.h
index 97b7b971c73..5f8677ee2f2 100644
--- a/intern/cycles/render/light.h
+++ b/intern/cycles/render/light.h
@@ -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
diff --git a/intern/cycles/render/nodes.cpp b/intern/cycles/render/nodes.cpp
index c468924fa66..3dad4d1a346 100644
--- a/intern/cycles/render/nodes.cpp
+++ b/intern/cycles/render/nodes.cpp
@@ -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);
diff --git a/intern/cycles/render/nodes.h b/intern/cycles/render/nodes.h
index 58c3d472cd3..35a7df690c3 100644
--- a/intern/cycles/render/nodes.h
+++ b/intern/cycles/render/nodes.h
@@ -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)
diff --git a/intern/cycles/render/osl.cpp b/intern/cycles/render/osl.cpp
index f1a22350060..dde622bff8a 100644
--- a/intern/cycles/render/osl.cpp
+++ b/intern/cycles/render/osl.cpp
@@ -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;
diff --git a/intern/cycles/render/osl.h b/intern/cycles/render/osl.h
index 95e35dd857b..7a3208c402a 100644
--- a/intern/cycles/render/osl.h
+++ b/intern/cycles/render/osl.h
@@ -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
diff --git a/intern/cycles/render/scene.cpp b/intern/cycles/render/scene.cpp
index ba47e3ab6f8..b35cdbd3db5 100644
--- a/intern/cycles/render/scene.cpp
+++ b/intern/cycles/render/scene.cpp
@@ -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));
}
diff --git a/intern/cycles/render/scene.h b/intern/cycles/render/scene.h
index 04bd4735a86..6c67433c9fc 100644
--- a/intern/cycles/render/scene.h
+++ b/intern/cycles/render/scene.h
@@ -119,6 +119,9 @@ public:
/* integrator */
device_vector<uint> sobol_directions;
+ /* ies lights */
+ device_vector<float> ies_lights;
+
KernelData data;
DeviceScene(Device *device);
diff --git a/intern/cycles/render/svm.cpp b/intern/cycles/render/svm.cpp
index c5b4060d5c3..eb8a35a271f 100644
--- a/intern/cycles/render/svm.cpp
+++ b/intern/cycles/render/svm.cpp
@@ -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;
diff --git a/intern/cycles/render/svm.h b/intern/cycles/render/svm.h
index 18be0fa9a22..7cf1e4ad791 100644
--- a/intern/cycles/render/svm.h
+++ b/intern/cycles/render/svm.h
@@ -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:
diff --git a/intern/cycles/util/CMakeLists.txt b/intern/cycles/util/CMakeLists.txt
index 24043e2231b..3b690860d53 100644
--- a/intern/cycles/util/CMakeLists.txt
+++ b/intern/cycles/util/CMakeLists.txt
@@ -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
index 00000000000..4824c886609
--- /dev/null
+++ b/intern/cycles/util/util_ies.cpp
@@ -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
index 00000000000..5933cb3962a
--- /dev/null
+++ b/intern/cycles/util/util_ies.h
@@ -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__ */
diff --git a/intern/cycles/util/util_math.h b/intern/cycles/util/util_math.h
index d0e91a2a1c9..fd3199f209f 100644
--- a/intern/cycles/util/util_math.h
+++ b/intern/cycles/util/util_math.h
@@ -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"
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py
index 7089f691ff2..60a8471ca3b 100644
--- a/release/scripts/startup/nodeitems_builtins.py
+++ b/release/scripts/startup/nodeitems_builtins.py
@@ -259,6 +259,7 @@ shader_node_categories = [
NodeItem("ShaderNodeTexChecker"),
NodeItem("ShaderNodeTexBrick"),
NodeItem("ShaderNodeTexPointDensity"),
+ NodeItem("ShaderNodeTexIES"),
]),
ShaderNewNodeCategory("SH_NEW_OP_COLOR", "Color", items=[
NodeItem("ShaderNodeMixRGB"),
diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h
index efad8e48e3d..bd3b191c96c 100644
--- a/source/blender/blenkernel/BKE_node.h
+++ b/source/blender/blenkernel/BKE_node.h
@@ -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
diff --git a/source/blender/blenkernel/intern/bpath.c b/source/blender/blenkernel/intern/bpath.c
index e9a8de4469d..cc4c28e8016 100644
--- a/source/blender/blenkernel/intern/bpath.c
+++ b/source/blender/blenkernel/intern/bpath.c
@@ -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;
diff --git a/source/blender/blenkernel/intern/node.c b/source/blender/blenkernel/intern/node.c
index 5d4b1ae8170..521874bb14f 100644
--- a/source/blender/blenkernel/intern/node.c
+++ b/source/blender/blenkernel/intern/node.c
@@ -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)
diff --git a/source/blender/editors/space_node/drawnode.c b/source/blender/editors/space_node/drawnode.c
index 5665a5cff2d..ea8d4b36d3b 100644
--- a/source/blender/editors/space_node/drawnode.c
+++ b/source/blender/editors/space_node/drawnode.c
@@ -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;
diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h
index 8d3ab29a2fb..95101ac1e51 100644
--- a/source/blender/makesdna/DNA_node_types.h
+++ b/source/blender/makesdna/DNA_node_types.h
@@ -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 */
diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h
index e512aebfa71..73beee51a8b 100644
--- a/source/blender/makesrna/RNA_access.h
+++ b/source/blender/makesrna/RNA_access.h
@@ -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;
diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c
index f5c464c0758..8dbb133a8ac 100644
--- a/source/blender/makesrna/intern/rna_nodetree.c
+++ b/source/blender/makesrna/intern/rna_nodetree.c
@@ -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;
diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt
index 5247599bdf7..cc0bef30047 100644
--- a/source/blender/nodes/CMakeLists.txt
+++ b/source/blender/nodes/CMakeLists.txt
@@ -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
diff --git a/source/blender/nodes/NOD_shader.h b/source/blender/nodes/NOD_shader.h
index b00307ed7fb..7cb52cda550 100644
--- a/source/blender/nodes/NOD_shader.h
+++ b/source/blender/nodes/NOD_shader.h
@@ -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
diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h
index bc90e33ed03..5864b3deb39 100644
--- a/source/blender/nodes/NOD_static_types.h
+++ b/source/blender/nodes/NOD_static_types.h
@@ -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
index 00000000000..8084f445e34
--- /dev/null
+++ b/source/blender/nodes/shader/nodes/node_shader_ies_light.c
@@ -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);
+}