diff --git a/src/textures/bump2normal.cpp b/src/textures/bump2normal.cpp index 63b691c1..ac0d650e 100644 --- a/src/textures/bump2normal.cpp +++ b/src/textures/bump2normal.cpp @@ -12,87 +12,26 @@ namespace luisa::render { using namespace luisa::compute; -/** -* # bump_image = np.power( - # cv.imread(material_bump, cv.IMREAD_GRAYSCALE) / 255, 2.2 - # ) - # h, w = bump_image.shape[:2] - # scale = 4 - # bump_image = cv.resize(bump_image, (w * scale, h * scale), interpolation=cv.INTER_LANCZOS4) - # bump_image = cv.GaussianBlur(bump_image, (5, 5), 0) - # dx_image = cv.copyMakeBorder(bump_image, 0, 0, 1, 1, cv.BORDER_REPLICATE) - # dy_image = cv.copyMakeBorder(bump_image, 1, 1, 0, 0, cv.BORDER_REPLICATE) - # strength = min(w, h) / 50 * scale - # dx_image = np.clip(strength * (dx_image[:, 2:] - dx_image[:, :-2]), -5, 5) - # dy_image = np.clip(-strength * (dy_image[2:, :] - dy_image[:-2, :]), -5, 5) - # dx_image = cv.resize(dx_image, (w, h), interpolation=cv.INTER_AREA) - # dy_image = cv.resize(dy_image, (w, h), interpolation=cv.INTER_AREA) - # dz_image = np.ones_like(dx_image) - # norm = np.sqrt(dx_image**2 + dy_image**2 + dz_image**2) - # normal_image = np.dstack([dz_image, dy_image, dx_image]) - # normal_image = (normal_image / norm[:, :, np.newaxis]) * 0.5 + 0.5 - # # normal_image = cv.GaussianBlur(normal_image, (3, 3), 0) - # # normal_image = cv.resize(normal_image, (w, h), interpolation=cv.INTER_CUBIC) - # normal_image = np.uint8(np.clip(normal_image * 255, 0, 255)) - # save_name = f"lr_exported_textures/{material_bump.split('/')[-1]}" - # cv.imwrite(save_name, normal_image) -*/ - -inline auto builtin_gaussian_filter_desc(uint kernel_size) noexcept { - static const auto nodes = [] { - auto make_desc = [](uint kernel_size) noexcept { - auto desc = luisa::make_shared<SceneNodeDesc>( - luisa::format("__bump2normal_texture_builtin_gaussian{}x{}", kernel_size, kernel_size), - SceneNodeTag::FILTER); - desc->define(SceneNodeTag::FILTER, "Gaussian", {}); - auto radius = (kernel_size + 1u) / 2.f; - auto sigma = radius / 3.f; - desc->add_property("radius", radius); - desc->add_property("sigma", sigma); - return eastl::make_pair(std::move(kernel_size), std::move(desc)); - }; - using namespace std::string_view_literals; - return luisa::fixed_map<uint, luisa::shared_ptr<SceneNodeDesc>, 2>{ - make_desc(3), - make_desc(5)}; - }(); - auto iter = nodes.find(kernel_size); - return iter == nodes.cend() ? nullptr : iter->second.get(); -} - class Bump2NormalTexture final : public Texture { private: Texture *_bump_texture; - Filter *_gaussian5x5, *_gaussian3x3;// TODO - float _scale{1.f}; public: Bump2NormalTexture(Scene *scene, const SceneNodeDesc *desc) noexcept - : Texture{scene, desc} { - _scale = desc->property_float_or_default("scale", 1.f); - - _bump_texture = scene->load_texture(desc->property_node("bump")); + : Texture{scene, desc}, + _bump_texture{scene->load_texture(desc->property_node("bump"))} { if (_bump_texture->channels() != 1u) { LUISA_WARNING_WITH_LOCATION("Bump image {} should only have 1 channel. {} found.", desc->identifier(), _bump_texture->channels()); } - LUISA_ASSERT(all(_bump_texture->resolution() >= 3u), - "Bump image {} resolution conflicts with the algorithm.", - desc->identifier()); - - _gaussian5x5 = scene->load_filter(builtin_gaussian_filter_desc(5)); - _gaussian3x3 = scene->load_filter(builtin_gaussian_filter_desc(3)); } - // [[nodiscard]] auto bump_texture() const noexcept { return _bump_texture; } [[nodiscard]] bool is_black() const noexcept override { return false; } [[nodiscard]] bool is_constant() const noexcept override { return false; } [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } [[nodiscard]] uint channels() const noexcept override { return 3u; } - [[nodiscard]] uint2 resolution() const noexcept override { - return _bump_texture->resolution(); - } + [[nodiscard]] uint2 resolution() const noexcept override { return _bump_texture->resolution(); } [[nodiscard]] luisa::unique_ptr<Instance> build( Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; }; @@ -100,129 +39,32 @@ class Bump2NormalTexture final : public Texture { class Bump2NormalTextureInstance final : public Texture::Instance { private: - uint _normal_texture_id; + const Texture::Instance *_bump; public: Bump2NormalTextureInstance(Pipeline &pipeline, - const Texture *texture, - uint normal_texture_id) noexcept - : Texture::Instance{pipeline, texture}, - _normal_texture_id{normal_texture_id} {} - [[nodiscard]] Float4 evaluate( - const Interaction &it, Expr<float> time) const noexcept override { - auto v = pipeline().tex2d(_normal_texture_id).sample(it.uv()); - return v; + const Texture *node, + const Texture::Instance *bump) noexcept + : Texture::Instance{pipeline, node}, _bump{bump} {} + [[nodiscard]] Float4 evaluate(const Interaction &it, Expr<float> time) const noexcept override { + auto n = def(make_float3(0.f)); + $outline { + auto size = static_cast<float>(std::max(node()->resolution().x, node()->resolution().y)); + auto step = std::min(1.f / size, 1.f / 256.f); + auto dx = _bump->evaluate(Interaction{it.uv() + make_float2(step, 0.f)}, time).x - + _bump->evaluate(Interaction{it.uv() - make_float2(step, 0.f)}, time).x; + auto dy = _bump->evaluate(Interaction{it.uv() + make_float2(0.f, step)}, time).x - + _bump->evaluate(Interaction{it.uv() - make_float2(0.f, step)}, time).x; + n = normalize(make_float3(dx / (2.f * step), dy / (2.f * step), 1.f)); + }; + return make_float4(n * 0.5f + 0.5f, 1.f); } }; luisa::unique_ptr<Texture::Instance> Bump2NormalTexture::build( Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { - - Clock clk; - - auto bump_texture = pipeline.build_texture(command_buffer, _bump_texture); - auto resolution = bump_texture->node()->resolution(); - auto resolution_scaled = make_uint2(resolution.x * _scale, resolution.y * _scale); - auto strength = std::min(resolution.x, resolution.y) * _scale; - - auto bump_scaled = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); - auto gaussian_blurred = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); - auto dx = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); - auto dy = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); - auto normal = pipeline.create<Image<float>>(PixelStorage::FLOAT4, resolution, 1u); - - Kernel2D scale_kernel = [&](ImageFloat target) { - auto dispatch_id = compute::dispatch_id().xy(); - auto resolution = dispatch_size().xy(); - auto uv = make_float2( - dispatch_id.x / Float(resolution.x), - dispatch_id.y / Float(resolution.y)); - auto value = bump_texture->evaluate(Interaction(uv), 0.f); - target->write(dispatch_id, value); - }; - auto scale_shader = pipeline.device().compile<2>(scale_kernel); - command_buffer << scale_shader(*bump_scaled).dispatch(resolution_scaled) << commit(); - - Kernel2D gaussian_kernel = [&](ImageFloat src, ImageFloat target, UInt kernel_size) { - auto dispatch_id = compute::dispatch_id().xy(); - auto resolution = dispatch_size().xy(); - auto dispatch_id_int = make_int2(dispatch_id); - auto resolution_int = make_int2(resolution); - auto half_kernel_size = Int(kernel_size) / 2; - auto value = def(0.f); - $for (i, -half_kernel_size, half_kernel_size + 1) { - $for (j, -half_kernel_size, half_kernel_size + 1) { - auto offset = make_int2(i, j); - auto index = abs(dispatch_id_int + offset); - index = ite( - index > resolution_int - 1, - 2 * resolution_int - index - 2, - index); - auto index_uint = make_uint2(index); - auto v = src->read(index_uint).x; - // TODO: Gaussian Filter only have host API - auto x_squared = length_squared(make_float2(offset)); - value += v / std::sqrt(pi * 2.f) * exp(-x_squared / 2.f); - }; - }; - target->write(dispatch_id, make_float4(value)); - }; - auto gaussian_shader = pipeline.device().compile<2>(gaussian_kernel); - command_buffer << gaussian_shader(*bump_scaled, *gaussian_blurred, 5u).dispatch(resolution_scaled) - << commit(); - - Kernel2D dxdy_kernel = [](ImageFloat src, ImageFloat dx, ImageFloat dy, Float strength) { - auto id = dispatch_id().xy(); - auto resolution = dispatch_size().xy(); - - auto x1_id = id.x + 1u; - x1_id = ite(x1_id == resolution.x, resolution.x - 1u, x1_id); - auto x2_id = id.x - 1u; - x2_id = ite(x2_id == -1u, 0u, x2_id); - auto x1 = src->read(make_uint2(x1_id, id.y)); - auto x2 = src->read(make_uint2(x2_id, id.y)); - auto dx_value = clamp(strength * (x1 - x2), -5.f, 5.f); - dx->write(id, dx_value); - - auto y1_id = id.y + 1u; - y1_id = ite(y1_id == resolution.y, resolution.y - 1u, y1_id); - auto y2_id = id.y - 1u; - y2_id = ite(y2_id == -1u, 0u, y2_id); - auto y1 = src->read(make_uint2(id.x, y1_id)); - auto y2 = src->read(make_uint2(id.x, y2_id)); - auto dy_value = clamp(-strength * (y1 - y2), -5.f, 5.f); - dy->write(id, dy_value); - }; - auto dxdy_shader = pipeline.device().compile<2>(dxdy_kernel); - command_buffer << dxdy_shader(*gaussian_blurred, *dx, *dy, strength).dispatch(resolution_scaled) - << commit(); - - // TODO: area interpolation - TextureSampler sampler{TextureSampler::Filter::LINEAR_LINEAR, TextureSampler::Address::MIRROR}; - auto dx_tex_id = pipeline.register_bindless(*dx, sampler); - auto dy_tex_id = pipeline.register_bindless(*dy, sampler); - - Kernel2D normal_kernel = [&](UInt dx_tex_id, UInt dy_tex_id, ImageFloat normal) { - auto id = dispatch_id().xy(); - auto resolution = dispatch_size().xy(); - auto uv = make_float2( - id.x / Float(resolution.x), - id.y / Float(resolution.y)); - auto dx = pipeline.tex2d(dx_tex_id).sample(uv).x; - auto dy = pipeline.tex2d(dy_tex_id).sample(uv).y; - auto dz = 1.f; - auto norm = sqrt(dx * dx + dy * dy + dz * dz); - auto normal_value = make_float4(dx, dy, dz, 1.f) / norm; - normal_value = (normal_value + 1.f) * 0.5f; - normal->write(id, normal_value); - }; - auto normal_shader = pipeline.device().compile<2>(normal_kernel); - command_buffer << normal_shader(dx_tex_id, dy_tex_id, *normal).dispatch(resolution) - << synchronize(); - auto normal_map_tex_id = pipeline.register_bindless(*normal, sampler); - - LUISA_INFO_WITH_LOCATION("Bump2NormalTextureInstance build time: {} ms", clk.toc()); - return luisa::make_unique<Bump2NormalTextureInstance>(pipeline, this, normal_map_tex_id); + auto bump = pipeline.build_texture(command_buffer, _bump_texture); + return luisa::make_unique<Bump2NormalTextureInstance>(pipeline, this, bump); } }// namespace luisa::render diff --git a/src/textures/bump2normal.cpp.old b/src/textures/bump2normal.cpp.old new file mode 100644 index 00000000..63b691c1 --- /dev/null +++ b/src/textures/bump2normal.cpp.old @@ -0,0 +1,230 @@ +// +// Created by mike on 4/17/24. +// + +#include <util/thread_pool.h> +#include <util/imageio.h> +#include <base/texture.h> +#include <base/pipeline.h> +#include <base/scene.h> + +namespace luisa::render { + +using namespace luisa::compute; + +/** +* # bump_image = np.power( + # cv.imread(material_bump, cv.IMREAD_GRAYSCALE) / 255, 2.2 + # ) + # h, w = bump_image.shape[:2] + # scale = 4 + # bump_image = cv.resize(bump_image, (w * scale, h * scale), interpolation=cv.INTER_LANCZOS4) + # bump_image = cv.GaussianBlur(bump_image, (5, 5), 0) + # dx_image = cv.copyMakeBorder(bump_image, 0, 0, 1, 1, cv.BORDER_REPLICATE) + # dy_image = cv.copyMakeBorder(bump_image, 1, 1, 0, 0, cv.BORDER_REPLICATE) + # strength = min(w, h) / 50 * scale + # dx_image = np.clip(strength * (dx_image[:, 2:] - dx_image[:, :-2]), -5, 5) + # dy_image = np.clip(-strength * (dy_image[2:, :] - dy_image[:-2, :]), -5, 5) + # dx_image = cv.resize(dx_image, (w, h), interpolation=cv.INTER_AREA) + # dy_image = cv.resize(dy_image, (w, h), interpolation=cv.INTER_AREA) + # dz_image = np.ones_like(dx_image) + # norm = np.sqrt(dx_image**2 + dy_image**2 + dz_image**2) + # normal_image = np.dstack([dz_image, dy_image, dx_image]) + # normal_image = (normal_image / norm[:, :, np.newaxis]) * 0.5 + 0.5 + # # normal_image = cv.GaussianBlur(normal_image, (3, 3), 0) + # # normal_image = cv.resize(normal_image, (w, h), interpolation=cv.INTER_CUBIC) + # normal_image = np.uint8(np.clip(normal_image * 255, 0, 255)) + # save_name = f"lr_exported_textures/{material_bump.split('/')[-1]}" + # cv.imwrite(save_name, normal_image) +*/ + +inline auto builtin_gaussian_filter_desc(uint kernel_size) noexcept { + static const auto nodes = [] { + auto make_desc = [](uint kernel_size) noexcept { + auto desc = luisa::make_shared<SceneNodeDesc>( + luisa::format("__bump2normal_texture_builtin_gaussian{}x{}", kernel_size, kernel_size), + SceneNodeTag::FILTER); + desc->define(SceneNodeTag::FILTER, "Gaussian", {}); + auto radius = (kernel_size + 1u) / 2.f; + auto sigma = radius / 3.f; + desc->add_property("radius", radius); + desc->add_property("sigma", sigma); + return eastl::make_pair(std::move(kernel_size), std::move(desc)); + }; + using namespace std::string_view_literals; + return luisa::fixed_map<uint, luisa::shared_ptr<SceneNodeDesc>, 2>{ + make_desc(3), + make_desc(5)}; + }(); + auto iter = nodes.find(kernel_size); + return iter == nodes.cend() ? nullptr : iter->second.get(); +} + +class Bump2NormalTexture final : public Texture { + +private: + Texture *_bump_texture; + Filter *_gaussian5x5, *_gaussian3x3;// TODO + float _scale{1.f}; + +public: + Bump2NormalTexture(Scene *scene, const SceneNodeDesc *desc) noexcept + : Texture{scene, desc} { + _scale = desc->property_float_or_default("scale", 1.f); + + _bump_texture = scene->load_texture(desc->property_node("bump")); + if (_bump_texture->channels() != 1u) { + LUISA_WARNING_WITH_LOCATION("Bump image {} should only have 1 channel. {} found.", + desc->identifier(), + _bump_texture->channels()); + } + LUISA_ASSERT(all(_bump_texture->resolution() >= 3u), + "Bump image {} resolution conflicts with the algorithm.", + desc->identifier()); + + _gaussian5x5 = scene->load_filter(builtin_gaussian_filter_desc(5)); + _gaussian3x3 = scene->load_filter(builtin_gaussian_filter_desc(3)); + } + // [[nodiscard]] auto bump_texture() const noexcept { return _bump_texture; } + [[nodiscard]] bool is_black() const noexcept override { return false; } + [[nodiscard]] bool is_constant() const noexcept override { return false; } + [[nodiscard]] luisa::string_view impl_type() const noexcept override { return LUISA_RENDER_PLUGIN_NAME; } + [[nodiscard]] uint channels() const noexcept override { return 3u; } + [[nodiscard]] uint2 resolution() const noexcept override { + return _bump_texture->resolution(); + } + [[nodiscard]] luisa::unique_ptr<Instance> build( + Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept override; +}; + +class Bump2NormalTextureInstance final : public Texture::Instance { + +private: + uint _normal_texture_id; + +public: + Bump2NormalTextureInstance(Pipeline &pipeline, + const Texture *texture, + uint normal_texture_id) noexcept + : Texture::Instance{pipeline, texture}, + _normal_texture_id{normal_texture_id} {} + [[nodiscard]] Float4 evaluate( + const Interaction &it, Expr<float> time) const noexcept override { + auto v = pipeline().tex2d(_normal_texture_id).sample(it.uv()); + return v; + } +}; + +luisa::unique_ptr<Texture::Instance> Bump2NormalTexture::build( + Pipeline &pipeline, CommandBuffer &command_buffer) const noexcept { + + Clock clk; + + auto bump_texture = pipeline.build_texture(command_buffer, _bump_texture); + auto resolution = bump_texture->node()->resolution(); + auto resolution_scaled = make_uint2(resolution.x * _scale, resolution.y * _scale); + auto strength = std::min(resolution.x, resolution.y) * _scale; + + auto bump_scaled = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); + auto gaussian_blurred = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); + auto dx = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); + auto dy = pipeline.create<Image<float>>(PixelStorage::FLOAT1, resolution_scaled, 1u); + auto normal = pipeline.create<Image<float>>(PixelStorage::FLOAT4, resolution, 1u); + + Kernel2D scale_kernel = [&](ImageFloat target) { + auto dispatch_id = compute::dispatch_id().xy(); + auto resolution = dispatch_size().xy(); + auto uv = make_float2( + dispatch_id.x / Float(resolution.x), + dispatch_id.y / Float(resolution.y)); + auto value = bump_texture->evaluate(Interaction(uv), 0.f); + target->write(dispatch_id, value); + }; + auto scale_shader = pipeline.device().compile<2>(scale_kernel); + command_buffer << scale_shader(*bump_scaled).dispatch(resolution_scaled) << commit(); + + Kernel2D gaussian_kernel = [&](ImageFloat src, ImageFloat target, UInt kernel_size) { + auto dispatch_id = compute::dispatch_id().xy(); + auto resolution = dispatch_size().xy(); + auto dispatch_id_int = make_int2(dispatch_id); + auto resolution_int = make_int2(resolution); + auto half_kernel_size = Int(kernel_size) / 2; + auto value = def(0.f); + $for (i, -half_kernel_size, half_kernel_size + 1) { + $for (j, -half_kernel_size, half_kernel_size + 1) { + auto offset = make_int2(i, j); + auto index = abs(dispatch_id_int + offset); + index = ite( + index > resolution_int - 1, + 2 * resolution_int - index - 2, + index); + auto index_uint = make_uint2(index); + auto v = src->read(index_uint).x; + // TODO: Gaussian Filter only have host API + auto x_squared = length_squared(make_float2(offset)); + value += v / std::sqrt(pi * 2.f) * exp(-x_squared / 2.f); + }; + }; + target->write(dispatch_id, make_float4(value)); + }; + auto gaussian_shader = pipeline.device().compile<2>(gaussian_kernel); + command_buffer << gaussian_shader(*bump_scaled, *gaussian_blurred, 5u).dispatch(resolution_scaled) + << commit(); + + Kernel2D dxdy_kernel = [](ImageFloat src, ImageFloat dx, ImageFloat dy, Float strength) { + auto id = dispatch_id().xy(); + auto resolution = dispatch_size().xy(); + + auto x1_id = id.x + 1u; + x1_id = ite(x1_id == resolution.x, resolution.x - 1u, x1_id); + auto x2_id = id.x - 1u; + x2_id = ite(x2_id == -1u, 0u, x2_id); + auto x1 = src->read(make_uint2(x1_id, id.y)); + auto x2 = src->read(make_uint2(x2_id, id.y)); + auto dx_value = clamp(strength * (x1 - x2), -5.f, 5.f); + dx->write(id, dx_value); + + auto y1_id = id.y + 1u; + y1_id = ite(y1_id == resolution.y, resolution.y - 1u, y1_id); + auto y2_id = id.y - 1u; + y2_id = ite(y2_id == -1u, 0u, y2_id); + auto y1 = src->read(make_uint2(id.x, y1_id)); + auto y2 = src->read(make_uint2(id.x, y2_id)); + auto dy_value = clamp(-strength * (y1 - y2), -5.f, 5.f); + dy->write(id, dy_value); + }; + auto dxdy_shader = pipeline.device().compile<2>(dxdy_kernel); + command_buffer << dxdy_shader(*gaussian_blurred, *dx, *dy, strength).dispatch(resolution_scaled) + << commit(); + + // TODO: area interpolation + TextureSampler sampler{TextureSampler::Filter::LINEAR_LINEAR, TextureSampler::Address::MIRROR}; + auto dx_tex_id = pipeline.register_bindless(*dx, sampler); + auto dy_tex_id = pipeline.register_bindless(*dy, sampler); + + Kernel2D normal_kernel = [&](UInt dx_tex_id, UInt dy_tex_id, ImageFloat normal) { + auto id = dispatch_id().xy(); + auto resolution = dispatch_size().xy(); + auto uv = make_float2( + id.x / Float(resolution.x), + id.y / Float(resolution.y)); + auto dx = pipeline.tex2d(dx_tex_id).sample(uv).x; + auto dy = pipeline.tex2d(dy_tex_id).sample(uv).y; + auto dz = 1.f; + auto norm = sqrt(dx * dx + dy * dy + dz * dz); + auto normal_value = make_float4(dx, dy, dz, 1.f) / norm; + normal_value = (normal_value + 1.f) * 0.5f; + normal->write(id, normal_value); + }; + auto normal_shader = pipeline.device().compile<2>(normal_kernel); + command_buffer << normal_shader(dx_tex_id, dy_tex_id, *normal).dispatch(resolution) + << synchronize(); + auto normal_map_tex_id = pipeline.register_bindless(*normal, sampler); + + LUISA_INFO_WITH_LOCATION("Bump2NormalTextureInstance build time: {} ms", clk.toc()); + return luisa::make_unique<Bump2NormalTextureInstance>(pipeline, this, normal_map_tex_id); +} + +}// namespace luisa::render + +LUISA_RENDER_MAKE_SCENE_NODE_PLUGIN(luisa::render::Bump2NormalTexture) \ No newline at end of file