Changeset View
Changeset View
Standalone View
Standalone View
source/blender/nodes/shader/nodes/node_shader_tex_image.cc
| Show All 13 Lines | |||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| * | * | ||||
| * The Original Code is Copyright (C) 2005 Blender Foundation. | * The Original Code is Copyright (C) 2005 Blender Foundation. | ||||
| * All rights reserved. | * All rights reserved. | ||||
| */ | */ | ||||
| #include "../node_shader_util.h" | #include "../node_shader_util.h" | ||||
| #include "BKE_image.h" | |||||
| #include "BLI_noise.hh" | |||||
| #include "IMB_imbuf.h" | |||||
| #include "IMB_imbuf_types.h" | |||||
| namespace blender::nodes { | namespace blender::nodes { | ||||
| static void sh_node_tex_image_declare(NodeDeclarationBuilder &b) | static void sh_node_tex_image_declare(NodeDeclarationBuilder &b) | ||||
| { | { | ||||
| b.is_function_node(); | b.is_function_node(); | ||||
| b.add_input<decl::Vector>("Vector").implicit_field(); | b.add_input<decl::Vector>("Vector").implicit_field(); | ||||
| b.add_output<decl::Color>("Color").no_muted_links(); | b.add_output<decl::Color>("Color").no_muted_links(); | ||||
| b.add_output<decl::Float>("Alpha").no_muted_links(); | b.add_output<decl::Float>("Alpha").no_muted_links(); | ||||
| ▲ Show 20 Lines • Show All 138 Lines • ▼ Show 20 Lines | else { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| namespace blender::nodes { | |||||
| class ImageFunction : public fn::MultiFunction { | |||||
| private: | |||||
| int interpolation_; | |||||
| int projection_; | |||||
| int extension_; | |||||
| bool alpha_clear_; | |||||
| int alpha_mode_; | |||||
| bool is_tiled_; | |||||
| ImBuf *ibuf_; | |||||
| public: | |||||
| ImageFunction(int interpolation, | |||||
| int projection, | |||||
| int extension, | |||||
| bool alpha_clear, | |||||
| int alpha_mode, | |||||
| bool is_tiled, | |||||
| ImBuf *ibuf) | |||||
| : interpolation_(interpolation), | |||||
| projection_(projection), | |||||
| extension_(extension), | |||||
| alpha_clear_(alpha_clear), | |||||
| alpha_mode_(alpha_mode), | |||||
| is_tiled_(is_tiled), | |||||
| ibuf_(ibuf) | |||||
| { | |||||
| static fn::MFSignature signature = create_signature(); | |||||
| this->set_signature(&signature); | |||||
| } | |||||
| static fn::MFSignature create_signature() | |||||
| { | |||||
| fn::MFSignatureBuilder signature{"ImageFunction"}; | |||||
| signature.single_input<float3>("Vector"); | |||||
| signature.single_output<ColorGeometry4f>("Color"); | |||||
| signature.single_output<float>("Alpha"); | |||||
| return signature.build(); | |||||
| } | |||||
| /* Remap coordinate from 0..1 box to -1..-1. */ | |||||
| static inline float3 point_texco_remap_square(const float3 co) | |||||
| { | |||||
| return co * 2.0f - 1.0f; | |||||
| } | |||||
| /* Projections. */ | |||||
| static inline float3 point_map_to_tube(const float3 co) | |||||
| { | |||||
| float u, v; | |||||
| v = (co.z + 1.0f) * 0.5f; | |||||
| const float len = safe_sqrtf(co.x * co.x + co.y * co.y); | |||||
| if (len > 0.0f) { | |||||
| u = (1.0f - (atan2f(co.x / len, co.y / len) / (float)M_PI)) * 0.5f; | |||||
| } | |||||
| else { | |||||
| v = u = 0.0f; | |||||
| } | |||||
| return float3(u, v, 0.0f); | |||||
| } | |||||
| static inline float3 point_map_to_sphere(const float3 co) | |||||
| { | |||||
| const float len = len_v3(co); | |||||
| float v, u; | |||||
| if (len > 0.0f) { | |||||
| if (co.x == 0.0f && co.y == 0.0f) { | |||||
| u = 0.0f; | |||||
| } | |||||
| else { | |||||
| u = (1.0f - atan2f(co.x, co.y) / (float)M_PI) / 2.0f; | |||||
| } | |||||
| v = 1.0f - safe_acosf(co.z / len) / (float)M_PI; | |||||
| } | |||||
| else { | |||||
| v = u = 0.0f; | |||||
| } | |||||
| return float3(u, v, 0.0f); | |||||
| } | |||||
| /* No projection blending. */ | |||||
| static inline float3 point_map_to_box(const float3 co) | |||||
| { | |||||
| float u, v; | |||||
| const float x1 = fabsf(co.x); | |||||
| const float y1 = fabsf(co.y); | |||||
| const float z1 = fabsf(co.z); | |||||
| if (z1 >= x1 && z1 >= y1) { | |||||
| u = (co.x + 1.0f) / 2.0f; | |||||
| v = (co.y + 1.0f) / 2.0f; | |||||
| } | |||||
| else if (y1 >= x1 && y1 >= z1) { | |||||
| u = (co.x + 1.0f) / 2.0f; | |||||
| v = (co.z + 1.0f) / 2.0f; | |||||
| } | |||||
| else { | |||||
| u = (co.y + 1.0f) / 2.0f; | |||||
| v = (co.z + 1.0f) / 2.0f; | |||||
| } | |||||
| return float3(u, v, 0.0f); | |||||
| } | |||||
| static inline int wrap_periodic(int x, const int width) | |||||
| { | |||||
| x %= width; | |||||
| if (x < 0) | |||||
| x += width; | |||||
| return x; | |||||
| } | |||||
| static inline int wrap_clamp(const int x, const int width) | |||||
| { | |||||
| return std::clamp(x, 0, width - 1); | |||||
| } | |||||
| static inline float4 image_pixel_lookup(const ImBuf *ibuf, const int px, const int py) | |||||
| { | |||||
| const float *result; | |||||
| // result = ibuf->rect_float + py * ibuf->x * 4 + px * 4; | |||||
| result = ibuf->rect_float + ((px + py * ibuf->x) * 4); | |||||
| /* Clamp 16bits floats limits. Higher/Lower values produce +/-inf. */ | |||||
| float4::clamp(result, -65520.0f, 65520.0f); | |||||
| return result; | |||||
| } | |||||
| static inline float frac(const float x, int *ix) | |||||
| { | |||||
| const int i = (int)x - ((x < 0.0f) ? 1 : 0); | |||||
| *ix = i; | |||||
| return x - (float)i; | |||||
| } | |||||
| static float4 image_cubic_texture_lookup(const ImBuf *ibuf, | |||||
| const float px, | |||||
| const float py, | |||||
| const int extension) | |||||
| { | |||||
| const int width = ibuf->x; | |||||
| const int height = ibuf->y; | |||||
| int ix, iy, nix, niy; | |||||
| const float tx = frac(px * (float)width - 0.5f, &ix); | |||||
| const float ty = frac(py * (float)width - 0.5f, &iy); | |||||
| int pix, piy, nnix, nniy; | |||||
| switch (extension) { | |||||
| case SHD_IMAGE_EXTENSION_REPEAT: | |||||
| ix = wrap_periodic(ix, width); | |||||
| iy = wrap_periodic(iy, height); | |||||
| pix = wrap_periodic(ix - 1, width); | |||||
| piy = wrap_periodic(iy - 1, height); | |||||
| nix = wrap_periodic(ix + 1, width); | |||||
| niy = wrap_periodic(iy + 1, height); | |||||
| nnix = wrap_periodic(ix + 2, width); | |||||
| nniy = wrap_periodic(iy + 2, height); | |||||
| break; | |||||
| case SHD_IMAGE_EXTENSION_CLIP: | |||||
| if (tx < 0.0f || ty < 0.0f || tx > 1.0f || ty > 1.0f) { | |||||
| return float4(0.0f, 0.0f, 0.0f, 0.0f); | |||||
| } | |||||
| ATTR_FALLTHROUGH; | |||||
| case SHD_IMAGE_EXTENSION_EXTEND: | |||||
| pix = wrap_clamp(ix - 1, width); | |||||
| piy = wrap_clamp(iy - 1, height); | |||||
| nix = wrap_clamp(ix + 1, width); | |||||
| niy = wrap_clamp(iy + 1, height); | |||||
| nnix = wrap_clamp(ix + 2, width); | |||||
| nniy = wrap_clamp(iy + 2, height); | |||||
| ix = wrap_clamp(ix, width); | |||||
| iy = wrap_clamp(iy, height); | |||||
| break; | |||||
| default: | |||||
| return float4(0.0f, 0.0f, 0.0f, 0.0f); | |||||
| } | |||||
| const int xc[4] = {pix, ix, nix, nnix}; | |||||
| const int yc[4] = {piy, iy, niy, nniy}; | |||||
| float u[4], v[4]; | |||||
| u[0] = (((-1.0f / 6.0f) * tx + 0.5f) * tx - 0.5f) * tx + (1.0f / 6.0f); | |||||
| u[1] = ((0.5f * tx - 1.0f) * tx) * tx + (2.0f / 3.0f); | |||||
| u[2] = ((-0.5f * tx + 0.5f) * tx + 0.5f) * tx + (1.0f / 6.0f); | |||||
| u[3] = (1.0f / 6.0f) * tx * tx * tx; | |||||
| v[0] = (((-1.0f / 6.0f) * ty + 0.5f) * ty - 0.5f) * ty + (1.0f / 6.0f); | |||||
| v[1] = ((0.5f * ty - 1.0f) * ty) * ty + (2.0f / 3.0f); | |||||
| v[2] = ((-0.5f * ty + 0.5f) * ty + 0.5f) * ty + (1.0f / 6.0f); | |||||
| v[3] = (1.0f / 6.0f) * ty * ty * ty; | |||||
| return (v[0] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[0])) + | |||||
| u[1] * (image_pixel_lookup(ibuf, xc[1], yc[0])) + | |||||
| u[2] * (image_pixel_lookup(ibuf, xc[2], yc[0])) + | |||||
| u[3] * (image_pixel_lookup(ibuf, xc[3], yc[0])))) + | |||||
| (v[1] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[1])) + | |||||
| u[1] * (image_pixel_lookup(ibuf, xc[1], yc[1])) + | |||||
| u[2] * (image_pixel_lookup(ibuf, xc[2], yc[1])) + | |||||
| u[3] * (image_pixel_lookup(ibuf, xc[3], yc[1])))) + | |||||
| (v[2] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[2])) + | |||||
| u[1] * (image_pixel_lookup(ibuf, xc[1], yc[2])) + | |||||
| u[2] * (image_pixel_lookup(ibuf, xc[2], yc[2])) + | |||||
| u[3] * (image_pixel_lookup(ibuf, xc[3], yc[2])))) + | |||||
| (v[3] * (u[0] * (image_pixel_lookup(ibuf, xc[0], yc[3])) + | |||||
| u[1] * (image_pixel_lookup(ibuf, xc[1], yc[3])) + | |||||
| u[2] * (image_pixel_lookup(ibuf, xc[2], yc[3])) + | |||||
| u[3] * (image_pixel_lookup(ibuf, xc[3], yc[3])))); | |||||
| } | |||||
| static float4 image_linear_texture_lookup(const ImBuf *ibuf, | |||||
| const float px, | |||||
| const float py, | |||||
| const int extension) | |||||
| { | |||||
| const int width = ibuf->x; | |||||
| const int height = ibuf->y; | |||||
| int ix, iy, nix, niy; | |||||
| const float tx = frac(px * (float)width - 0.5f, &ix); | |||||
| const float ty = frac(py * (float)width - 0.5f, &iy); | |||||
| switch (extension) { | |||||
| case SHD_IMAGE_EXTENSION_CLIP: | |||||
| if (tx < 0.0f || ty < 0.0f || tx > 1.0f || ty > 1.0f) { | |||||
| return float4(0.0f, 0.0f, 0.0f, 0.0f); | |||||
| } | |||||
| ATTR_FALLTHROUGH; | |||||
| case SHD_IMAGE_EXTENSION_EXTEND: | |||||
| nix = wrap_clamp(ix + 1, width); | |||||
| niy = wrap_clamp(iy + 1, height); | |||||
| ix = wrap_clamp(ix, width); | |||||
| iy = wrap_clamp(iy, height); | |||||
| break; | |||||
| default: | |||||
| case SHD_IMAGE_EXTENSION_REPEAT: | |||||
| ix = wrap_periodic(ix, width); | |||||
| iy = wrap_periodic(iy, height); | |||||
| nix = wrap_periodic(ix + 1, width); | |||||
| niy = wrap_periodic(iy + 1, height); | |||||
| break; | |||||
| } | |||||
| return (float4(1.0f) - ty) * (float4(1.0f) - tx) * image_pixel_lookup(ibuf, ix, iy) + | |||||
| (float4(1.0f) - ty) * tx * image_pixel_lookup(ibuf, nix, iy) + | |||||
| ty * (float4(1.0f) - tx) * image_pixel_lookup(ibuf, ix, niy) + | |||||
| ty * tx * image_pixel_lookup(ibuf, nix, niy); | |||||
| } | |||||
| static float4 image_closest_texture_lookup(const ImBuf *ibuf, | |||||
| const float px, | |||||
| const float py, | |||||
| const int extension) | |||||
| { | |||||
| const int width = ibuf->x; | |||||
| const int height = ibuf->y; | |||||
| int ix, iy; | |||||
| const float tx = frac(px * (float)width - 0.5f, &ix); | |||||
| const float ty = frac(py * (float)width - 0.5f, &iy); | |||||
| switch (extension) { | |||||
| case SHD_IMAGE_EXTENSION_REPEAT: | |||||
| ix = wrap_periodic(ix, width); | |||||
| iy = wrap_periodic(iy, height); | |||||
| break; | |||||
| case SHD_IMAGE_EXTENSION_CLIP: | |||||
| if (tx < 0.0f || ty < 0.0f || tx > 1.0f || ty > 1.0f) { | |||||
| return float4(0.0f, 0.0f, 0.0f, 0.0f); | |||||
| } | |||||
| ATTR_FALLTHROUGH; | |||||
| case SHD_IMAGE_EXTENSION_EXTEND: | |||||
| ix = wrap_clamp(ix, width); | |||||
| iy = wrap_clamp(iy, height); | |||||
| break; | |||||
| default: | |||||
| return float4(0.0f, 0.0f, 0.0f, 0.0f); | |||||
| } | |||||
| return image_pixel_lookup(ibuf, ix, iy); | |||||
| } | |||||
| void call(IndexMask mask, fn::MFParams params, fn::MFContext UNUSED(context)) const override | |||||
| { | |||||
| const VArray<float3> &vector = params.readonly_single_input<float3>(0, "Vector"); | |||||
| MutableSpan<ColorGeometry4f> r_color = params.uninitialized_single_output<ColorGeometry4f>( | |||||
| 1, "Color"); | |||||
| MutableSpan<float> r_alpha = params.uninitialized_single_output_if_required<float>(2, "Alpha"); | |||||
| const bool output_color = !r_color.is_empty(); | |||||
| const bool output_alpha = !r_alpha.is_empty(); | |||||
| if (ibuf_) { | |||||
| if (!ibuf_->rect_float) { | |||||
| BLI_thread_lock(LOCK_IMAGE); | |||||
| if (!ibuf_->rect_float) { | |||||
| IMB_float_from_rect(ibuf_); | |||||
| } | |||||
| BLI_thread_unlock(LOCK_IMAGE); | |||||
| if (!ibuf_->rect_float) { | |||||
| return; | |||||
| } | |||||
| } | |||||
| /* Set flags. */ | |||||
| const bool use_cubic = ELEM(interpolation_, SHD_INTERP_CUBIC, SHD_INTERP_SMART); | |||||
| const bool use_linear = interpolation_ == SHD_INTERP_LINEAR; | |||||
| for (int64_t i : mask) { | |||||
| float4 color_data; | |||||
| float3 p = vector[i]; | |||||
| /* Projection. */ | |||||
| if (projection_ == SHD_PROJ_TUBE) { | |||||
| p = point_texco_remap_square(p); | |||||
| p = point_map_to_tube(p); | |||||
| } | |||||
| else if (projection_ == SHD_PROJ_SPHERE) { | |||||
| p = point_texco_remap_square(p); | |||||
| p = point_map_to_sphere(p); | |||||
| } | |||||
| else if (projection_ == SHD_PROJ_BOX) { | |||||
| /* Straight box projection, no normal data. */ | |||||
| p = point_map_to_box(p); | |||||
| } | |||||
| /* Sample image texture. */ | |||||
| if (use_cubic) { | |||||
| color_data = image_cubic_texture_lookup(ibuf_, p.x, p.y, extension_); | |||||
| } | |||||
| else if (use_linear) { | |||||
| color_data = image_linear_texture_lookup(ibuf_, p.x, p.y, extension_); | |||||
| } | |||||
| else { | |||||
| color_data = image_closest_texture_lookup(ibuf_, p.x, p.y, extension_); | |||||
| } | |||||
| /* Handle alpha. */ | |||||
| if (alpha_clear_) { | |||||
| /* Don't let alpha affect color output. */ | |||||
| color_data.w = 1.0f; | |||||
| } | |||||
| else { | |||||
| /* Output premultiplied alpha depending on alpha socket usage. This makes | |||||
| * it so that if we blend the color with a transparent shader using alpha as | |||||
| * a factor, we don't multiply alpha into the color twice. And if we do | |||||
| * not, then there will be no artifacts from zero alpha areas. */ | |||||
| if (alpha_mode_ == IMA_ALPHA_PREMUL) { | |||||
| if (output_alpha) { | |||||
| premul_to_straight_v4(color_data); | |||||
| } | |||||
| else { | |||||
| color_data.w = 1.0f; | |||||
| } | |||||
| } | |||||
| else { | |||||
| if (output_alpha) { | |||||
| color_data.w = 1.0f; | |||||
| } | |||||
| else { | |||||
| straight_to_premul_v4(color_data); | |||||
| } | |||||
| } | |||||
| } | |||||
| if (output_color) { | |||||
| r_color[i] = (ColorGeometry4f)color_data; | |||||
| } | |||||
| if (output_alpha) { | |||||
| r_alpha[i] = color_data.w; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| }; | |||||
| static void sh_node_image_tex_build_multi_function( | |||||
| blender::nodes::NodeMultiFunctionBuilder &builder) | |||||
| { | |||||
| bNode &node = builder.node(); | |||||
| NodeTexImage *tex = (NodeTexImage *)node.storage; | |||||
| Image *ima = (Image *)node.id; | |||||
| if (ima) { | |||||
| /* We get the image user from the original node, since GPU image keeps | |||||
| * a pointer to it and the dependency refreshes the original. */ | |||||
| bNode *node_original = node.original ? node.original : &node; | |||||
| NodeTexImage *tex_original = (NodeTexImage *)node_original->storage; | |||||
| ImageUser *iuser = &tex_original->iuser; | |||||
| void *lock; | |||||
| ImBuf *ibuf = BKE_image_acquire_ibuf(ima, iuser, &lock); | |||||
| if (ibuf) { | |||||
| bool is_tiled = ima->source == IMA_SRC_TILED; | |||||
| bool alpha_clear = ELEM(ima->alpha_mode, IMA_ALPHA_IGNORE, IMA_ALPHA_CHANNEL_PACKED) || | |||||
| IMB_colormanagement_space_name_is_data(ima->colorspace_settings.name); | |||||
| builder.construct_and_set_matching_fn<ImageFunction>(tex->interpolation, | |||||
| tex->projection, | |||||
| tex->extension, | |||||
| alpha_clear, | |||||
| ima->alpha_mode, | |||||
| is_tiled, | |||||
| ibuf); | |||||
| BKE_image_release_ibuf(ima, ibuf, lock); | |||||
| } | |||||
| } | |||||
| } | |||||
| } // namespace blender::nodes | |||||
| /* node type definition */ | /* node type definition */ | ||||
| void register_node_type_sh_tex_image(void) | void register_node_type_sh_tex_image(void) | ||||
| { | { | ||||
| static bNodeType ntype; | static bNodeType ntype; | ||||
| sh_node_type_base(&ntype, SH_NODE_TEX_IMAGE, "Image Texture", NODE_CLASS_TEXTURE, 0); | sh_fn_node_type_base(&ntype, SH_NODE_TEX_IMAGE, "Image Texture", NODE_CLASS_TEXTURE, 0); | ||||
| ntype.declare = blender::nodes::sh_node_tex_image_declare; | ntype.declare = blender::nodes::sh_node_tex_image_declare; | ||||
| node_type_init(&ntype, node_shader_init_tex_image); | node_type_init(&ntype, node_shader_init_tex_image); | ||||
| node_type_storage( | node_type_storage( | ||||
| &ntype, "NodeTexImage", node_free_standard_storage, node_copy_standard_storage); | &ntype, "NodeTexImage", node_free_standard_storage, node_copy_standard_storage); | ||||
| node_type_gpu(&ntype, node_shader_gpu_tex_image); | node_type_gpu(&ntype, node_shader_gpu_tex_image); | ||||
| node_type_label(&ntype, node_image_label); | node_type_label(&ntype, node_image_label); | ||||
| node_type_size_preset(&ntype, NODE_SIZE_LARGE); | node_type_size_preset(&ntype, NODE_SIZE_LARGE); | ||||
| ntype.build_multi_function = blender::nodes::sh_node_image_tex_build_multi_function; | |||||
| nodeRegisterType(&ntype); | nodeRegisterType(&ntype); | ||||
| } | } | ||||