What is the best way to generate a transparent image from a mesh?
Asked Answered
B

7

0

Hi,

in my project I have a spawnmenu with a lot of items. But I want all item buttons to have a little preview image of the item. Doing this manually would be a lot of work, since I have around 400 items, so what would be a way to generate these images? (Items are 3D Scenes with Mesh)

Buryat answered 30/1 at 21:45 Comment(0)
S
0

This sounded so interesting, I wanted to try it too.

extends Node

@export var models : Array[PackedScene]
var save_path = "res://screencapture/screenshots/"
var viewport : Viewport


func _ready() -> void:
	create_images()


func create_images():
	viewport = get_viewport()
	viewport.transparent_bg = true
	
	for model in models:
		var model_instance = model.instantiate()
		add_child(model_instance)
		await RenderingServer.frame_post_draw
		
		var image =  viewport.get_texture().get_image()
		image.convert(Image.FORMAT_RGBA8)
		image.save_png(save_path + "%s.png" %model_instance.name)
		model_instance.queue_free()
		
	print("Done")
	get_tree().quit()

I positioned the camera away from the center, with default settings and added some light.


Sunder answered 30/1 at 23:44 Comment(0)
S
0

I haven't used this before, but you could try using the following method:
https://docs.godotengine.org/en/stable/classes/class_viewport.html#class-viewport-method-get-texture

You could setup a scene where your items spawn in one by one and then create an image from that I guess.
Not sure how good this works. 🤷‍♂️

Sunder answered 30/1 at 22:51 Comment(0)
S
0

This sounded so interesting, I wanted to try it too.

extends Node

@export var models : Array[PackedScene]
var save_path = "res://screencapture/screenshots/"
var viewport : Viewport


func _ready() -> void:
	create_images()


func create_images():
	viewport = get_viewport()
	viewport.transparent_bg = true
	
	for model in models:
		var model_instance = model.instantiate()
		add_child(model_instance)
		await RenderingServer.frame_post_draw
		
		var image =  viewport.get_texture().get_image()
		image.convert(Image.FORMAT_RGBA8)
		image.save_png(save_path + "%s.png" %model_instance.name)
		model_instance.queue_free()
		
	print("Done")
	get_tree().quit()

I positioned the camera away from the center, with default settings and added some light.


Sunder answered 30/1 at 23:44 Comment(0)
L
0

Sunder EDIT: the mystery is solved. this is now documentation of my sins and failures.
subview_port.get_texture() returns a ViewportTexture which is dynamic. it updates whenever the SubViewport does, so every TextureRect or Sprite2D will look identical. that's true even if all their textures are unique, because they will all point to the same SubViewport drawing the same scene. there's two options to deal with that:

  1. create another viewport and scene to render for every single item you want to display. you could mitigate the load by splitting your menu into pages and only rendering what's on that page. keep all the other resources invisible, but stored in memory so you're not getting lag spikes everytime you move to another page.
  2. see Sunder's code up above
    i got blank image data with 0x0 pixels after it was applied it to a TextureRect and a Sprite2D, so
    i tried saving the image to the filesystem with vimg.save_image("res://icon.png") and looking at the file.
    here it is:

    turns out what happened is i didn't convert the image format to anything sane.
    when that was fixed with img.convert(Image.FORMAT_RGB8), the image now looks like this:
    that's because raw image data was grabbed before the RenderingServer had a chance to draw anything.
    doing await RenderingServer.frame_post_draw should fix that, however it didn't, and we'll never know why. mistakes were made.
Luxuriant answered 30/1 at 23:44 Comment(0)
S
0

Just noticed that image.convert(Image.FORMAT_RGBA8) is not necessary.

Sunder answered 30/1 at 23:45 Comment(0)
L
0

Sunder interesting. for me it was very necessary until i copied your code and scene verbatim. my code was lost to a redo/undo blunder, so that's a tragedy.
it could have something to do with the fact my scene was set up to to populate a menu as it captured image data from a child SubViewport.

Luxuriant answered 31/1 at 0:22 Comment(0)
B
0

Sunder is there a way to resize that image resolution wise? If I use resize() it to 1:1, the image get squeezed, but I want it to be cropped. If I use crop(), the object will not be centered anymore, since it crops to the upper left corner.

Buryat answered 31/1 at 15:42 Comment(0)
L
0

Buryat i couldn't find an easy way through Godot, but if you're looking for a batch script and you're cool with the icons being stored on disk rather than generated and stored in memory during runtime, there's other options:

  1. if you're a linux enthusiast, and your system has imagemagick, you could always use the convert command.
  2. there's python and the Image class from the PIL module. the documentation implies you can crop a square from any 4 points of the image.
    https://pillow.readthedocs.io/en/stable/reference/Image.html

if you desperately need to go the live generated route, there's a pretty hacky way (or not considering the node's name) to do it with Control:

  1. create a Control node
  2. add a TextureRect as child of Control
  3. apply texture to TextureRect
  4. check the clip contents boolean of the parent Control node
  5. the Control node should now be "cropping" your image. all you need to do is resize the Control and and move the TextureRect around.

two immediate downsides to this solution:

  1. node paths are a little more obfuscated in code.
  2. transform operations such as sizing and container behavior are now more complicated, or not, depending on how good you are at math.

besides that, the performance cost is nil.

Luxuriant answered 1/2 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.