如何应用着色器并仅生成图像一次?

发布于 2025-01-09 18:19:38 字数 425 浏览 6 评论 0原文

我正在尝试将像素化着色器应用于我的纹理,并且我只需要将其应用一次,之后我可以一遍又一遍地重复使用我的着色器生成的图像作为纹理,而不必每次都进行计算。

那么我该如何拍摄一些图像 ->应用着色器并在每次游戏加载时仅渲染一次 ->并使用它们作为我的纹理?

到目前为止,我已经设法找到要应用的着色器:

shader_type canvas_item;

uniform int amount = 40;

void fragment()
{
    vec2 grid_uv = round(UV * float(amount)) / float(amount);
    
    vec4 text = texture(TEXTURE, grid_uv);
    
    COLOR = text;
}

但我不知道如何使用它渲染图像

I'm trying to apply a pixelation shader to my textures and I need it to be applied only once, after that I can reuse my shader generated images as textures over and over without having to calculate every single time.

so how do I take a few images -> apply a shader and render them only once every time the game loads -> and use them as my textures?

so far I've managed to find the shader to apply:

shader_type canvas_item;

uniform int amount = 40;

void fragment()
{
    vec2 grid_uv = round(UV * float(amount)) / float(amount);
    
    vec4 text = texture(TEXTURE, grid_uv);
    
    COLOR = text;
}

but I have no idea how to render out the images using it

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

表情可笑 2025-01-16 18:19:38

着色器驻留在 GPU 中,它们的输出显示在屏幕上。为了保存图像,CPU 必须查看 GPU 输出,但这通常不会发生。而且由于不经过CPU,所以性能不错。 通常。好吧,至少比 CPU 一直这样做要好。

此外,您确定不想通过其他方式获得像素艺术外观吗?例如从纹理中删除过滤器,更改拉伸模式并在小分辨率下工作,也许还可以启用像素捕捉?不?观看如何为 Godot 中的像素艺术游戏制作丝般光滑的相机。仍然没有?好吧...

无论如何,对于你想要的东西,你将需要一个Viewport


视口设置

您需要创建一个视口不要忘记设置其大小。还可能需要将 render_target_v_flip 设置为 true,这会垂直翻转图像。如果您发现输出图像颠倒,那是因为您需要切换render_target_v_flip

然后将要渲染的内容放置为 Viewport 的子级。


渲染

接下来,您可以从视口读取纹理,将其转换为图像,并将其保存为png。我在附加到 Viewport 的工具脚本上执行此操作,因此我将有一个解决方法来从检查器面板触发代码。我的代码如下所示:

tool
extends Viewport

export var save:bool setget do_save

func do_save(new_value) -> void:
    var image := get_texture().get_data()
    var error := image.save_png("res://output.png")
    if error != OK:
        push_error("failed to save output image.")

当然,您可以导出一个FILE路径String以轻松在检查器面板中更改它。在这里,我将介绍常见的边缘情况:

tool
extends Viewport

export(String, FILE) var path:String
export var save:bool setget do_save

func do_save(_new_value) -> void:
    var target_path := path.strip_edges()
    var folder := target_path.get_base_dir()
    var file_name := target_path.get_file()
    var extension := target_path.get_extension()
    if file_name == "":
        push_error("empty file name.")
        return

    if not (Directory.new()).dir_exists(folder):
        push_error("output folder does not exist.")
        return

    if extension != "png":
        target_path += "png" if target_path.ends_with(".") else ".png"

    var image := get_texture().get_data()
    var error := image.save_png(target_path)
    if error != OK:
        push_error("failed to save output image.")
        return

    print("image saved to ", target_path)

另一种选择是使用 ResourceSaver

tool
extends Viewport

export var save:bool setget do_save

func do_save(new_value) -> void:
    var image := get_texture().get_data()
    var error := ResourceSaver.save("res://image.res", image)
    if error != OK:
        push_error("failed to save output image.")

这仅适用于 Godot 编辑器,并且仅适用于 Godot,因为您获得了 Godot 资源文件。尽管我发现使用 Godot 生成图像的想法很有趣。如果您想为 Godot 自动生成它们,我建议使用 ResourceSaver


关于从工具脚本保存资源

在上面的示例中,我假设您要保存到资源路径。这是因为目的是将输出图像用作Godot 中的资源。使用资源路径有几个含义:

  1. 这可能不适用于导出的游戏(因为目标是改进工作流程,所以这是可以的)。
  2. Godot 需要重新导入资源,但不会注意到它发生了变化。

我们可以处理 EditorPlugin 中的第二点,如果这就是你正在做的事情,你可以这样做来告诉 Godot 扫描更改:

get_editor_interface().get_resource_filesystem().scan()

如果你不是,你可以通过创建一个空的EditorPlugin。我们的想法是这样做:

var ep = EditorPlugin.new()
ep.get_editor_interface().get_resource_filesystem().scan()
ep.free()

顺便说一句,您将希望缓存 EditorPlugin 而不是每次都创建一个新的。或者更好的是,缓存从 get_resource_filesystem 获取的 EditorFileSystem


自动化

现在,我意识到将东西放入 Viewport 中可能会很麻烦。如果您不需要一直这样做,那么这对于您的工作流程来说可能没问题。

但是自动化呢?好吧,无论采用哪种方法,您都需要一个 tool 脚本来创建一个隐藏的 Viewport,采用一个 Node,检查它是否有着色器,如果是,则将其临时移动到Viewport,获取渲染纹理(get_texture())将其设置为Node的纹理>,删除着色器,并返回Node 到其在场景中的原始位置。 或者不要在节点中寻找着色器,而是始终将着色器应用于任何节点,可能作为资源加载而不是硬编码。 >

注意:我相信您需要在将 Node 添加到 Viewport 和获取纹理之间传递一个空闲帧,以便纹理更新。 或者是两个空闲帧?好吧,如果一个不起作用,请尝试添加另一个。


关于制作 EditorPlugin

如您所知,您可以从项目设置创建插件。这将为您创建一个 EditorPlugin 脚本。在那里,您可以将选项添加到工具菜单(使用add_tool_menu_item),或将其添加到编辑器的工具栏(使用add_control_to_container)。并让它作用于编辑场景中的当前选择(您可以使用get_selection,或覆盖edithandles方法)。 您可能还想为此创建一个撤消条目,请参阅get_undo_redo

或者,您也可以让它跟踪(或查找)Nodebuild 虚拟方法,该方法在项目即将运行时运行。 我没有使用过 build 虚拟方法,所以我不知道它是否有任何需要注意的怪癖。

Shaders reside in the GPU, and their output goes to the screen. To save the image, the CPU would have to see the GPU output, and that does not happen… Usually. And since it does not go through the CPU, the performance is good. Usually. Well, at least it is better than if the CPU was doing it all the time.

Also, are you sure you don't want to get a pixel art look by other means? Such as removing filter from the texture, changing the stretch mode and working on a small resolution, and perhaps enable pixel snap? No? Watch How to make a silky smooth camera for pixelart games in Godot. Still No? Ok...

Anyway, for what you want, you are going to need a Viewport.


Viewport setup

What you will need is to create a Viewport. Don't forget to set its size. Also may want to set render_target_v_flip to true, this flips the image vertically. If you find the output image is upside down it is because you need to toggle render_target_v_flip.

Then place as child of the Viewport what you want to render.


Rendering

Next, you can read the texture form the Viewport, convert it to an image, and save it to a png. I'm doing this on a tool script attached to the Viewport, so I'll have a workaround to trigger the code from the inspector panel. My code looks like this:

tool
extends Viewport

export var save:bool setget do_save

func do_save(new_value) -> void:
    var image := get_texture().get_data()
    var error := image.save_png("res://output.png")
    if error != OK:
        push_error("failed to save output image.")

You can, of course, export a FILE path String to ease changing it in the inspector panel. Here I'm handing common edge cases:

tool
extends Viewport

export(String, FILE) var path:String
export var save:bool setget do_save

func do_save(_new_value) -> void:
    var target_path := path.strip_edges()
    var folder := target_path.get_base_dir()
    var file_name := target_path.get_file()
    var extension := target_path.get_extension()
    if file_name == "":
        push_error("empty file name.")
        return

    if not (Directory.new()).dir_exists(folder):
        push_error("output folder does not exist.")
        return

    if extension != "png":
        target_path += "png" if target_path.ends_with(".") else ".png"

    var image := get_texture().get_data()
    var error := image.save_png(target_path)
    if error != OK:
        push_error("failed to save output image.")
        return

    print("image saved to ", target_path)

Another option is to use ResourceSaver:

tool
extends Viewport

export var save:bool setget do_save

func do_save(new_value) -> void:
    var image := get_texture().get_data()
    var error := ResourceSaver.save("res://image.res", image)
    if error != OK:
        push_error("failed to save output image.")

This will only work from the Godot editor, and will only work for Godot, since you get a Godot resource file. Although I find interesting the idea of using Godot to generate images. I'm going to suggest going with ResourceSaver if you want to automate generating them for Godot.


About saving resources from tool scripts

In the examples above, I'm assuming you are saving to a resource path. This is because the intention is to use the output image as a resource in Godot. Using a resource path has a couple implications:

  1. This might not work on an exported game (since the goals is improve the workflow, this is OK).
  2. Godot would need to re-import the resource, but will not notice it changed.

We can deal with the second point from an EditorPlugin, if that is what you are doing, you can do this to tell Godot to scan for changes:

get_editor_interface().get_resource_filesystem().scan()

And if you are not, you can cheat by creating an empty EditorPlugin. The idea is to do this:

var ep = EditorPlugin.new()
ep.get_editor_interface().get_resource_filesystem().scan()
ep.free()

By the way, you will want to cache cache the EditorPlugin instead of making a new one each time. Or better yet, cache the EditorFileSystem you get from get_resource_filesystem.


Automation

Now, I'm aware that it can be cumbersome to have to place things inside the Viewport. It might be Ok for your workflow if you don't need to do it all the time.

But what about automating it? Well, regardless of the approach, you will need a tool script that makes a hidden Viewport, takes a Node, checks if it has a shader, if it does, it moves it temporarily to the Viewport, get the rendered texture (get_texture()) sets it as the texture of the Node, removes the shader, and returns the Node to its original position in the scene. Or instead of looking for a shader in the Node, always apply a shader to whatever Node, perhaps loaded as a resource instead of hard-coded.

Note: I believe you need to let an idle frame pass between adding the Node to the Viewport and getting the texture, so the texture updates. Or was it two idle frames? Well, if one does not work, try adding another one.


About making an EditorPlugin

As you know, you can create an addon from project settings. This will create an EditorPlugin script for you. There you can either add an option to the tools menu (with add_tool_menu_item), or add it to the tool bar of the editor (with add_control_to_container). And have it act on the current selection in the edited scene (you can either use get_selection, or overwrite the edit and handles methods). You may also want to make an undo entry for that, see get_undo_redo.

Or, alternatively you can have it keep track (or look for) the Nodes it has to act upon, and then work on the build virtual method, which runs when the project is about to run. I haven't worked with the build virtual method, so I don't know if it has any quirks to gotchas to be aware of.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文