1
$\begingroup$

I’m working on a task that involves generating a large number of textures in Blender using Python (bpy) as a standalone (without the GUI). While I can automate the creation of shader nodes, I’m struggling to extract the resulting texture images efficiently. Currently, I render the images, but this involves saving them to files, which isn’t necessary for my task and adds significant runtime overhead.

Since my textures are 2D and only depend on the shader nodes (lighting, camera angle, etc., are irrelevant), I’m wondering:

Is there a way to render an image and access the pixel values directly from a script, without saving to a file?

Alternatively, is there another method to directly sample the output of the shader nodes?

(Edit: some people offered baking as a method to sample the output of the shader. However, that method is only available using cycles, and from my attempts it is even slower than a full render. These are the settings I used for baking, is there a faster method?)

bpy.context.scene.render.engine = 'CYCLES' 
bpy.context.scene.cycles.samples = 1 
bpy.context.scene.cycles.bake_type = 'EMIT'
bake_image = bpy.data.images.new("BakeResult", width=512, height=512)
image_node = nodes.new('ShaderNodeTexImage') 
image_node.image = bake_image 
image_node.select = True
material.node_tree.nodes.active = image_node bpy.ops.object.bake(type='EMIT')
$\endgroup$
11
  • 1
    $\begingroup$ Do you really need to use a shader? I take it you can't use pure Python/numpy because you want to use particular shader nodes like a Noise Texture, and implementing it yourself would be too much of a hassle - but those nodes also exist in Geometry Nodes therefore I wonder if geonodes could be used. $\endgroup$ Commented Dec 2 at 11:23
  • 2
    $\begingroup$ How to store rendered image as an nd array without saving the image in blender python? (no answer). You may want to use a RAM disk for improved performance. $\endgroup$ Commented Dec 2 at 11:25
  • $\begingroup$ You can apparently access the image if Viewer Node is connected. $\endgroup$ Commented Dec 2 at 12:42
  • 1
    $\begingroup$ You wrote "lighting, camera angle, etc., are irrelevant" - those are precisely the things that you can't emulate in geonodes, so it seems you can limit generating the trees into a subset supported by geonodes. $\endgroup$ Commented Dec 3 at 15:02
  • 1
    $\begingroup$ @MarkusvonBroady, I think that's a very great idea! This may be the answer. One can have a subdivided plane and instead of writing the result of node tree to pixels write it as an attribute to the vertices, then it's just a matter of reading this from the geometry. It seems GN modifier needs to be applied for this to work, but it seems to work, so the question is: may this be faster than saving the image? I think it's possible. $\endgroup$ Commented Dec 3 at 23:32

2 Answers 2

1
$\begingroup$

If you bake a texture, you have the image in Blender, it is accessible through the API and is not saved to disk.

enter image description here

If you render an image without output to file

bpy.ops.render.render(animation=False, write_still=False)

And you have Viewer node connected in the compositing, you can access the data block for Viewer Node image:

enter image description here

enter image description here

So you basically have the image with all pixel RGBA values listed in a single list going from left to right, from bottom to top, you can also get the dimensions from image.size:

enter image description here

So it's just a matter of putting it into whatever format you need from there.

$\endgroup$
10
  • $\begingroup$ The only way I found for baking involved using Cycles as an engine, and took way more time then a full render. Am I missing something? Is there a faster method for baking? $\endgroup$
    – Oren Matar
    Commented Nov 29 at 12:38
  • $\begingroup$ All bakes that do not need lighting information (color, roughness, normal, ...) can be baked with a single sample, while thoose which use light (like Ambient Occlusion) get better results with an higher sample value. If you bake everything using 4096 samples obiously it will take a long time. $\endgroup$ Commented Nov 29 at 12:44
  • $\begingroup$ Unfortunately using this with cycles is still slower than a full render with eevee. bpy.context.scene.render.engine = 'CYCLES' bpy.context.scene.cycles.samples = 1 bpy.context.scene.cycles.bake_type = 'EMIT' bake_image = bpy.data.images.new("BakeResult", width=512, height=512) image_node = nodes.new('ShaderNodeTexImage') image_node.image = bake_image image_node.select = True material.node_tree.nodes.active = image_node bpy.ops.object.bake(type='EMIT') takes 260 ms. while A full render with eevee only takes 80 ms. How can I make the bake faster? $\endgroup$
    – Oren Matar
    Commented Nov 29 at 13:18
  • 1
    $\begingroup$ @OrenMatar You should edit the question and provide more detail if you want more specific advice. $\endgroup$ Commented Nov 29 at 17:33
  • 1
    $\begingroup$ @OrenMatar You never mention in the question you want it to be on the command line. What are you trying to accomplish? Are you building a tool, or are you just in need to process a lot shaders? In the latter case you can still use it in Blender with gui. What I wonder is, does this viewer work even before you render an image? Because I also was thinking about accessing the "Render Result" texture, but I thought it will get updated upon render, which from a script you can't do without saving to a file? Or can you. $\endgroup$ Commented Dec 2 at 18:13
1
$\begingroup$

If you wanted to try using geometry to store the texture made in Geometry Nodes, you would need to have some geometry with vertices positioned in 2d space and indexed in similar way as image pixels.

You can create a .blend file with the setup and then load it with bpy Python module with bpy.ops.wm.open_mainfile(filepath=filepath) operator which might be easier than to set everything up using Python. So I'll be using Blender with UI for this.

It so happens that you can subdivide a plane using Loop Cut and Slide operator( Ctrl + R ) and then delete the boundary edge verts( Alt + Click on boundary edge 2 times and hit X -> V to delete vertices) and vertex indices happen to match what we need:

enter image description here

I am using MeasureIt add-on to view vertex indices.

You then need to store the texture as an attribute with Store named Attribute node making sure its type is set to Color:

enter image description here

After applying the Geometry Nodes modifier( Ctrl + A while hovering over its header) the attribute is accessible from the API in object.data.attributes:

enter image description here

The format is different, but you can read it any way you want.

attr = C.object.data.attributes['Output'].data
pixels = [x for vert in attr for x in vert.color]
print(pixels)

enter image description here

I have not tested it with bpy as Python module and really have no idea if applying the modifier wouldn't slow things down too much though.

It seems to somewhat work:

enter image description here

Just going through this process I start to doubt the viability of this in terms of speed very much though. Working with geometry in Python might not be the fastest thing. You will probably need to optimize any way possible. Using foreach_get() would be a start.

enter image description here

I am not sure if you need to use geometry at all - maybe it's possible to just use points or something else. Might be worth exploring further.

$\endgroup$
2
  • $\begingroup$ You're right, from a few basic attempts it looks like just creating the geometry nodes takes longer than a full render. Profiling showed that the fault is with "_view_layer_update" which takes 85% of the runtime, but I don't know if there's anyway to remove it. But the direction is clever - in the end I'm just trying to apply a few mathematical function (nodes) to position values, and there's no need to render. You can see the standalone python code I have here: blenderartists.org/t/… $\endgroup$
    – Oren Matar
    Commented Dec 5 at 10:49
  • 1
    $\begingroup$ In the end of the day, if you want to optimize, you can take the c++ code of the nodes and use it in your own program otherwise resigning from Blender entirely. Still, for non- tiny images, the parallelism gpu provides can't be matched, ESPECIALLY if you don't need to wait for render 1 before doing render 2 $\endgroup$ Commented Dec 5 at 12:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .