Skip to content

Instantly share code, notes, and snippets.

@partybusiness
Last active December 20, 2024 22:27
Show Gist options
  • Select an option

  • Save partybusiness/8b22700435d1d5943285618c75971e45 to your computer and use it in GitHub Desktop.

Select an option

Save partybusiness/8b22700435d1d5943285618c75971e45 to your computer and use it in GitHub Desktop.
@tool
extends EditorScript
class_name GenerateCubemapEditorScript
# collection of helper scripts for generating cubemaps
# file you wish to output the generated cubemap to
var cubemap_filename:String = "color_test_cubemap.tres"
# file you wish to output the generated cubemap to
var cubemap_image_filename:String = "color_test_cubemap.png"
# name of a Node3D in the currently open scene used as a camera position
# if you aren't using one, you can leave it blank
var camera_position_node3D:String = ""
# size of image generated per side from camera
var image_size:int = 2048
# single = apply same image to each side of the cubemap
# six = provide six image textures for each side of the cubemap
# camera = renders six images from the currently open scene
enum GenModes {single, six, camera}
# call by using File -> Run in Script Editor
# set value of mode below for which method you want to use
# then go to the corresponding function and set any variables followed with # SET VALUE
func _run() -> void:
var mode:GenModes = GenModes.camera # change this for other modes
match mode:
GenModes.single:
gen_single()
GenModes.six:
gen_six()
GenModes.camera:
gen_camera()
func gen_single() -> void:
var default_image:Image = flip_image(Image.load_from_file("res://icon.svg")) # SET VALUE
var new_cubemap:Cubemap = Cubemap.new()
# Front (+X), Back (-X), Top (+Y), Bottom (-Y), Left (+Z), Right (-Z)
var all_images:Array[Image] = [default_image, default_image, default_image, default_image, default_image, default_image]
new_cubemap.create_from_images(all_images)
ResourceSaver.save(new_cubemap, "res://" + cubemap_filename)
func gen_six() -> void:
# NOTE: images are flipped to match the results when using EYEDIR on a samplerCube in a shader
var new_cubemap:Cubemap = Cubemap.new()
var right_image:Image = flip_image(Image.load_from_file("res://dice/five.png")) # SET VALUE
var left_image:Image = flip_image(Image.load_from_file("res://dice/two.png")) # SET VALUE
var top_image:Image = flip_image(Image.load_from_file("res://dice/four.png")) # SET VALUE
var bottom_image:Image = flip_image(Image.load_from_file("res://dice/three.png")) # SET VALUE
var back_image:Image = flip_image(Image.load_from_file("res://dice/six.png")) # SET VALUE
var front_image:Image = flip_image(Image.load_from_file("res://dice/one.png")) # SET VALUE
# NOTE: I encountered one claim that the order is Front, Back, Top, Bottom, Left, Right
# but if I interpret a zero-rotation camera as facing forward, the following is more correct:
var all_images:Array[Image] = [right_image, left_image, top_image, bottom_image, back_image, front_image]
new_cubemap.create_from_images(all_images)
ResourceSaver.save(new_cubemap, "res://" + cubemap_filename)
static func flip_image(source:Image, down_scale:int = 1) -> Image:
var new_image:Image = source.duplicate(true)
for x in range(0, source.get_width()):
for y in range(0, source.get_height()):
new_image.set_pixel(x, y, source.get_pixel(source.get_width() - x - 1, y))
if down_scale > 1:
new_image.resize(source.get_width() / down_scale, source.get_height() / down_scale, Image.INTERPOLATE_LANCZOS)
return new_image
func gen_camera() -> void:
print(get_scene().name)
# I couldn't find a good way to render from a camera in an EditorScript so I'm forced
#to add an @tool Node to the scene and let that handle the actual rendering.
# But I couldn't get EditorInterface.edit_node() to work on the @tool Node so I still
#need this EditorScript
var get_position:Vector3 = Vector3.ZERO
# check if the scene contains a position node to override get_position
if camera_position_node3D.length()>0:
var position_source:Node3D = get_scene().find_child(camera_position_node3D)
if position_source != null:
get_position = position_source.global_position
var new_gen = GenerateCubemapFromCamera.new()
new_gen.name = "GenerateCubemapFromCamera"
get_scene().add_child(new_gen)
new_gen.owner = get_scene()
new_gen.vrs_update_mode = Viewport.VRS_UPDATE_ALWAYS
new_gen.size = Vector2i(image_size, image_size)
new_gen.world_3d = new_gen.get_world_3d()
#instantiate camera
var camera = Camera3D.new()
new_gen.add_child(camera)
camera.fov = 90.0
camera.name = "camera_name"
camera.global_position = get_position
camera.owner = get_scene()
new_gen.camera = camera
new_gen.image_size = image_size
new_gen.cubemap_filename = cubemap_image_filename
EditorInterface.edit_node(new_gen) #need viewport selected or it won't render
new_gen.run_now = true
@tool
extends SubViewport
class_name GenerateCubemapFromCamera
@export var run_now:bool = false
@export var image_size:int = 4096
@export var down_scale:int = 4
@export var cubemap_filename:String = "camera_cubemap.png"
@export var camera:Camera3D
func _process(_delta: float) -> void:
if run_now:
run_now = false
size = Vector2i.ONE * image_size * down_scale
# NOTE: images are flipped to match the results when using EYEDIR on a samplerCube in a shader
await get_tree().process_frame # bonus frame wait
camera.rotation_degrees = Vector3(0.0, 0.0, 0.0)
await get_tree().process_frame # wait for camera to render
var front_image:Image = GenerateCubemapEditorScript.flip_image(get_texture().get_image(), down_scale)
camera.rotation_degrees = Vector3(0.0, 180.0, 0.0)
await get_tree().process_frame # wait for camera to render
var back_image:Image = GenerateCubemapEditorScript.flip_image(get_texture().get_image(), down_scale)
camera.rotation_degrees = Vector3(90.0, 180.0, 0.0)
await get_tree().process_frame # wait for camera to render
var top_image:Image = GenerateCubemapEditorScript.flip_image(get_texture().get_image(), down_scale)
camera.rotation_degrees = Vector3(-90.0, 180.0, 0.0)
await get_tree().process_frame # wait for camera to render
var bottom_image:Image = GenerateCubemapEditorScript.flip_image(get_texture().get_image(), down_scale)
camera.rotation_degrees = Vector3(0.0, 90.0, 0.0)
await get_tree().process_frame # wait for camera to render
var left_image:Image = GenerateCubemapEditorScript.flip_image(get_texture().get_image(), down_scale)
camera.rotation_degrees = Vector3(0.0, -90.0, 0.0)
await get_tree().process_frame # wait for camera to render
var right_image:Image = GenerateCubemapEditorScript.flip_image(get_texture().get_image(), down_scale)
await get_tree().process_frame # bonus frame wait
var all_images:Array[Image] = [right_image, left_image, top_image, bottom_image, back_image, front_image]
save_cubemap_png(all_images, "res://" + cubemap_filename)
print("complete")
#self.queue_free() # removes itself after finishing
func save_cubemap_png(all_images:Array[Image], path_name:String) -> void:
# assumes images are in order: [right_image, left_image, top_image, bottom_image, back_image, front_image]
var image_size:Vector2i = Vector2i(all_images[0].get_width(), all_images[0].get_height())
var new_image:Image = Image.create(image_size.x * 3, image_size.y * 2, false, all_images[0].get_format())
var index:int = 0
var source_rect:Rect2i = Rect2i(0, 0, image_size.x, image_size.y)
new_image.blit_rect(all_images[0], source_rect, Vector2i(0, 0)) # right
new_image.blit_rect(all_images[1], source_rect, Vector2i(image_size.x, 0)) # left
new_image.blit_rect(all_images[2], source_rect, Vector2i(image_size.x * 2, 0)) # top
new_image.blit_rect(all_images[3], source_rect, Vector2i(0, image_size.y)) # bottom
new_image.blit_rect(all_images[4], source_rect, Vector2i(image_size.x, image_size.y)) # back
new_image.blit_rect(all_images[5], source_rect, Vector2i(image_size.x * 2.0, image_size.y)) # front
new_image.save_png(path_name)
# set up import settings on the image
await get_tree().process_frame
EditorInterface.get_resource_filesystem().scan() #
await get_tree().process_frame
var import_settings:ConfigFile = ConfigFile.new()
import_settings.load(path_name + ".import")
print(import_settings)
import_settings.set_value("remap", "importer", "cubemap_texture")
import_settings.set_value("remap", "type", "CompressedCubemap")
import_settings.set_value("params", "slices/arrangement", 2)
import_settings.set_value("params", "mipmaps/generate", false)
import_settings.save(path_name + ".import")
EditorInterface.get_resource_filesystem().scan()
shader_type sky;
uniform samplerCube sky_cube:source_color;
void sky() {
COLOR = texture(sky_cube, EYEDIR).rgb;
}
shader_type spatial;
render_mode unshaded;
uniform samplerCube sky_cube:source_color;
varying vec3 world_position;
void vertex()
{
world_position = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
void fragment() {
vec3 view_dir = world_position - CAMERA_POSITION_WORLD;
ALBEDO = texture(sky_cube, view_dir).rgb;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment