Skip to content

Commit

Permalink
EEVEE-Next: Add operator to convert a world volume to mesh
Browse files Browse the repository at this point in the history
EEVEE-Next world volume are infinite like Cycles. EEVEE-Classic world volumes
end at the clip_end of the camera/viewport. This can lead to confusion as
it would render different then expected.

This PR adds an operator to convert a world volume into a mesh volume. The
operator can be found in the shader editor (world mode) and in the properties
panel/World/Volume.

**Why an operator?**

As this alters the content of the scene we want the artist to be in control of
the conversion. Doing it automatic lead to a lot of complexity and cases that
might not be expected by the user.

Pull Request: https://projects.blender.org/blender/blender/pulls/119734
  • Loading branch information
jeroenbakker-atmind committed May 10, 2024
1 parent 5a76ba9 commit f01e84e
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 1 deletion.
1 change: 1 addition & 0 deletions scripts/startup/bl_operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"uvcalc_transform",
"vertexpaint_dirt",
"view3d",
"world",
"wm",
]

Expand Down
156 changes: 156 additions & 0 deletions scripts/startup/bl_operators/world.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import bpy
import bmesh


class WORLD_OT_convert_volume_to_mesh(bpy.types.Operator):
"""
Convert the volume of a world to a mesh.
The world's volume used to be rendered by EEVEE Legacy. Conversion is needed for it to render properly"""
bl_label = "Convert Volume"
bl_options = {'REGISTER', 'UNDO'}
bl_idname = "world.convert_volume_to_mesh"

@classmethod
def poll(cls, context):
world = cls._world_get(context)
if not world or not world.use_nodes:
return False

ntree = world.node_tree
node = ntree.get_output_node('EEVEE')
return bool(node.inputs['Volume'].links)

def execute(self, context):
cls = self.__class__
world = cls._world_get(context)
view_layer = context.view_layer

world_tree = world.node_tree
world_output = world_tree.get_output_node('EEVEE')
name = f"{world.name}_volume"

collection = bpy.data.collections.new(name)
view_layer.layer_collection.collection.children.link(collection)

# Add World Volume Mesh object to scene
mesh = bpy.data.meshes.new(name)
object = bpy.data.objects.new(name, mesh)
object.display.show_shadows = False

bm = bmesh.new()
bmesh.ops.create_icosphere(bm, subdivisions=0, radius=1e5)
bm.to_mesh(mesh)
bm.free()

# Remove all non-essential attributes
for attribute in mesh.attributes:
if attribute.is_internal or attribute.is_required:
continue
mesh.attributes.remove(attribute)

material = bpy.data.materials.new(name)
mesh.materials.append(material)
material.use_nodes = True
volume_tree = material.node_tree
for node in volume_tree.nodes:
if node.type != 'OUTPUT_MATERIAL':
volume_tree.nodes.remove(node)
volume_output = volume_tree.get_output_node('EEVEE')

links_to_add = []
self.__sync_rna_properties(volume_output, world_output)
self.__sync_node_input(
volume_tree,
volume_output,
volume_output.inputs['Volume'],
world_output,
world_output.inputs['Volume'],
links_to_add)
self.__sync_links(volume_tree, links_to_add)

# Add transparent volume for other render engines
if volume_output.target == 'EEVEE':
all_output = volume_tree.nodes.new(type="ShaderNodeOutputMaterial")
transparent = volume_tree.nodes.new(type="ShaderNodeBsdfTransparent")
volume_tree.links.new(transparent.outputs[0], all_output.inputs[0])

# Remove all volume links from the world node tree.
for link in world_output.inputs['Volume'].links:
world_tree.links.remove(link)

collection.objects.link(object)
object.select_set(True)
view_layer.objects.active = object

world.use_eevee_finite_volume = False

return {"FINISHED"}

@staticmethod
def _world_get(context):
if hasattr(context, 'world'):
return context.world
return context.scene.world

def __sync_node_input(
self,
dst_tree: bpy.types.NodeTree,
dst_node: bpy.types.Node,
dst_socket: bpy.types.NodeSocket,
src_node: bpy.types.Node,
src_socket: bpy.types.NodeSocket,
links_to_add) -> None:
self.__sync_rna_properties(dst_socket, src_socket)
for src_link in src_socket.links:
src_linked_node = src_link.from_node
dst_linked_node = self.__sync_node(dst_tree, src_linked_node, links_to_add)

from_socket_index = src_node.outputs.find(src_link.from_socket.name)
dst_tree.links.new(
dst_linked_node.outputs[from_socket_index],
dst_socket
)

def __sync_node(self, dst_tree: bpy.types.NodeTree, src_node: bpy.types.Node, links_to_add) -> bpy.types.Node:
"""
Find the counter part of the src_node in dst_tree. When found return the counter part. When not found
create the counter part, sync it and return the created node.
"""
if src_node.name in dst_tree.nodes:
return dst_tree.nodes[src_node.name]

dst_node = dst_tree.nodes.new(src_node.bl_idname)

self.__sync_rna_properties(dst_node, src_node)
self.__sync_node_inputs(dst_tree, dst_node, src_node, links_to_add)
return dst_node

def __sync_rna_properties(self, dst, src) -> None:
for rna_prop in src.bl_rna.properties:
if rna_prop.is_readonly:
continue

attr_name = rna_prop.identifier
if attr_name in ['bl_idname', 'bl_static_type']:
continue
setattr(dst, attr_name, getattr(src, attr_name))

def __sync_node_inputs(
self,
dst_tree: bpy.types.NodeTree,
dst_node: bpy.types.Node,
src_node: bpy.types.Node,
links_to_add) -> None:
for index in range(len(src_node.inputs)):
src_socket = src_node.inputs[index]
dst_socket = dst_node.inputs[index]
self.__sync_node_input(dst_tree, dst_node, dst_socket, src_node, src_socket, links_to_add)

def __sync_links(self, dst_tree: bpy.types.NodeTree, links_to_add) -> None:
pass


classes = (
WORLD_OT_convert_volume_to_mesh,
)
3 changes: 3 additions & 0 deletions scripts/startup/bl_ui/properties_world.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ def draw(self, context):

layout.use_property_split = True

if world.use_eevee_finite_volume:
layout.operator("world.convert_volume_to_mesh", icon='WORLD', text="Convert Volume")

if node:
input = find_node_input(node, "Volume")
if input:
Expand Down
4 changes: 4 additions & 0 deletions scripts/startup/bl_ui/space_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,15 @@ def draw(self, context):

if snode.shader_type == 'WORLD':
NODE_MT_editor_menus.draw_collapsible(context, layout)
world = scene.world

if snode_id:
row = layout.row()
row.prop(snode_id, "use_nodes")

if world and world.use_eevee_finite_volume:
row.operator("world.convert_volume_to_mesh", emboss=False, icon='WORLD', text="Convert Volume")

layout.separator_spacer()

row = layout.row()
Expand Down
2 changes: 1 addition & 1 deletion source/blender/blenkernel/BKE_blender_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extern "C" {

/* Blender file format version. */
#define BLENDER_FILE_VERSION BLENDER_VERSION
#define BLENDER_FILE_SUBVERSION 31
#define BLENDER_FILE_SUBVERSION 32

/* Minimum Blender version that supports reading file written with the current
* version. Older Blender versions will test this and cancel loading the file, showing a warning to
Expand Down
49 changes: 49 additions & 0 deletions source/blender/blenloader/intern/versioning_400.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3416,6 +3416,55 @@ void blo_do_versions_400(FileData *fd, Library * /*lib*/, Main *bmain)
}
}

if (!MAIN_VERSION_FILE_ATLEAST(bmain, 402, 31)) {
/* Mark old EEVEE world volumes for showing conversion operator. */
LISTBASE_FOREACH (World *, world, &bmain->worlds) {
if (world->nodetree) {
/* NOTE: duplicated from `ntreeShaderOutputNode` with small adjustments so it can be called
* during versioning. */
bNode *output_node = nullptr;

LISTBASE_FOREACH (bNode *, node, &world->nodetree->nodes) {
if (node->type != SH_NODE_OUTPUT_WORLD) {
continue;
}

if (node->custom1 == SHD_OUTPUT_ALL) {
if (output_node == nullptr) {
output_node = node;
}
else if (output_node->custom1 == SHD_OUTPUT_ALL) {
if ((node->flag & NODE_DO_OUTPUT) && !(output_node->flag & NODE_DO_OUTPUT)) {
output_node = node;
}
}
}
else if (node->custom1 == SHD_OUTPUT_EEVEE) {
if (output_node == nullptr) {
output_node = node;
}
else if ((node->flag & NODE_DO_OUTPUT) && !(output_node->flag & NODE_DO_OUTPUT)) {
output_node = node;
}
}
}
/* End duplication. */

if (output_node) {
bNodeSocket *volume_input_socket = static_cast<bNodeSocket *>(
BLI_findlink(&output_node->inputs, 1));
if (volume_input_socket) {
LISTBASE_FOREACH (bNodeLink *, node_link, &world->nodetree->links) {
if (node_link->tonode == output_node && node_link->tosock == volume_input_socket) {
world->flag |= WO_USE_EEVEE_FINITE_VOLUME;
}
}
}
}
}
}
}

/**
* Always bump subversion in BKE_blender_version.h when adding versioning
* code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check.
Expand Down
5 changes: 5 additions & 0 deletions source/blender/makesdna/DNA_world_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ enum {
* otherwise anim-editors will not read correctly.
*/
WO_DS_SHOW_TEXS = 1 << 2,
/**
* World uses volume that is created in old version of EEVEE (<4.2). These volumes should be
* converted manually. (Ref: #119734).
*/
WO_USE_EEVEE_FINITE_VOLUME = 1 << 3,
};

/** #World::probe_resolution. */
Expand Down
9 changes: 9 additions & 0 deletions source/blender/makesrna/intern/rna_world.cc
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ void RNA_def_world(BlenderRNA *brna)

rna_def_animdata_common(srna);

/* Flags */
prop = RNA_def_property(srna, "use_eevee_finite_volume", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", WO_USE_EEVEE_FINITE_VOLUME);
RNA_def_property_ui_text(prop,
"Finite Volume",
"The world's volume used to be rendered by EEVEE Legacy. Conversion is "
"needed for it to render properly");
RNA_def_property_clear_flag(prop, PROP_ANIMATABLE);

/* colors */
prop = RNA_def_property(srna, "color", PROP_FLOAT, PROP_COLOR);
RNA_def_property_float_sdna(prop, nullptr, "horr");
Expand Down

0 comments on commit f01e84e

Please sign in to comment.