diff options
author | Campbell Barton <ideasman42@gmail.com> | 2018-05-27 12:06:29 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2018-05-27 12:06:29 +0300 |
commit | 9a74b603670cc3fab14d9fcd7be7b3c17731a8ee (patch) | |
tree | 0611021c1450e586308d58223353793dcc137afe /intern | |
parent | 474971f3d84e5eacde7e92fc86ef4e10a5e0786c (diff) | |
parent | bc3727a94374d9c56745c943a1e6574627a88510 (diff) |
Merge branch 'master' into blender2.8
Diffstat (limited to 'intern')
27 files changed, 960 insertions, 20 deletions
diff --git a/intern/cycles/blender/blender_shader.cpp b/intern/cycles/blender/blender_shader.cpp index cc2a6824190..15d633b5c9a 100644 --- a/intern/cycles/blender/blender_shader.cpp +++ b/intern/cycles/blender/blender_shader.cpp @@ -811,6 +811,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 a8379914b83..983988f4e36 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 cd879e27e72..b1be0b2155a 100644 --- a/intern/cycles/kernel/kernel_light.h +++ b/intern/cycles/kernel/kernel_light.h @@ -224,7 +224,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 */ @@ -250,7 +250,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 01a8b50cb21..91f9b4e3bec 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" @@ -176,6 +178,9 @@ LightManager::LightManager() LightManager::~LightManager() { + foreach(IESSlot *slot, ies_slots) { + delete slot; + } } bool LightManager::has_background_light(Scene *scene) @@ -865,6 +870,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); @@ -879,6 +887,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*/) @@ -886,5 +895,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 539a33f4e41..acbbdd15ddf 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" @@ -87,6 +89,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, @@ -116,9 +123,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" |