Following my previous question on how to generate textures without rendering, using the python module as a standalone:
Someone suggested that I actually use geometry nodes! I can create a grid and then store the texture values for each vertex and access it from there without any need to render. This sounds like a great idea - I just want to get texture value, no need for lighting etc - but I can’t find how to access data in “stored name attribute” and bring it back to python.
Here’s the code I have:
import bpy
def create_node(node_tree, type_name, node_x_location, node_location_step_x=0):
"""Creates a node of a given type, and sets/updates the location of the node on the X axis.
Returning the node object and the next location on the X axis for the next node.
"""
node_obj = node_tree.nodes.new(type=type_name)
node_obj.location.x = node_x_location
node_x_location += node_location_step_x
return node_obj, node_x_location
bpy.ops.mesh.primitive_plane_add()
bpy.ops.node.new_geometry_nodes_modifier()
node_tree = bpy.data.node_groups["Geometry Nodes"]
out_node = node_tree.nodes["Group Output"]
in_node = node_tree.nodes["Group Input"]
node_x_location = 0
node_location_step_x = 300
grid, node_x_location = create_node(node_tree, "GeometryNodeMeshGrid", node_x_location, node_location_step_x)
grid.inputs[2].default_value = 512
grid.inputs[3].default_value = 512
voronoi, node_x_location = create_node(node_tree, "ShaderNodeTexVoronoi", node_x_location, node_location_step_x)
store_attr, node_x_location = create_node(node_tree, "GeometryNodeStoreNamedAttribute", node_x_location, node_location_step_x)
store_attr.inputs[2].default_value = 'out'
out_node.location.x = node_x_location
node_tree.links.new(grid.outputs[0], store_attr.inputs["Geometry"])
node_tree.links.new(store_attr.outputs["Geometry"], out_node.inputs["Geometry"])
node_tree.links.new(voronoi.outputs["Distance"], store_attr.inputs["Value"])
Any ideas how can I get the data back to python now?
(The goal of this whole task is to be able to generate lots of random textures - randomly generating node trees with different combinations of textures, then generating their corresponding image)
You need to wait for the nodetree to be evaluated (after a context update).
If you run this after (in separated actions) your initial script has ran:
import bpy
dpg = bpy.context.view_layer.depsgraph
ev_obj = bpy.data.objects['Cube'].evaluated_get(dpg)
attributes = ev_obj.data.attributes['out'].data
#just check the first 20 entries, as there are 262144 of them.
slice = attributes[:20]
for i, atr in enumerate(slice):
print(i, atr.value)
How do I get the nodetree evaluated? Sure, when I manually run this separately it works but the goal is to automate the whole process so I don’t have to manually separate it.
Is there a faster method to access the data and put it in a np.array? iterating with a for loop is very slow
Sadly this method is still somehow slower than rendering a full image! Which is insane because I really just need to access the evaluations of the textures, ignoring lights, camera etc. When I profiled the code, almost the entire runtime went to _view_layer_update. I don’t know what that is, is there any way to remove or optimize it?
I wonder how geometry nodes are updated. It surely is optimized somehow, but might it be faster to work with premade geometry?.. Another thing is - geometry has a lot of other stuff: edges, faces, normals… I wonder if that has anything to do with how fast it updates. Maybe using only points or only vertices might be faster.
some months ago, Iliya told me in the chat how all gets evaluated (just an overview)… it has some complex branched stuff, where some nodes require different steps… I cannot clarify it better than this…
Well… for speed, the best is to use the gpu module…
A 512*512 buffer there, would render in a fraction of a millisecond, and the only perk would be transfering the data from the GPU to the disk…
If it’s just to use a Voronoi texture, it’s rather a simple solution; but if one wants to use the complete shader nodetree, it will require that all nodes have their function written in glsl (which can be copied from the source), and a compiler for the nodetree. it’s doable, but it requires a good knowledge of glsl, and quite some code
Amazing! it seems to work, but still takes 2-3x the runtime of a full render…
Now, how can I make the view layer update use the GPU? I tried switching to cycles and use GPU device but that has no effect.
Now my code is:
import bpy
import numpy as np
def create_node(node_tree, type_name, node_x_location, node_location_step_x=0):
"""Creates a node of a given type, and sets/updates the location of the node on the X axis.
Returning the node object and the next location on the X axis for the next node.
"""
node_obj = node_tree.nodes.new(type=type_name)
node_obj.location.x = node_x_location
node_x_location += node_location_step_x
return node_obj, node_x_location
bpy.ops.node.new_geometry_nodes_modifier()
node_tree = bpy.data.node_groups["Geometry Nodes"]
out_node = node_tree.nodes["Group Output"]
in_node = node_tree.nodes["Group Input"]
node_x_location = 0
node_location_step_x = 300
grid, node_x_location = create_node(node_tree, "GeometryNodeMeshGrid", node_x_location, node_location_step_x)
grid.inputs[2].default_value = 512
grid.inputs[3].default_value = 512
voronoi, node_x_location = create_node(node_tree, "ShaderNodeTexVoronoi", node_x_location, node_location_step_x)
store_attr, node_x_location = create_node(node_tree, "GeometryNodeStoreNamedAttribute", node_x_location, node_location_step_x)
store_attr.inputs[2].default_value = 'out'
out_node.location.x = node_x_location
node_tree.links.new(grid.outputs[0], store_attr.inputs["Geometry"])
node_tree.links.new(store_attr.outputs["Geometry"], out_node.inputs["Geometry"])
node_tree.links.new(voronoi.outputs["Distance"], store_attr.inputs["Value"])
bpy.context.view_layer.update()
dpg = bpy.context.view_layer.depsgraph
ev_obj = bpy.data.objects['Cube'].evaluated_get(dpg)
attributes = ev_obj.data.attributes['out'].data
nparray = np.zeros(len(attributes), dtype=np.float32)
attributes.foreach_get('value', nparray)
print(nparray)
You can’t!
All your code requires to run in CPU only. View_layers and GN don’t work in GPU, so you’re doomed to use the CPU.
When I mentioned the GPU above, was in the same direction as I explained in the other thread…
For example, these are the vert and frag shaders for a simple voronoi (taken from 2.79).
The frag shader, includes most nodes that existed at that time (thought the execution only calls the node_tex_voronoi function). mat_Material.001.vert (2.3 KB) mat_Material.001.frag (100.0 KB)
With a some work, one might be able to update this to 4.0+… but it’s enough to get the idea.