Compositor: Port rotation nodes from geometry nodes

This patch adds support for Rotation sockets as well as existing nodes
from Geometry Nodes. This includes the Rotate Vector, Invert Rotation,
Rotation to Euler, Euler to Rotation, Rotate Rotation, Rotation to Axis
Angle, Axis Angle to Rotation, Mix Rotation, Rotation to Quaternion,
Quaternion to Rotation, Align Rotation to Vector, and Axes to Rotation
nodes.

Pull Request: https://projects.blender.org/blender/blender/pulls/154845
This commit is contained in:
Criss-Ivana 2026-05-06 13:26:35 +02:00 • committed by Omar Emara
parent 88610c0b25
commit f1185c873b
92 changed files with 1303 additions and 64 deletions

View file

@ -48,6 +48,7 @@ class NODE_MT_compositor_node_input_constant_base(node_add_menu.NodeMenu):
self.node_operator(layout, "FunctionNodeInputMenu")
self.node_operator(layout, "CompositorNodeNormal")
self.node_operator(layout, "GeometryNodeInputObject")
self.node_operator(layout, "FunctionNodeInputRotation")
self.node_operator(layout, "FunctionNodeInputString")
self.node_operator(layout, "ShaderNodeValue")
self.node_operator(layout, "FunctionNodeInputVector")
@ -290,6 +291,7 @@ class NODE_MT_compositor_node_utilities_base(node_add_menu.NodeMenu):
self.draw_menu(layout, path="Utilities/Math")
self.draw_menu(layout, path="Utilities/Vector")
self.draw_menu(layout, path="Utilities/Matrix")
self.draw_menu(layout, path="Utilities/Rotation")
self.draw_menu(layout, path="Utilities/Text")
layout.separator()
self.node_operator(layout, "CompositorNodeLevels")
@ -401,6 +403,31 @@ class NODE_MT_compositor_utilities_matrix_base(node_add_menu.NodeMenu):
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_compositor_node_rotation_base(node_add_menu.NodeMenu):
bl_label = "Rotation"
menu_path = "Utilities/Rotation"
def draw(self, _context):
layout = self.layout
self.node_operator(layout, "FunctionNodeAlignRotationToVector")
self.node_operator(layout, "FunctionNodeAxesToRotation")
self.node_operator(layout, "FunctionNodeAxisAngleToRotation")
self.node_operator(layout, "FunctionNodeEulerToRotation")
self.node_operator(layout, "FunctionNodeInvertRotation")
props = self.node_operator(layout, "ShaderNodeMix", label="Mix Rotation")
ops = props.settings.add()
ops.name = "data_type"
ops.value = "'ROTATION'"
self.node_operator(layout, "FunctionNodeRotateRotation")
self.node_operator(layout, "FunctionNodeRotateVector")
self.node_operator(layout, "FunctionNodeRotationToAxisAngle")
self.node_operator(layout, "FunctionNodeRotationToEuler")
self.node_operator(layout, "FunctionNodeRotationToQuaternion")
self.node_operator(layout, "FunctionNodeQuaternionToRotation")
self.draw_assets_for_catalog(layout, self.menu_path)
class NODE_MT_compositor_node_creative_base(node_add_menu.NodeMenu):
bl_label = "Creative"
@ -467,6 +494,7 @@ add_menus = {
"NODE_MT_category_compositor_math": NODE_MT_compositor_node_math_base,
"NODE_MT_category_compositor_text": NODE_MT_compositor_node_text_base,
"NODE_MT_category_compositor_matrix": NODE_MT_compositor_utilities_matrix_base,
"NODE_MT_category_compositor_rotation": NODE_MT_compositor_node_rotation_base,
"NODE_MT_compositor_node_add_all": NODE_MT_compositor_node_all_base,
}
add_menus = node_add_menu.generate_menus(
@ -497,6 +525,7 @@ swap_menus = {
"NODE_MT_compositor_node_math_swap": NODE_MT_compositor_node_math_base,
"NODE_MT_compositor_node_text_swap": NODE_MT_compositor_node_text_base,
"NODE_MT_compositor_node_matrix_swap": NODE_MT_compositor_utilities_matrix_base,
"NODE_MT_compositor_node_rotation_swap": NODE_MT_compositor_node_rotation_base,
"NODE_MT_compositor_node_swap_all": NODE_MT_compositor_node_all_base,
}
swap_menus = node_add_menu.generate_menus(

View file

@ -136,6 +136,10 @@ static ColorGeometry4b float2_to_byte_color(const float2 &a)
{
return color::encode(float2_to_color(a));
}
static math::Quaternion float2_to_quaternion(const float2 &a)
{
return math::to_quaternion(math::EulerXYZ(float3(a.x, a.y, 0.0f)));
}
static bool float3_to_bool(const float3 &a)
{
@ -601,6 +605,10 @@ static math::Quaternion float4x4_to_quaternion(const float4x4 &a)
return math::normalized_to_quaternion_safe(math::normalize(float3x3(a)));
}
static float2 quaternion_to_float2(const math::Quaternion &a)
{
return float2(math::to_euler(a).xyz().xy());
}
static float3 quaternion_to_float3(const math::Quaternion &a)
{
return float3(math::to_euler(a).xyz());
@ -642,6 +650,7 @@ static DataTypeConversions create_implicit_conversions()
add_implicit_conversion<float2, int8_t, float2_to_int8>(conversions);
add_implicit_conversion<float2, ColorGeometry4f, float2_to_color>(conversions);
add_implicit_conversion<float2, ColorGeometry4b, float2_to_byte_color>(conversions);
add_implicit_conversion<float2, math::Quaternion, float2_to_quaternion>(conversions);
add_implicit_conversion<float3, bool, float3_to_bool>(conversions);
add_implicit_conversion<float3, int8_t, float3_to_int8>(conversions);
@ -767,6 +776,7 @@ static DataTypeConversions create_implicit_conversions()
add_implicit_conversion<float4x4, math::Quaternion, float4x4_to_quaternion>(conversions);
add_implicit_conversion<math::Quaternion, float2, quaternion_to_float2>(conversions);
add_implicit_conversion<math::Quaternion, float3, quaternion_to_float3>(conversions);
add_implicit_conversion<math::Quaternion, float4, quaternion_to_float4>(conversions);
add_implicit_conversion<math::Quaternion, float4x4, quaternion_to_float4x4>(conversions);

View file

@ -79,6 +79,18 @@ template<typename T> struct QuaternionBase {
return {this->w, this->x, this->y, this->z};
}
/** C-style pointer dereference. */
operator const T *() const
{
return reinterpret_cast<const T *>(this);
}
operator T *()
{
return reinterpret_cast<T *>(this);
}
/**
* Create an exponential map representation of this quaternion.
* An exponential map is basically the rotation axis multiplied by the rotation angle.

View file

@ -18,6 +18,7 @@
#include "BLI_implicit_sharing.hh"
#include "BLI_math_interp.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_math_vector_types.hh"
@ -59,6 +60,7 @@ enum class ResultType : uint8_t {
Bool,
Float4x4,
Menu,
Quaternion,
/* Single value only types. See Result::is_single_value_only_type. */
String,
@ -169,6 +171,7 @@ class Result {
bool,
float4x4,
nodes::MenuValue,
math::Quaternion,
std::string,
Object *,
Image *,
@ -669,8 +672,8 @@ BLI_INLINE_METHOD T Result::sample(const float2 &coordinates,
const int2 size = domain_.data_size;
const float2 texel_coordinates = coordinates * float2(size);
if constexpr (is_same_any_v<T, float, float2, float3, float4, Color>) {
T pixel_value = T(0);
if constexpr (is_same_any_v<T, float, float2, float3, float4, Color, math::Quaternion>) {
T pixel_value;
const float *buffer = static_cast<const float *>(this->cpu_data().data());
float *output = nullptr;
if constexpr (std::is_same_v<T, float>) {

View file

@ -11,6 +11,7 @@
#include "BLI_index_mask.hh"
#include "BLI_map.hh"
#include "BLI_math_base.hh"
#include "BLI_math_euler.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_vector.hh"
@ -290,6 +291,15 @@ mf::Variable *MultiFunctionProcedureOperation::get_constant_input_variable(
value);
break;
}
case SOCK_ROTATION: {
const bNodeSocketValueRotation *rotation =
input.default_value_typed<bNodeSocketValueRotation>();
const math::EulerXYZ euler(float3(rotation->value_euler));
const math::Quaternion value = math::to_quaternion(euler);
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<math::Quaternion>>(
value);
break;
}
case SOCK_OBJECT: {
Object *value = input.default_value_typed<bNodeSocketValueObject>()->value;
constant_function = &procedure_.construct_function<mf::CustomMF_Constant<Object *>>(value);

View file

@ -169,6 +169,8 @@ const char *RealizeOnDomainOperation::get_realization_shader_name()
return "compositor_realize_on_domain_float4x4";
case ResultType::Menu:
return "compositor_realize_on_domain_menu";
case ResultType::Quaternion:
return "compositor_realize_on_domain_bicubic_float4";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -210,6 +212,8 @@ const char *RealizeOnDomainOperation::get_realization_shader_name()
return "compositor_realize_on_domain_float4x4";
case ResultType::Menu:
return "compositor_realize_on_domain_menu";
case ResultType::Quaternion:
return "compositor_realize_on_domain_float4";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -262,7 +266,8 @@ void RealizeOnDomainOperation::realize_on_domain_cpu(const float3x3 &transformat
int4,
bool,
float4x4,
nodes::MenuValue>(
nodes::MenuValue,
math::Quaternion>(
[&]<typename T>() { realize_on_domain<T>(input, output, transformation); });
}

View file

@ -13,6 +13,7 @@
#include "BLI_generic_pointer.hh"
#include "BLI_generic_span.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_quaternion_types.hh"
#include "BLI_math_vector_types.hh"
#include "GPU_shader.hh"
@ -55,6 +56,7 @@ bool Result::is_single_value_only_type(ResultType type)
case ResultType::Bool:
case ResultType::Float4x4:
case ResultType::Menu:
case ResultType::Quaternion:
return false;
case ResultType::String:
case ResultType::Object:
@ -106,6 +108,8 @@ gpu::TextureFormat Result::gpu_texture_format(ResultType type, ResultPrecision p
/* Menu values are technically stored in 32-bit integers, but 8 is sufficient in
* practice. */
return gpu::TextureFormat::SINT_8;
case ResultType::Quaternion:
return gpu::TextureFormat::SFLOAT_16_16_16_16;
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -152,6 +156,8 @@ gpu::TextureFormat Result::gpu_texture_format(ResultType type, ResultPrecision p
/* Menu values are technically stored in 32-bit integers, but 8 is sufficient in
* practice. */
return gpu::TextureFormat::SINT_8;
case ResultType::Quaternion:
return gpu::TextureFormat::SFLOAT_32_32_32_32;
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -176,6 +182,7 @@ eGPUDataFormat Result::gpu_data_format(ResultType type)
switch (type) {
case ResultType::Float:
case ResultType::Color:
case ResultType::Quaternion:
case ResultType::Float4:
case ResultType::Float3:
case ResultType::Float2:
@ -373,6 +380,8 @@ const CPPType &Result::cpp_type(const ResultType type)
return CPPType::get<float4x4>();
case ResultType::Menu:
return CPPType::get<nodes::MenuValue>();
case ResultType::Quaternion:
return CPPType::get<math::Quaternion>();
case ResultType::String:
return CPPType::get<std::string>();
case ResultType::Object:
@ -420,6 +429,8 @@ const char *Result::type_name(const ResultType type)
return "float4x4";
case ResultType::Menu:
return "menu";
case ResultType::Quaternion:
return "quaternion";
case ResultType::String:
return "string";
case ResultType::Object:
@ -543,6 +554,9 @@ void Result::allocate_single_value()
case ResultType::Menu:
this->set_single_value(nodes::MenuValue(0));
break;
case ResultType::Quaternion:
this->set_single_value(math::Quaternion(0.0f, 0.0f, 0.0f, 0.0f));
break;
case ResultType::String:
this->set_single_value(std::string(""));
break;
@ -584,6 +598,7 @@ Result Result::upload_to_gpu(const bool from_pool) const
switch (this->type()) {
case ResultType::Float:
case ResultType::Color:
case ResultType::Quaternion:
case ResultType::Float4:
case ResultType::Float2:
case ResultType::Int:
@ -659,6 +674,7 @@ Result Result::download_to_cpu() const
switch (this->type()) {
case ResultType::Float:
case ResultType::Color:
case ResultType::Quaternion:
case ResultType::Float4:
case ResultType::Float2:
case ResultType::Int:
@ -996,6 +1012,7 @@ int64_t Result::channels_count() const
case ResultType::Color:
case ResultType::Float4:
case ResultType::Int4:
case ResultType::Quaternion:
return 4;
case ResultType::Float4x4:
return 16;
@ -1053,6 +1070,7 @@ void Result::update_single_value_data()
case ResultType::Int4:
case ResultType::Bool:
case ResultType::Menu:
case ResultType::Quaternion:
GPU_texture_update(
this->gpu_texture(), this->get_gpu_data_format(), this->single_value().get());
break;

View file

@ -74,6 +74,7 @@ static GPUType gpu_type_from_socket(const bNodeSocket &socket)
return GPU_NONE;
}
case SOCK_RGBA:
case SOCK_ROTATION:
return GPU_VEC4;
case SOCK_MATRIX:
return GPU_MAT4;

View file

@ -11,6 +11,8 @@
#include "BLI_assert.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_euler.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BLI_ustring.hh"
#include "BLI_vector_set.hh"
@ -235,6 +237,14 @@ static void initialize_input_stack_value(const bNodeSocket &input, GPUNodeStack
stack.vec[0] = int(value);
break;
}
case SOCK_ROTATION: {
const bNodeSocketValueRotation *rotation =
input.default_value_typed<bNodeSocketValueRotation>();
const math::EulerXYZ euler(float3(rotation->value_euler));
const math::Quaternion value = math::to_quaternion(euler);
copy_v4_v4(stack.vec, float4(value));
break;
}
case SOCK_MATRIX:
/* Matrix sockets do not have default values. */
BLI_assert_unreachable();
@ -289,6 +299,8 @@ static const char *get_set_function_name(const ResultType type)
case ResultType::Menu:
/* GPUMaterial doesn't support int, so it is passed as a float. */
return "set_float";
case ResultType::Quaternion:
return "set_quaternion";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -524,6 +536,8 @@ static const char *get_store_function_name(ResultType type)
return "node_compositor_store_output_float4x4";
case ResultType::Menu:
return "node_compositor_store_output_menu";
case ResultType::Quaternion:
return "node_compositor_store_output_quaternion";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -724,6 +738,8 @@ static const char *glsl_store_expression_from_result_type(ResultType type)
/* GPUMaterial doesn't support int, so it is passed as a float, and we need to convert it
* back to int before writing it. */
return "ivec4(int(value))";
case ResultType::Quaternion:
return "value";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -749,6 +765,7 @@ static ImageType gpu_image_type_from_result_type(const ResultType type)
case ResultType::Float3:
case ResultType::Color:
case ResultType::Float4:
case ResultType::Quaternion:
return ImageType::Float2D;
case ResultType::Int:
case ResultType::Int2:
@ -797,6 +814,8 @@ std::string ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_
"void store_float4x4(const uint id, float4x4 value)";
/* GPUMaterial doesn't support int, so it is passed as a float. */
const std::string store_menu_function_header = "void store_menu(const uint id, float value)";
const std::string store_quaternion_function_header =
"void store_quaternion(const uint id, vec4 value)";
/* Each of the store functions is essentially a single switch case on the given ID, so start by
* opening the function with a curly bracket followed by opening a switch statement in each of
@ -813,6 +832,7 @@ std::string ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_
std::stringstream store_bool_function;
std::stringstream store_float4x4_function;
std::stringstream store_menu_function;
std::stringstream store_quaternion_function;
const std::string store_function_start = "\n{\n switch (id) {\n";
store_float_function << store_float_function_header << store_function_start;
store_float2_function << store_float2_function_header << store_function_start;
@ -826,6 +846,7 @@ std::string ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_
store_bool_function << store_bool_function_header << store_function_start;
store_float4x4_function << store_float4x4_function_header << store_function_start;
store_menu_function << store_menu_function_header << store_function_start;
store_quaternion_function << store_quaternion_function_header << store_function_start;
shader_create_info.builtins(BuiltinBits::GLOBAL_INVOCATION_ID);
@ -899,6 +920,9 @@ std::string ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_
case ResultType::Menu:
store_menu_function << common_case_code.str();
break;
case ResultType::Quaternion:
store_quaternion_function << common_case_code.str();
break;
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -927,11 +951,13 @@ std::string ShaderOperation::generate_code_for_outputs(ShaderCreateInfo &shader_
store_bool_function << store_function_end;
store_float4x4_function << store_function_end;
store_menu_function << store_function_end;
store_quaternion_function << store_function_end;
return store_float_function.str() + store_float2_function.str() + store_float3_function.str() +
store_float4_function.str() + store_color_function.str() + store_int_function.str() +
store_int2_function.str() + store_int3_function.str() + store_int4_function.str() +
store_bool_function.str() + store_float4x4_function.str() + store_menu_function.str();
store_bool_function.str() + store_float4x4_function.str() + store_menu_function.str() +
store_quaternion_function.str();
}
static const char *glsl_type_from_result_type(ResultType type)
@ -967,6 +993,8 @@ static const char *glsl_type_from_result_type(ResultType type)
case ResultType::Menu:
/* GPUMaterial doesn't support int, so it is passed as a float. */
return "float";
case ResultType::Quaternion:
return "vec4";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -1013,6 +1041,8 @@ static const char *glsl_swizzle_from_result_type(ResultType type)
return "xyzw";
case ResultType::Menu:
return "x";
case ResultType::Quaternion:
return "xyzw";
case ResultType::String:
case ResultType::Object:
case ResultType::Image:

View file

@ -5,6 +5,7 @@
#include <string>
#include "BLI_assert.h"
#include "BLI_math_euler.hh"
#include "BLI_math_vector_types.hh"
#include "DNA_node_types.h"
@ -110,6 +111,14 @@ void SingleValueNodeInputOperation::execute()
result.set_single_value(value);
break;
}
case SOCK_ROTATION: {
const bNodeSocketValueRotation *rotation =
input_socket_.default_value_typed<bNodeSocketValueRotation>();
const math::EulerXYZ euler(float3(rotation->value_euler));
const math::Quaternion value = math::to_quaternion(euler);
result.set_single_value(value);
break;
}
case SOCK_OBJECT: {
Object *value = input_socket_.default_value_typed<bNodeSocketValueObject>()->value;
result.set_single_value(value);

View file

@ -76,6 +76,8 @@ ResultType socket_data_type_to_result_type(const eNodeSocketDatatype data_type,
return ResultType::Menu;
case SOCK_STRING:
return ResultType::String;
case SOCK_ROTATION:
return ResultType::Quaternion;
case SOCK_OBJECT:
return ResultType::Object;
case SOCK_IMAGE:

View file

@ -85,6 +85,15 @@ void convert_float_to_bool()
imageStore(image_out, texel, int4(float_to_bool(value.x)));
}
void convert_float_to_quaternion()
{
auto &sampler_in = sampler_get(compositor_convert_float_to_quaternion, input_tx);
auto &image_out = image_get(compositor_convert_float_to_quaternion, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float_to_quaternion(value.x));
}
/* --------------------------------------------------------------------
* Float2 to other.
*/
@ -161,6 +170,15 @@ void convert_float2_to_bool()
imageStore(image_out, texel, int4(float2_to_bool(value.xy)));
}
void convert_float2_to_quaternion()
{
auto &sampler_in = sampler_get(compositor_convert_float2_to_quaternion, input_tx);
auto &image_out = image_get(compositor_convert_float2_to_quaternion, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float2_to_quaternion(value.xy));
}
/* --------------------------------------------------------------------
* Float3 to other.
*/
@ -237,6 +255,15 @@ void convert_float3_to_bool()
imageStore(image_out, texel, int4(float3_to_bool(value.xyz)));
}
void convert_float3_to_quaternion()
{
auto &sampler_in = sampler_get(compositor_convert_float3_to_quaternion, input_tx);
auto &image_out = image_get(compositor_convert_float3_to_quaternion, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float3_to_quaternion(value.xyz));
}
/* --------------------------------------------------------------------
* Float4 to other.
*/
@ -313,6 +340,15 @@ void convert_float4_to_bool()
imageStore(image_out, texel, int4(float4_to_bool(value)));
}
void convert_float4_to_quaternion()
{
auto &sampler_in = sampler_get(compositor_convert_float4_to_quaternion, input_tx);
auto &image_out = image_get(compositor_convert_float4_to_quaternion, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float4_to_quaternion(value));
}
/* --------------------------------------------------------------------
* Color to other.
*/
@ -705,3 +741,60 @@ void convert_bool_to_float4()
int4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float4(bool_to_float4(bool(value.x))));
}
/* --------------------------------------------------------------------
* Float4x4 to other.
*/
void convert_float4x4_to_quaternion()
{
auto &sampler_in = sampler_get(compositor_convert_float4x4_to_quaternion, input_tx);
auto &image_out = image_get(compositor_convert_float4x4_to_quaternion, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4x4 mat = texture_load_float4x4(sampler_in, texel);
imageStore(image_out, texel, float4x4_to_quaternion(mat));
}
/* --------------------------------------------------------------------
* Quaternion to other.
*/
void convert_quaternion_to_float2()
{
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float2, input_tx);
auto &image_out = image_get(compositor_convert_quaternion_to_float2, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float4(quaternion_to_float2(value), 0.0f, 0.0f));
}
void convert_quaternion_to_float3()
{
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float3, input_tx);
auto &image_out = image_get(compositor_convert_quaternion_to_float3, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float4(quaternion_to_float3(value), 0.0f));
}
void convert_quaternion_to_float4()
{
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float4, input_tx);
auto &image_out = image_get(compositor_convert_quaternion_to_float4, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
imageStore(image_out, texel, float4(quaternion_to_float4(value)));
}
void convert_quaternion_to_float4x4()
{
auto &sampler_in = sampler_get(compositor_convert_quaternion_to_float4x4, input_tx);
auto &image_out = image_get(compositor_convert_quaternion_to_float4x4, output_img);
int2 texel = int2(gl_GlobalInvocationID.xy);
float4 value = texture_load(sampler_in, texel);
float4x4 mat = quaternion_to_float4x4(value);
imageStore(image_out, int3(texel, 0), mat[0]);
imageStore(image_out, int3(texel, 1), mat[1]);
imageStore(image_out, int3(texel, 2), mat[2]);
imageStore(image_out, int3(texel, 3), mat[3]);
}

View file

@ -21,6 +21,12 @@ COMPUTE_SOURCE("compositor_convert.glsl")
SAMPLER(0, isampler2D, input_tx)
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_float4x4_shared)
LOCAL_GROUP_SIZE(16, 16)
COMPUTE_SOURCE("compositor_convert.glsl")
SAMPLER(0, sampler2DArray, input_tx)
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Float to other.
*/
@ -81,6 +87,13 @@ COMPUTE_FUNCTION("convert_float_to_bool")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_float_to_quaternion)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_float_to_quaternion")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Int to other.
*/
@ -321,6 +334,13 @@ COMPUTE_FUNCTION("convert_float2_to_bool")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_float2_to_quaternion)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_float2_to_quaternion")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Float3 to other.
*/
@ -381,6 +401,13 @@ COMPUTE_FUNCTION("convert_float3_to_bool")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_float3_to_quaternion)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_float3_to_quaternion")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Color to other.
*/
@ -504,6 +531,13 @@ COMPUTE_FUNCTION("convert_float4_to_bool")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_float4_to_quaternion)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_float4_to_quaternion")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Bool to other.
*/
@ -574,3 +608,46 @@ IMAGE(0, SFLOAT_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_color_to_alpha")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Float4x4 to other.
*/
GPU_SHADER_CREATE_INFO(compositor_convert_float4x4_to_quaternion)
ADDITIONAL_INFO(compositor_convert_float4x4_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_float4x4_to_quaternion")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
/* --------------------------------------------------------------------
* Quaternion to other.
*/
GPU_SHADER_CREATE_INFO(compositor_convert_quaternion_to_float2)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_quaternion_to_float2")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_quaternion_to_float3)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_quaternion_to_float3")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_quaternion_to_float4)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2D, output_img)
COMPUTE_FUNCTION("convert_quaternion_to_float4")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()
GPU_SHADER_CREATE_INFO(compositor_convert_quaternion_to_float4x4)
ADDITIONAL_INFO(compositor_convert_float_shared)
IMAGE(0, SFLOAT_16_16_16_16, write, image2DArray, output_img)
COMPUTE_FUNCTION("convert_quaternion_to_float4x4")
DO_STATIC_COMPILATION()
GPU_SHADER_CREATE_END()

View file

@ -64,6 +64,12 @@ void float_to_bool(float value, float &output_value)
output_value = float(float_to_bool(value));
}
[[node]]
void float_to_quaternion(float value, float4 &output_value)
{
output_value = float_to_quaternion(value);
}
/* --------------------------------------------------------------------
* Int to other.
*/
@ -168,6 +174,12 @@ void float2_to_bool(float2 value, float &output_value)
output_value = float(float2_to_bool(value));
}
[[node]]
void float2_to_quaternion(float2 value, float4 &output_value)
{
output_value = float2_to_quaternion(value);
}
/* --------------------------------------------------------------------
* Float3 to other.
*/
@ -220,6 +232,12 @@ void float3_to_bool(float3 value, float &output_value)
output_value = float(float3_to_bool(value));
}
[[node]]
void float3_to_quaternion(float3 value, float4 &output_value)
{
output_value = float3_to_quaternion(value);
}
/* --------------------------------------------------------------------
* Color to other.
*/
@ -324,6 +342,12 @@ void float4_to_bool(float4 value, float &output_value)
output_value = float(float4_to_bool(value));
}
[[node]]
void float4_to_quaternion(float4 value, float4 &output_value)
{
output_value = float4_to_quaternion(value);
}
/* --------------------------------------------------------------------
* Int2 to other.
*/
@ -480,6 +504,44 @@ void bool_to_int3(float value, float3 &output_value)
output_value = float3(bool_to_int3(bool(value)));
}
/* --------------------------------------------------------------------
* Float4x4 to other.
*/
[[node]]
void float4x4_to_quaternion(float4x4 mat, float4 &output_value)
{
output_value = float4x4_to_quaternion(mat);
}
/* --------------------------------------------------------------------
* Quaternion to other.
*/
[[node]]
void quaternion_to_float2(float4 value, float2 &output_value)
{
output_value = quaternion_to_float2(value);
}
[[node]]
void quaternion_to_float3(float4 value, float3 &output_value)
{
output_value = quaternion_to_float3(value);
}
[[node]]
void quaternion_to_float4(float4 value, float4 &output_value)
{
output_value = quaternion_to_float4(value);
}
[[node]]
void quaternion_to_float4x4(float4 value, float4x4 &output_value)
{
output_value = quaternion_to_float4x4(value);
}
/* --------------------------------------------------------------------
* Internal Code Generation Functions.
*/
@ -543,3 +605,13 @@ float4 float4_from_float(float value)
{
return float_to_color(value);
}
float4 float4_from_float4x4(float4x4 mat)
{
return float4x4_to_quaternion(mat);
}
float4x4 float4x4_from_float4(float4 value)
{
return quaternion_to_float4x4(value);
}

View file

@ -28,6 +28,12 @@ void set_float4(float4 input_value, float4 &output_value)
output_value = input_value;
}
[[node]]
void set_quaternion(float4 input_value, float4 &output_value)
{
output_value = input_value;
}
[[node]]
void set_color(float4 input_value, float4 &output_value)
{

View file

@ -102,3 +102,10 @@ void node_compositor_store_output_menu(const float id, float value, float &out_v
store_menu(floatBitsToUint(id), value);
out_value = value;
}
[[node]]
void node_compositor_store_output_quaternion(const float id, float4 value, float4 &out_value)
{
store_quaternion(floatBitsToUint(id), value);
out_value = value;
}

View file

@ -2,6 +2,9 @@
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_matrix_construct_lib.glsl"
#include "gpu_shader_math_rotation_conversion_lib.glsl"
/* --------------------------------------------------------------------
* Float to other.
*/
@ -46,6 +49,11 @@ bool float_to_bool(float value)
return value > 0.0f;
}
float4 float_to_quaternion(float value)
{
return to_quaternion(EulerXYZ::from_float3(float3(value))).as_float4();
}
/* --------------------------------------------------------------------
* Int to other.
*/
@ -134,6 +142,11 @@ bool float2_to_bool(float2 value)
return !all(equal(value, float2(0.0f)));
}
float4 float2_to_quaternion(float2 value)
{
return to_quaternion(EulerXYZ::from_float3(float3(value, 0.0f))).as_float4();
}
/* --------------------------------------------------------------------
* Float3 to other.
*/
@ -178,6 +191,11 @@ bool float3_to_bool(float3 value)
return !all(equal(value, float3(0.0f)));
}
float4 float3_to_quaternion(float3 value)
{
return to_quaternion(EulerXYZ::from_float3(value)).as_float4();
}
/* --------------------------------------------------------------------
* Color to other.
*/
@ -266,6 +284,11 @@ bool float4_to_bool(float4 value)
return !all(equal(value, float4(0.0f)));
}
float4 float4_to_quaternion(float4 value)
{
return value;
}
/* --------------------------------------------------------------------
* Int2 to other.
*/
@ -397,3 +420,44 @@ int3 bool_to_int3(bool value)
{
return int3(value);
}
/* --------------------------------------------------------------------
* Float4x4 to other.
*/
float4 float4x4_to_quaternion(float4x4 mat)
{
float3x3 mat_3x3 = normalize(to_float3x3(mat));
return to_quaternion(to_euler(mat_3x3)).as_float4();
}
/* --------------------------------------------------------------------
* Quaternion to other.
*/
float2 quaternion_to_float2(float4 value)
{
Quaternion quat = Quaternion{UNPACK4(value)};
return to_euler(from_rotation(quat)).as_float3().xy();
}
float3 quaternion_to_float3(float4 value)
{
Quaternion quat = Quaternion{UNPACK4(value)};
return to_euler(from_rotation(quat)).as_float3();
}
float4 quaternion_to_float4(float4 value)
{
return value;
}
float4x4 quaternion_to_float4x4(float4 value)
{
Quaternion quat = Quaternion{UNPACK4(value)};
float3x3 mat_3x3 = from_rotation(quat);
return float4x4(float4(mat_3x3[0], 0.0f),
float4(mat_3x3[1], 0.0f),
float4(mat_3x3[2], 0.0f),
float4(0.0f, 0.0f, 0.0f, 1.0f));
}

View file

@ -630,8 +630,11 @@ set(GLSL_SRC
shaders/common/gpu_shader_utildefines_lib.glsl
shaders/material/gpu_shader_material_add_shader.glsl
shaders/material/gpu_shader_material_align_rotation_to_vector.glsl
shaders/material/gpu_shader_material_axes_to_rotation.glsl
shaders/material/gpu_shader_material_ambient_occlusion.glsl
shaders/material/gpu_shader_material_attribute.glsl
shaders/material/gpu_shader_material_axis_angle_to_rotation.glsl
shaders/material/gpu_shader_material_background.glsl
shaders/material/gpu_shader_material_bevel.glsl
shaders/material/gpu_shader_material_wavelength.glsl
@ -646,6 +649,7 @@ set(GLSL_SRC
shaders/material/gpu_shader_material_displacement.glsl
shaders/material/gpu_shader_material_eevee_specular.glsl
shaders/material/gpu_shader_material_emission.glsl
shaders/material/gpu_shader_material_euler_to_rotation.glsl
shaders/material/gpu_shader_material_fractal_noise.glsl
shaders/material/gpu_shader_material_fractal_voronoi.glsl
shaders/material/gpu_shader_material_fresnel.glsl
@ -659,6 +663,7 @@ set(GLSL_SRC
shaders/material/gpu_shader_material_hue_sat_val.glsl
shaders/material/gpu_shader_material_implicit_defaults.glsl
shaders/material/gpu_shader_material_invert.glsl
shaders/material/gpu_shader_material_invert_rotation.glsl
shaders/material/gpu_shader_material_layer_weight.glsl
shaders/material/gpu_shader_material_light_falloff.glsl
shaders/material/gpu_shader_material_light_path.glsl
@ -677,6 +682,7 @@ set(GLSL_SRC
shaders/material/gpu_shader_material_particle_info.glsl
shaders/material/gpu_shader_material_point_info.glsl
shaders/material/gpu_shader_material_principled.glsl
shaders/material/gpu_shader_material_quaternion_to_rotation.glsl
shaders/material/gpu_shader_material_raycast.glsl
shaders/material/gpu_shader_material_ray_portal.glsl
shaders/material/gpu_shader_material_refraction.glsl
@ -684,6 +690,11 @@ set(GLSL_SRC
shaders/material/gpu_shader_material_rgb_to_bw.glsl
shaders/material/gpu_shader_material_radial_tiling.glsl
shaders/material/gpu_shader_material_radial_tiling_shared.glsl
shaders/material/gpu_shader_material_rotate_rotation.glsl
shaders/material/gpu_shader_material_rotate_vector.glsl
shaders/material/gpu_shader_material_rotation_to_axis_angle.glsl
shaders/material/gpu_shader_material_rotation_to_euler.glsl
shaders/material/gpu_shader_material_rotation_to_quaternion.glsl
shaders/material/gpu_shader_material_separate_color.glsl
shaders/material/gpu_shader_material_separate_xyz.glsl
shaders/material/gpu_shader_material_set.glsl

View file

@ -19,3 +19,32 @@ struct Quaternion {
return float4(this->x, this->y, this->z, this->w);
}
};
/* -------------------------------------------------------------------- */
/** \name Quaternion Math
* \{ */
Quaternion math_quaternion_multiply(Quaternion a, Quaternion b)
{
Quaternion result;
result.x = a.x * b.x - a.y * b.y - a.z * b.z - a.w * b.w;
result.y = a.x * b.y + a.y * b.x + a.z * b.w - a.w * b.z;
result.z = a.x * b.z - a.y * b.w + a.z * b.x + a.w * b.y;
result.w = a.x * b.w + a.y * b.z - a.z * b.y + a.w * b.x;
return result;
}
Quaternion quaternion_conjugate(Quaternion q)
{
return {q.x, -q.y, -q.z, -q.w};
}
float3 transform_point_by_quaternion(Quaternion q, float3 v)
{
const Quaternion v_quat = {0.0f, v.x, v.y, v.z};
const Quaternion result = math_quaternion_multiply(math_quaternion_multiply(q, v_quat),
quaternion_conjugate(q));
return float3(result.y, result.z, result.w);
}
/** \} */

View file

@ -197,6 +197,38 @@ Quaternion to_quaternion(float4x4 mat, const bool normalized)
return to_quaternion(to_float3x3(mat), normalized);
}
Quaternion to_quaternion(AxisAngle axis_angle)
{
float angle_cos = cos(axis_angle.angle);
/** Using half angle identities: sin(angle / 2) = sqrt((1 - angle_cos) / 2) */
float sine = sqrt(0.5f - angle_cos * 0.5f);
float cosine = sqrt(0.5f + angle_cos * 0.5f);
/* TODO(fclem): Optimize. */
float angle_sin = sin(axis_angle.angle);
if (angle_sin < 0.0f) {
sine = -sine;
}
Quaternion quat;
quat.x = cosine;
quat.y = axis_angle.axis.x * sine;
quat.z = axis_angle.axis.y * sine;
quat.w = axis_angle.axis.z * sine;
return quat;
}
Quaternion to_quaternion(float3 axis, float angle)
{
if (is_zero(axis)) {
return Quaternion::identity();
}
AxisAngle aa;
aa.axis = normalize(axis);
aa.angle = angle;
return to_quaternion(aa);
}
/** \} */
/* -------------------------------------------------------------------- */
@ -248,27 +280,6 @@ EulerXYZ to_euler(float4x4 mat)
/** \name Axis Angle Functions
* \{ */
Quaternion to_axis_angle(AxisAngle axis_angle)
{
float angle_cos = cos(axis_angle.angle);
/** Using half angle identities: sin(angle / 2) = sqrt((1 - angle_cos) / 2) */
float sine = sqrt(0.5f - angle_cos * 0.5f);
float cosine = sqrt(0.5f + angle_cos * 0.5f);
/* TODO(fclem): Optimize. */
float angle_sin = sin(axis_angle.angle);
if (angle_sin < 0.0f) {
sine = -sine;
}
Quaternion quat;
quat.x = cosine;
quat.y = axis_angle.axis.x * sine;
quat.z = axis_angle.axis.y * sine;
quat.w = axis_angle.axis.z * sine;
return quat;
}
AxisAngle to_axis_angle(Quaternion quat)
{
/* Calculate angle/2, and sin(angle/2). */

View file

@ -0,0 +1,114 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_constants_lib.glsl"
#include "gpu_shader_math_rotation_conversion_lib.glsl"
#include "gpu_shader_math_rotation_lib.glsl"
#include "gpu_shader_math_vector_compare_lib.glsl"
float angle_normalized_v3v3(float3 v1, float3 v2)
{
v1 = normalize(v1);
v2 = normalize(v2);
if (dot(v1, v2) >= 0.0f) {
return 2.0f * asin(clamp(length(v2 - v1) / 2.0f, -1.0f, 1.0f));
}
const float3 v2_n = -v2;
return M_PI - 2.0f * asin(clamp(length(v2_n - v1) / 2.0f, -1.0f, 1.0f));
}
float3 project_plane_normalized_v3_v3v3(float3 p, float3 v_plane)
{
const float3 v_plane_n = normalize(v_plane);
const float mul = dot(p, v_plane_n);
return p - v_plane_n * mul;
}
float angle_signed_on_axis_v3v3_v3(float3 v1, float3 v2, float3 axis)
{
const float3 axis_n = normalize(axis);
const float3 v1_proj = project_plane_normalized_v3_v3v3(v1, axis_n);
const float3 v2_proj = project_plane_normalized_v3_v3v3(v2, axis_n);
float angle = angle_normalized_v3v3(v1_proj, v2_proj);
const float3 tproj = cross(v2_proj, v1_proj);
if (dot(tproj, axis) < 0.0f) {
angle = M_PI * 2.0f - angle;
}
return angle;
}
[[node]]
void align_rotation_to_vector_auto_pivot(float4 rotation_in,
float factor,
float3 input_vector,
float3 local_main_axis,
out float4 rotation)
{
if (is_zero(input_vector)) {
rotation = rotation_in;
return;
}
const Quaternion quat_in = Quaternion{UNPACK4(rotation_in)};
const float3 old_axis = transform_point_by_quaternion(quat_in, local_main_axis);
const float3 new_axis = normalize(input_vector);
float3 rotation_axis = cross(old_axis, new_axis);
if (is_zero(rotation_axis)) {
/* The vectors are linearly dependent, so we fall back to another axis. */
rotation_axis = cross(old_axis, float3(1.0f, 0.0f, 0.0f));
if (is_zero(rotation_axis)) {
/* This is now guaranteed to not be zero. */
rotation_axis = cross(old_axis, float3(0.0f, 1.0f, 0.0f));
}
}
const float full_angle = angle_normalized_v3v3(old_axis, new_axis);
const float angle = factor * full_angle;
AxisAngle aa;
aa.axis = normalize(rotation_axis);
aa.angle = angle;
rotation = math_quaternion_multiply(to_quaternion(aa), quat_in).as_float4();
}
[[node]]
void align_rotation_to_vector_fixed_pivot(float4 rotation_in,
float factor,
float3 input_vector,
float3 local_main_axis,
float3 local_pivot_axis,
out float4 rotation)
{
if (all(equal(local_main_axis, local_pivot_axis))) {
/* Can't compute any meaningful rotation angle in this case. */
rotation = rotation_in;
return;
}
if (is_zero(input_vector)) {
rotation = rotation_in;
return;
}
const Quaternion quat_in = Quaternion{UNPACK4(rotation_in)};
const float3 old_axis = transform_point_by_quaternion(quat_in, local_main_axis);
const float3 pivot_axis = transform_point_by_quaternion(quat_in, local_pivot_axis);
float full_angle = angle_signed_on_axis_v3v3_v3(input_vector, old_axis, pivot_axis);
if (full_angle > M_PI) {
/* Make sure the point is rotated as little as possible. */
full_angle -= 2.0f * M_PI;
}
const float angle = factor * full_angle;
AxisAngle aa;
aa.axis = normalize(pivot_axis);
aa.angle = angle;
rotation = math_quaternion_multiply(to_quaternion(aa), quat_in).as_float4();
}

View file

@ -0,0 +1,65 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_rotation_conversion_lib.glsl"
#include "gpu_shader_math_vector_compare_lib.glsl"
#include "gpu_shader_math_vector_lib.glsl"
[[node]]
void axes_to_rotation(float3 primary_in,
float3 secondary_in,
float primary_idx_f,
float secondary_idx_f,
float tertiary_idx_f,
float tertiary_factor,
out float4 rotation)
{
float3 primary = normalize(primary_in);
float3 secondary = secondary_in;
float3 tertiary;
const bool primary_is_non_zero = !is_zero(primary);
const bool secondary_is_non_zero = !is_zero(secondary);
if (primary_is_non_zero && secondary_is_non_zero) {
tertiary = cross(primary, secondary);
if (is_zero(tertiary)) {
tertiary = orthogonal<float3>(primary);
}
tertiary = normalize(tertiary);
secondary = cross(tertiary, primary);
}
else if (primary_is_non_zero) {
secondary = orthogonal<float3>(primary);
secondary = normalize(secondary);
tertiary = cross(primary, secondary);
}
else if (secondary_is_non_zero) {
secondary = normalize(secondary);
primary = orthogonal<float3>(secondary);
primary = normalize(primary);
tertiary = cross(primary, secondary);
}
else {
rotation = float4(1.0f, 0.0f, 0.0f, 0.0f);
return;
}
const int primary_axis = int(primary_idx_f);
const int secondary_axis = int(secondary_idx_f);
const int tertiary_axis = int(tertiary_idx_f);
float3x3 mat;
mat[primary_axis] = primary;
mat[secondary_axis] = secondary;
mat[tertiary_axis] = tertiary_factor * tertiary;
rotation = to_quaternion(mat).as_float4();
}
[[node]]
void axes_to_rotation_identity(float3 primary_in, float3 secondary_in, out float4 rotation)
{
rotation = float4(1.0f, 0.0f, 0.0f, 0.0f);
}

View file

@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_rotation_conversion_lib.glsl"
[[node]]
void axis_angle_to_rotation(float3 axis, float angle, out float4 rotation)
{
rotation = to_quaternion(axis, angle).as_float4();
}

View file

@ -0,0 +1,12 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_euler_lib.glsl"
#include "gpu_shader_math_rotation_conversion_lib.glsl"
[[node]]
void euler_to_rotation(float3 euler, out float4 rotation)
{
rotation = to_quaternion(EulerXYZ::from_float3(euler)).as_float4();
}

View file

@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_quaternion_lib.glsl"
[[node]]
void invert_rotation(float4 rotation, out float4 result)
{
result = quaternion_conjugate(Quaternion{UNPACK4(rotation)}).as_float4();
}

View file

@ -3,6 +3,7 @@
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_common_color_utils.glsl"
#include "gpu_shader_math_rotation_lib.glsl"
[[node]]
void node_mix_blend(float fac,
@ -13,9 +14,12 @@ void node_mix_blend(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = mix(col1, col2, fac);
}
@ -29,9 +33,12 @@ void node_mix_add(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = mix(col1, col1 + col2, fac);
@ -47,9 +54,12 @@ void node_mix_mult(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = mix(col1, col1 * col2, fac);
@ -65,9 +75,12 @@ void node_mix_screen(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -85,9 +98,12 @@ void node_mix_overlay(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -125,9 +141,12 @@ void node_mix_sub(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = mix(col1, col1 - col2, fac);
@ -144,9 +163,12 @@ void node_mix_div_fallback(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -173,9 +195,12 @@ void node_mix_diff(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = mix(col1, abs(col1 - col2), fac);
@ -191,9 +216,12 @@ void node_mix_exclusion(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = max(mix(col1, col1 + col2 - 2.0f * col1 * col2, fac), 0.0f);
@ -209,9 +237,12 @@ void node_mix_dark(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol.rgb = mix(col1.rgb, min(col1.rgb, col2.rgb), fac);
@ -227,9 +258,12 @@ void node_mix_light(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol.rgb = mix(col1.rgb, max(col1.rgb, col2.rgb), fac);
outcol.a = col1.a;
@ -244,9 +278,12 @@ void node_mix_dodge(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = col1;
@ -297,9 +334,12 @@ void node_mix_burn(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float tmp, facm = 1.0f - fac;
@ -358,9 +398,12 @@ void node_mix_hue(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -389,9 +432,12 @@ void node_mix_sat(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -418,9 +464,12 @@ void node_mix_val(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -442,9 +491,12 @@ void node_mix_color(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -474,9 +526,12 @@ void node_mix_soft(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
float facm = 1.0f - fac;
@ -496,9 +551,12 @@ void node_mix_linear(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = col1 + fac * (2.0f * (col2 - float4(0.5f)));
@ -514,9 +572,12 @@ void node_mix_float(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
/* Avoid using mix() due to float precision issues caused by different implementations. */
outfloat = f1 * (1.0f - fac) + f2 * fac;
@ -531,9 +592,12 @@ void node_mix_vector(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
/* Avoid using mix() due to float precision issues caused by different implementations. */
outvec = v1 * (1.0f - fac) + v2 * fac;
@ -548,9 +612,12 @@ void node_mix_vector_non_uniform(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
/* Avoid using mix() due to float precision issues caused by different implementations. */
outvec = v1 * (float3(1.0f) - facvec) + v2 * facvec;
@ -565,9 +632,12 @@ void node_mix_rgba(float fac,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol)
float4 &outcol,
float4 &outrot)
{
outcol = mix(col1, col2, fac);
}
@ -589,3 +659,22 @@ void node_mix_clamp_value(float value, float min, float max, float &outfloat)
{
outfloat = clamp(value, min, max);
}
[[node]]
void node_mix_rotation(float fac,
float3 facvec,
float f1,
float f2,
float3 v1,
float3 v2,
float4 col1,
float4 col2,
float4 rot1,
float4 rot2,
float &outfloat,
float3 &outvec,
float4 &outcol,
float4 &outrot)
{
outrot = interpolate(Quaternion{UNPACK4(rot1)}, Quaternion{UNPACK4(rot2)}, fac).as_float4();
}

View file

@ -0,0 +1,17 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_vector_compare_lib.glsl"
[[node]]
void quaternion_to_rotation(float w, float x, float y, float z, out float4 rotation)
{
const float4 quat = float4(w, x, y, z);
if (!is_zero(quat)) {
rotation = normalize(quat);
}
else {
rotation = float4(1.0f, 0.0f, 0.0f, 0.0f);
}
}

View file

@ -0,0 +1,19 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_quaternion_lib.glsl"
[[node]]
void rotate_rotation_global(float4 rotation, float4 rotate_by, out float4 result)
{
result = math_quaternion_multiply(Quaternion{UNPACK4(rotate_by)}, Quaternion{UNPACK4(rotation)})
.as_float4();
}
[[node]]
void rotate_rotation_local(float4 rotation, float4 rotate_by, out float4 result)
{
result = math_quaternion_multiply(Quaternion{UNPACK4(rotation)}, Quaternion{UNPACK4(rotate_by)})
.as_float4();
}

View file

@ -0,0 +1,11 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_quaternion_lib.glsl"
[[node]]
void rotate_vector(float3 vector, float4 rotation, out float3 result)
{
result = transform_point_by_quaternion(Quaternion{UNPACK4(rotation)}, vector);
}

View file

@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_rotation_conversion_lib.glsl"
[[node]]
void rotation_to_axis_angle(float4 rotation, out float3 axis, out float angle)
{
const AxisAngle aa = to_axis_angle(Quaternion{UNPACK4(rotation)});
axis = aa.axis;
angle = aa.angle;
}

View file

@ -0,0 +1,13 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "gpu_shader_math_matrix_construct_lib.glsl"
#include "gpu_shader_math_rotation_conversion_lib.glsl"
[[node]]
void rotation_to_euler(float4 rotation, out float3 euler)
{
Quaternion quat = Quaternion{UNPACK4(rotation)};
euler = to_euler(from_rotation(quat)).as_float3();
}

View file

@ -0,0 +1,12 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
[[node]]
void rotation_to_quaternion(float4 rotation, out float w, out float x, out float y, out float z)
{
w = rotation.x;
x = rotation.y;
y = rotation.z;
z = rotation.w;
}

View file

@ -3275,7 +3275,7 @@ static const EnumPropertyItem *rna_ShaderNodeMix_data_type_itemf(bContext * /*C*
const eNodeSocketDatatype data_type = eNodeSocketDatatype(item->value);
if (data_type == SOCK_ROTATION) {
const bNodeTree *tree = reinterpret_cast<const bNodeTree *>(ptr->owner_id);
if (tree->type == NTREE_GEOMETRY) {
if (ELEM(tree->type, NTREE_GEOMETRY, NTREE_COMPOSIT)) {
return true;
}
}

View file

@ -117,6 +117,7 @@ static bool composite_node_tree_socket_type_valid(bke::bNodeTreeType * /*ntreety
SOCK_RGBA,
SOCK_MATRIX,
SOCK_MENU,
SOCK_ROTATION,
SOCK_STRING,
SOCK_OBJECT,
SOCK_FONT);
@ -141,6 +142,21 @@ static bool composite_validate_link(eNodeSocketDatatype from_type, eNodeSocketDa
return true;
}
if (ELEM(from_type, SOCK_FLOAT, SOCK_VECTOR) && to_type == SOCK_ROTATION) {
return true;
}
if (from_type == SOCK_MATRIX && to_type == SOCK_ROTATION) {
return true;
}
if (from_type == SOCK_ROTATION && to_type == SOCK_MATRIX) {
return true;
}
if (from_type == SOCK_ROTATION && to_type == SOCK_VECTOR) {
return true;
}
return from_type == to_type;
}

View file

@ -642,6 +642,7 @@ class FileOutputOperation : public NodeOperation {
case ResultType::Bool:
case ResultType::Float4x4:
case ResultType::Menu:
case ResultType::Quaternion:
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -683,6 +684,7 @@ class FileOutputOperation : public NodeOperation {
case ResultType::Bool:
case ResultType::Float4x4:
case ResultType::Menu:
case ResultType::Quaternion:
case ResultType::String:
case ResultType::Object:
case ResultType::Image:
@ -745,6 +747,7 @@ class FileOutputOperation : public NodeOperation {
case ResultType::Bool:
case ResultType::Float4x4:
case ResultType::Menu:
case ResultType::Quaternion:
case ResultType::String:
case ResultType::Object:
case ResultType::Image:

View file

@ -13,6 +13,7 @@
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_align_rotation_to_vector_cc {
@ -191,6 +192,35 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
math::Axis::from_int(node.custom1), NodeAlignEulerToVectorPivotAxis(node.custom2));
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
const math::Axis main_axis_mode = math::Axis::from_int(node->custom1);
const NodeAlignEulerToVectorPivotAxis pivot_axis_mode = NodeAlignEulerToVectorPivotAxis(
node->custom2);
float3 local_main_axis = {0.0f, 0.0f, 0.0f};
local_main_axis[main_axis_mode.as_int()] = 1.0f;
if (pivot_axis_mode == FN_NODE_ALIGN_EULER_TO_VECTOR_PIVOT_AXIS_AUTO) {
return GPU_stack_link(
mat, node, "align_rotation_to_vector_auto_pivot", in, out, GPU_constant(local_main_axis));
}
float3 local_pivot_axis = {0.0f, 0.0f, 0.0f};
local_pivot_axis[pivot_axis_mode - 1] = 1.0f;
return GPU_stack_link(mat,
node,
"align_rotation_to_vector_fixed_pivot",
in,
out,
GPU_constant(local_main_axis),
GPU_constant(local_pivot_axis));
}
static void node_rna(StructRNA *srna)
{
static const EnumPropertyItem axis_items[] = {
@ -243,7 +273,7 @@ static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(
fn_cmp_node_type_base(
&ntype, "FunctionNodeAlignRotationToVector"_ustr, FN_NODE_ALIGN_ROTATION_TO_VECTOR);
ntype.ui_name = "Align Rotation to Vector";
ntype.ui_description = "Orient a rotation along the given direction";
@ -253,6 +283,7 @@ static void node_register()
ntype.initfunc = node_init;
ntype.draw_buttons = node_layout;
ntype.build_multi_function = node_build_multi_function;
ntype.gpu_fn = node_gpu_material;
bke::node_register_type(ntype);
node_rna(ntype.rna_ext.srna);

View file

@ -11,6 +11,7 @@
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_axes_to_rotation_cc {
@ -150,6 +151,38 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
math::Axis::from_int(node.custom1), math::Axis::from_int(node.custom2));
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
if (node->custom1 == node->custom2) {
return GPU_stack_link(mat, node, "axes_to_rotation_identity", in, out);
}
const float primary = float(node->custom1);
const float secondary = float(node->custom2);
/* Through cancellation this will set the last axis to be the one that's neither the primary
* nor secondary axis. */
const int tertiary_axis = (0 + 1 + 2) - node->custom1 - node->custom2;
const float tertiary = float(tertiary_axis);
/* Might have to invert the axis to make sure that the created matrix has determinant 1. */
const bool invert_tertiary = (node->custom2 + 1) % 3 == node->custom1;
const float tertiary_factor = invert_tertiary ? -1.0f : 1.0f;
return GPU_stack_link(mat,
node,
"axes_to_rotation",
in,
out,
GPU_constant(&primary),
GPU_constant(&secondary),
GPU_constant(&tertiary),
GPU_constant(&tertiary_factor));
}
static void node_extra_info(NodeExtraInfoParams &params)
{
if (params.node.custom1 == params.node.custom2) {
@ -188,7 +221,7 @@ static void node_rna(StructRNA *srna)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeAxesToRotation"_ustr, FN_NODE_AXES_TO_ROTATION);
fn_cmp_node_type_base(&ntype, "FunctionNodeAxesToRotation"_ustr, FN_NODE_AXES_TO_ROTATION);
ntype.ui_name = "Axes to Rotation";
ntype.ui_description =
"Create a rotation from a primary and (ideally orthogonal) secondary axis";
@ -199,6 +232,7 @@ static void node_register()
ntype.build_multi_function = node_build_multi_function;
ntype.draw_buttons = node_layout;
ntype.get_extra_info = node_extra_info;
ntype.gpu_fn = node_gpu_material;
node_rna(ntype.rna_ext.srna);
bke::node_register_type(ntype);
}

View file

@ -8,6 +8,7 @@
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_axis_angle_to_rotation_cc {
@ -33,6 +34,15 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "axis_angle_to_rotation", in, out);
}
static void node_eval_elem(value_elem::ElemEvalParams &params)
{
using namespace value_elem;
@ -67,13 +77,14 @@ static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(
fn_cmp_node_type_base(
&ntype, "FunctionNodeAxisAngleToRotation"_ustr, FN_NODE_AXIS_ANGLE_TO_ROTATION);
ntype.ui_name = "Axis Angle to Rotation";
ntype.ui_description = "Build a rotation from an axis and a rotation around that axis";
ntype.enum_name_legacy = "AXIS_ANGLE_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;

View file

@ -8,6 +8,7 @@
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_euler_to_rotation_cc {
@ -18,6 +19,15 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Rotation>("Rotation"_ustr);
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "euler_to_rotation", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<float3, math::Quaternion>(
@ -55,12 +65,13 @@ static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeEulerToRotation"_ustr, FN_NODE_EULER_TO_ROTATION);
fn_cmp_node_type_base(&ntype, "FunctionNodeEulerToRotation"_ustr, FN_NODE_EULER_TO_ROTATION);
ntype.ui_name = "Euler to Rotation";
ntype.ui_description = "Build a rotation from separate angles around each axis";
ntype.enum_name_legacy = "EULER_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;

View file

@ -47,7 +47,7 @@ static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeInputRotation"_ustr, FN_NODE_INPUT_ROTATION);
fn_cmp_node_type_base(&ntype, "FunctionNodeInputRotation"_ustr, FN_NODE_INPUT_ROTATION);
ntype.ui_name = "Rotation";
ntype.ui_description =
"Provide a rotation value that can be connected to other nodes in the tree";

View file

@ -5,6 +5,7 @@
#include "BLI_math_quaternion.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_invert_rotation_cc {
@ -17,6 +18,15 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Rotation>("Rotation"_ustr).align_with_previous();
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "invert_rotation", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<math::Quaternion, math::Quaternion>(
@ -27,12 +37,13 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeInvertRotation"_ustr, FN_NODE_INVERT_ROTATION);
fn_cmp_node_type_base(&ntype, "FunctionNodeInvertRotation"_ustr, FN_NODE_INVERT_ROTATION);
ntype.ui_name = "Invert Rotation";
ntype.ui_description = "Compute the inverse of the given rotation";
ntype.enum_name_legacy = "INVERT_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}

View file

@ -5,6 +5,7 @@
#include "BLI_math_quaternion.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_quaternion_to_rotation_cc {
@ -28,16 +29,26 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "quaternion_to_rotation", in, out);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(
fn_cmp_node_type_base(
&ntype, "FunctionNodeQuaternionToRotation"_ustr, FN_NODE_QUATERNION_TO_ROTATION);
ntype.ui_name = "Quaternion to Rotation";
ntype.ui_description = "Build a rotation from quaternion components";
ntype.enum_name_legacy = "QUATERNION_TO_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}

View file

@ -8,6 +8,7 @@
#include "NOD_rna_define.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotate_rotation_cc {
@ -32,6 +33,28 @@ static void node_layout(ui::Layout &layout, bContext * /*C*/, PointerRNA *ptr)
layout.prop(ptr, "rotation_space", ui::ITEM_R_EXPAND, std::nullopt, ICON_NONE);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
const char *name = nullptr;
switch (RotationSpace(node->custom1)) {
case RotationSpace::Global:
name = "rotate_rotation_global";
break;
case RotationSpace::Local:
name = "rotate_rotation_local";
break;
}
if (name != nullptr) {
return GPU_stack_link(mat, node, name, in, out);
}
return 0;
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
switch (RotationSpace(builder.node().custom1)) {
@ -77,13 +100,14 @@ static void node_rna(StructRNA *srna)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeRotateRotation"_ustr, FN_NODE_ROTATE_ROTATION);
fn_cmp_node_type_base(&ntype, "FunctionNodeRotateRotation"_ustr, FN_NODE_ROTATE_ROTATION);
ntype.ui_name = "Rotate Rotation";
ntype.ui_description = "Apply a secondary rotation to a given rotation value";
ntype.enum_name_legacy = "ROTATE_ROTATION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.draw_buttons = node_layout;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);

View file

@ -5,6 +5,7 @@
#include "BLI_math_quaternion.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotate_vector_cc {
@ -18,6 +19,15 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Rotation>("Rotation"_ustr);
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotate_vector", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI2_SO<float3, math::Quaternion, float3>(
@ -29,12 +39,13 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeRotateVector"_ustr, FN_NODE_ROTATE_VECTOR);
fn_cmp_node_type_base(&ntype, "FunctionNodeRotateVector"_ustr, FN_NODE_ROTATE_VECTOR);
ntype.ui_name = "Rotate Vector";
ntype.ui_description = "Apply a rotation to a given vector";
ntype.enum_name_legacy = "ROTATE_VECTOR";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}

View file

@ -9,6 +9,7 @@
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotation_to_axis_angle_cc {
@ -46,6 +47,15 @@ class QuaterniontoAxisAngleFunction : public mf::MultiFunction {
}
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotation_to_axis_angle", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static QuaterniontoAxisAngleFunction fn;
@ -89,13 +99,14 @@ static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(
fn_cmp_node_type_base(
&ntype, "FunctionNodeRotationToAxisAngle"_ustr, FN_NODE_ROTATION_TO_AXIS_ANGLE);
ntype.ui_name = "Rotation to Axis Angle";
ntype.ui_description = "Convert a rotation to axis angle components";
ntype.enum_name_legacy = "ROTATION_TO_AXIS_ANGLE";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;

View file

@ -8,6 +8,7 @@
#include "NOD_value_elem_eval.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotation_to_euler_cc {
@ -18,6 +19,15 @@ static void node_declare(NodeDeclarationBuilder &b)
b.add_output<decl::Vector>("Euler"_ustr).subtype(PROP_EULER);
};
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotation_to_euler", in, out);
}
static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
{
static auto fn = mf::build::SI1_SO<math::Quaternion, float3>(
@ -54,12 +64,13 @@ static void node_eval_inverse(inverse_eval::InverseEvalParams &params)
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(&ntype, "FunctionNodeRotationToEuler"_ustr, FN_NODE_ROTATION_TO_EULER);
fn_cmp_node_type_base(&ntype, "FunctionNodeRotationToEuler"_ustr, FN_NODE_ROTATION_TO_EULER);
ntype.ui_name = "Rotation to Euler";
ntype.ui_description = "Convert a standard rotation value to an Euler rotation";
ntype.enum_name_legacy = "ROTATION_TO_EULER";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
ntype.eval_elem = node_eval_elem;
ntype.eval_inverse_elem = node_eval_inverse_elem;

View file

@ -5,6 +5,7 @@
#include "BLI_math_quaternion_types.hh"
#include "node_function_util.hh"
#include "node_shader_util.hh"
namespace blender::nodes::node_fn_rotation_to_quaternion_cc {
@ -56,16 +57,26 @@ static void node_build_multi_function(NodeMultiFunctionBuilder &builder)
builder.set_matching_fn(fn);
}
static int node_gpu_material(GPUMaterial *mat,
bNode *node,
bNodeExecData * /*execdata*/,
GPUNodeStack *in,
GPUNodeStack *out)
{
return GPU_stack_link(mat, node, "rotation_to_quaternion", in, out);
}
static void node_register()
{
static bke::bNodeType ntype;
fn_node_type_base(
fn_cmp_node_type_base(
&ntype, "FunctionNodeRotationToQuaternion"_ustr, FN_NODE_ROTATION_TO_QUATERNION);
ntype.ui_name = "Rotation to Quaternion";
ntype.ui_description = "Retrieve the quaternion components representing a rotation";
ntype.enum_name_legacy = "ROTATION_TO_QUATERNION";
ntype.nclass = NODE_CLASS_CONVERTER;
ntype.declare = node_declare;
ntype.gpu_fn = node_gpu_material;
ntype.build_multi_function = node_build_multi_function;
bke::node_register_type(ntype);
}

View file

@ -26,7 +26,14 @@ namespace blender {
static int node_exec_socket_use_stack(bNodeSocket *sock)
{
/* NOTE: INT and BOOL supported as FLOAT. Only for EEVEE. */
return ELEM(sock->type, SOCK_INT, SOCK_BOOLEAN, SOCK_FLOAT, SOCK_VECTOR, SOCK_RGBA, SOCK_SHADER);
return ELEM(sock->type,
SOCK_INT,
SOCK_BOOLEAN,
SOCK_FLOAT,
SOCK_VECTOR,
SOCK_RGBA,
SOCK_SHADER,
SOCK_ROTATION);
}
bNodeStack *node_get_socket_stack(bNodeStack *stack, bNodeSocket *sock)
@ -140,6 +147,9 @@ static bNodeStack *setup_stack(bNodeStack *stack, bNodeTree *ntree, bNode *node,
case SOCK_RGBA:
node_socket_get_color(ntree, node, sock, ns->vec);
break;
case SOCK_ROTATION:
node_socket_get_rotation(ntree, node, sock, ns->vec);
break;
default:
break;
}

View file

@ -12,6 +12,7 @@
#include "DNA_node_types.h"
#include "BLI_listbase.h"
#include "BLI_math_rotation.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_utildefines.h"
@ -296,6 +297,25 @@ void node_socket_set_vector(bNodeTree *ntree,
RNA_float_set_array(&ptr, "default_value", value);
}
void node_socket_get_rotation(bNodeTree *ntree, bNode * /*node*/, bNodeSocket *sock, float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
float euler[3];
RNA_float_get_array(&ptr, "default_value", euler);
eul_to_quat(value, euler);
}
void node_socket_set_rotation(bNodeTree *ntree,
bNode * /*node*/,
bNodeSocket *sock,
const float *value)
{
PointerRNA ptr = RNA_pointer_create_discrete(id_cast<ID *>(ntree), RNA_NodeSocket, sock);
float euler[3];
quat_to_eul(euler, value);
RNA_float_set_array(&ptr, "default_value", euler);
}
/** \} */
} // namespace blender

View file

@ -66,5 +66,10 @@ void node_socket_get_color(bNodeTree *ntree, bNode *node, bNodeSocket *sock, flo
void node_socket_set_color(bNodeTree *ntree, bNode *node, bNodeSocket *sock, const float *value);
void node_socket_get_vector(bNodeTree *ntree, bNode *node, bNodeSocket *sock, float *value);
void node_socket_set_vector(bNodeTree *ntree, bNode *node, bNodeSocket *sock, const float *value);
void node_socket_get_rotation(bNodeTree *ntree, bNode *node, bNodeSocket *sock, float *value);
void node_socket_set_rotation(bNodeTree *ntree,
bNode *node,
bNodeSocket *sock,
const float *value);
} // namespace blender

View file

@ -154,6 +154,9 @@ static void nodestack_get_vec(float *in, short type_in, bNodeStack *ns)
copy_v3_v3(in, from);
}
}
else if (type_in == SOCK_ROTATION) {
copy_v4_v4(in, from);
}
else { /* type_in==SOCK_RGBA */
if (ns->sockettype == SOCK_RGBA) {
copy_v4_v4(in, from);
@ -218,6 +221,9 @@ void node_gpu_stack_from_data(GPUNodeStack *gs, bNodeSocket *socket, bNodeStack
else if (socket->type == SOCK_SHADER) {
gs->type = GPU_CLOSURE;
}
else if (socket->type == SOCK_ROTATION) {
gs->type = GPU_VEC4;
}
else {
gs->type = GPU_NONE;
}

View file

@ -358,7 +358,7 @@ static const char *gpu_shader_get_name(eNodeSocketDatatype data_type,
return nullptr;
}
case SOCK_ROTATION:
return nullptr;
return "node_mix_rotation";
default:
BLI_assert_unreachable();
return nullptr;

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06c8fe74e220b8ee06a03b1ca387030acc6f1f9ef447d7105914db74258cd003
size 81502

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d19d7ee16337cde23143173a4e5ee2ce75a7184bcb605779ec769ed9c89d47b7
size 83079

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6764f5e87261734ec4551728ea6f47a2efe2d65ae8c76645f7bee5a164079772
size 42936

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d546a9d14cdb4d3c9dedf5307ebfa7053ca67a99e66e04e6aa80ad707f8e9941
size 79010

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1b8cd10c9cae0d874dd6803467e87e1eee93bb7fcd1a744344820d51af166dc2
size 82691

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2b5776bdb0bb6c7b0f8f6250654b3814c076eb9734f596f01ac4a319b3682592
size 82811

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b60d5951d046d9c640dc592e6b8b805059622b2bd1a8aad13f69920e7eb535f7
size 85178

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:858979c4232a26d4de9bd12e41e35409f0c9332c886ae90c9c6c326a2cad862a
size 62042

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:706aa244e58d4a8be8d1361ef381c559ef190d533e35ee9f2d940cadc7a5e8b6
size 83079

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da86e840127b2114051b87add5a0bb01b41974cf1aae96499b5f2fd63696cb4d
size 81906

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e2235cb71ff03d9b5772255bb581c3ce1983a04bac46afebc8f890a4fe6ab222
size 76079

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b30b434c11fc5884a1c0c2a3c3695a78bcd611084c2ec373b5d39442ecb8e6ed
size 54782

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:39ef7231add469fb1bcb6f1cb78168bf1cde3d53f965ad0580529945e43ff889
size 75825

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4e1d1323bcc170dd566ce5535546d0ffe128803f1df82736cb2595d195eecef5
size 82959

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9a6fe963dabf6aa433ee1a6127f6ad23c65dca2ff7d82a474b5a42d37308f8a9
size 16017

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ac26285272be12fda5456b440c77a7fe1811a9ba6aefad0316f03c33868716df
size 79541

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:db44c0cf191f4bcc2edb086884fcf7d51292ef8e15d14d3005fbe840943bf68e
size 82966

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:985f1f18f429eab009c5343e1c10a374e7a070a7e1a7d26f09ac0567baa93c45
size 85901

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d4d19405bfbfbc78f3eab5dae5fa7159b000ddc0317b51a8311fd4f61b95838a
size 59511

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f47665147f8ea0ef0fb6fa5bb605886430af9901be269fbe8aa2ee0f2effa855
size 109608

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58eab1611f8de57c36399db8ccacbf02d46b61de47aa132ec44746b5304beed9
size 109926

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:75d55c9f292bf38444b6eac7605d25313d4453bfcc0777846b6e0723eb1c0607
size 355339

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c065a41ba1f3b08336cd9abdddfda23249324a370267194e9772f830b9c1cd3b
size 113512

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:23f92292f29ca88a0febe5fcc9fb7db645f627f638763cf06fae0a58f21791ff
size 109493

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aa9d6519dfb7a29ebe787518e18bc231da819b30b6fe129ef912fad2759a454f
size 112481

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a73e1b9503c3fba4a6e9c83f3ef81d63b0a9fd1f1a3d25683cc7a65956dc406d
size 113693

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:150c5ba6d218ddb85029cb2defdb0bfcf27ce137a6a5a25530e21faa96cf2d89
size 112172

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5b0b007ab7d1fd69734b80b88d4a55009f2136b3b9341e1660cb11272f7e87df
size 113097

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8a931ad8a043825304dfdffc374081aa498e65c138a590f761c1765911e0ec31
size 111750

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5c90ea24dd5c2d2d272fe9fed346dd5cb13513ecabca237db5280e9000100c5d
size 111374

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:25b958c525b3afe2ece32387e1b14172b8aa3f9d69be067e49ef39ba2f3a6de3
size 108009

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ad44c9bbb2cea0c29469d3cb1852a91ced1f85a46142abc180f6243bbd0c51df
size 109388

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fb1419b44c6904feee6ed5848e9fd574d4b8ec0cd7be0d1f47499d54b6303d0c
size 108167

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:688504c12b53e3556f9b055c233a1fef2f6d7953a5498f9a7e2abe0b7a91fc5d
size 104310

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:293efd232f64a367234b5760df32a6961d1304d8244a7aaf13230d20fcb6c5c7
size 110528

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:419a71de463b7ee664fd74d07441c6f1ee2bff2c4d63537d49b2fbc07ab5af79
size 110551

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:09692efd427248e1ec5e73e971f8189cb8aded58361a47efcde361da03fd2a6d
size 111628

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8d957eeed636984d95b255c8c60686549539fc84d602f33aa1facadf45245477
size 108264

View file

@ -1099,6 +1099,7 @@ if(TEST_SRC_DIR_EXISTS)
texture
utilities
vector
rotation
group
pixel_nodes
@ -1137,6 +1138,7 @@ if(WITH_GPU_COMPOSITOR_TESTS AND TEST_SRC_DIR_EXISTS)
texture
utilities
vector
rotation
group
pixel_nodes

View file

@ -496,7 +496,7 @@ class CompositorNodeGroupInterfaceTest(AbstractNodeGroupInterfaceTest, NodeGroup
self.do_test_socket_type("NodeSocketInt", subtype='PERCENTAGE')
self.do_test_invalid_socket_type("NodeSocketMaterial")
self.do_test_socket_type("NodeSocketObject")
self.do_test_invalid_socket_type("NodeSocketRotation")
self.do_test_socket_type("NodeSocketRotation")
self.do_test_invalid_socket_type("NodeSocketShader")
self.do_test_socket_type("NodeSocketString")
self.do_test_invalid_socket_type("NodeSocketTexture")