Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Custom Shader support to CommonFilters.py #1643

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

raytopianprojects
Copy link
Contributor

@raytopianprojects raytopianprojects commented Apr 15, 2024

Issue description

There have been many requests from community members to be able to add their own shaders to CommonFilters because it saves them a lot of time and effort of having to re-implement filters that are already supported.

Solution description

In short these changes allow users to add their own shaders to CustomFilters, have ported all built in shaders to use new system and additionally add Chromatic Aberration, Vignette, Tint, Distortion, Resolution, LUT, and Film Grain as built in filters because they are now common.

New API

delFilter(name) 
loadFilter(name, shader_string, uniforms=None, consts=None, shader_inputs=None, needed_textures=None, needed_coords=None, render_into=None, auxbits=None, is_filepath=False, order=None

Here is an example of how AmbientOcclusion is implemented using the new system.

load_filter("AmbientOcclusion",
                   shader_string="o_color *= tex2D(k_txssao2, l_texcoord_ssao2).r;\n", # This will code will be added to the shader used by finalquad.
                   needed_textures=["ssao0", "ssao1", "ssao2", "depth", "aux"], # Tells the system additional textures that need to be created
                   needed_coords=["ssao2"], # Some shaders need additional coords due to render passes
                   auxbits=[AuxBitplaneAttrib.ABOAuxNormal], # Enables use of AuxBits generated by the auto shader
                   # render into takes the format of a dictonary whose arguments will be passed into renderQuadInto
                   render_into={ 
                       "filter-ssao0": {
                           "colortex": "ssao0", # a string type means use a texture with this name
                           "shader_inputs": {
                               "depth": "depth",
                               "params1": (numsamples, -amount / numsamples, radius, 0),
                               "params2": (strength, falloff, 0, 0),
                               "normal": "aux",
                               "random": base.loader.loadTexture("maps/random.rgb"),
                           },
                           "shader": Shader.make(SSAO_BODY % numsamples, Shader.SL_Cg) # Can also be a string
                       },
                       "filter-ssao1": {
                           "colortex": "ssao1",
                           "shader_inputs": {
                               "src": "ssao0",
                           },
                           "div": 2,
                           "shader": Shader.make(BLUR_X, Shader.SL_Cg)
                       },
                       "filter-ssao2": {
                           "colortex": "ssao2",
                           "shader_inputs":
                               {
                                   "src": "ssao1",
                               },
                           "shader": Shader.make(BLUR_Y, Shader.SL_Cg)
                       },
                   }, order=2) # Order allows users to set what order a filter will be applied. If no order is set then the filter's order will be set equal to the total amount of filters that are being applied.

Due to changes to reconfigure there have been minor breaking changes (some filters don't take the same arguments due to not needing them), despite this though all filters have been tested and have the same visual output.

Checklist

I have done my best to ensure that…

  • …I have familiarized myself with the CONTRIBUTING.md file
  • …this change follows the coding style and design patterns of the codebase
  • …I own the intellectual property rights to this code
  • …the intent of this change is clearly explained
  • …existing uses of the Panda3D API are not broken
  • …the changed code is adequately covered by the test suite, where possible.

Allow users to add their own shaders to CustomFilters. Port all built in shaders to use new system. Additionally add Chromatic Aberration, Vignette, Tint, Distortion, Resolution, LUT, and Film Grain as built in filters.

vfs: VirtualFileSystem = VirtualFileSystem.get_global_ptr()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type annotation is only useful if the type-checker doesn't have information about what VirtualFileSystem.get_global_ptr returns (i.e., through stubs). If that's the case, the type-checker almost certainly doesn't know anything else about a VirtualFileSystem, so it doesn't really do much in any case. I'd recommend removing it, unless I'm missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that.

@raytopianprojects raytopianprojects marked this pull request as ready for review April 16, 2024 14:46
@rdb
Copy link
Member

rdb commented May 14, 2024

This is pretty neat. I think we need a new filter system on the C end of Panda, but there's no way that'll make it in time for 1.11, and this is simple enough that I might consider accepting this in the interim.

I prefer splitting the PR into the part containing the new system and the part with the additional filters for individual review.

I'm on the fence about stuffing the entire definition into a single dictionary structure. It gets unwieldy compared to an object hierarchy, but on the other hand, it does make it easier to load filters from some json-like file structure, hmm.

How is the order of render targets maintained? Dictionary insertion order?

If the uniforms and shader inputs are supposed to be specified in the same load_filter call, should they not go in the same place, with a shader input dictionary containing name, type and (default) value? Or is there a reason it was done this way?

Use the word "sort" instead of "order" for consistency with other parts of the Panda API.

Having the order default to None for these filters is a breaking change. There's an order enforced currently, one which makes sense. It shouldn't suddenly make tone mapping go after sRGB encoding if someone happens to call them in a different order, with the default settings.

I will think a bit more about the other aspects.

@raytopianprojects
Copy link
Contributor Author

raytopianprojects commented May 14, 2024

This is pretty neat. I think we need a new filter system on the C end of Panda, but there's no way that'll make it in time for 1.11, and this is simple enough that I might consider accepting this in the interim.

Yeah I agree that this is more of a stop gap then the permanent system.

I prefer splitting the PR into the part containing the new system and the part with the additional filters for individual review.

I'll split the PR up and it'd be good anyways to get feedback on some new filters people would like.

I'm on the fence about stuffing the entire definition into a single dictionary structure. It gets unwieldy compared to an object hierarchy, but on the other hand, it does make it easier to load filters from some json-like file structure, hmm.

The dictionary structure was partially inspired by how webgpu's render pipelines work and in my opinion it's nice to use but also very powerful. I can also look into adding json loading/saving for the filters. I mean we can already save/load particles so why not filters. If I do add that though loadFilter should probably become setFilter instead.

How is the order of render targets maintained? Dictionary insertion order?

Currently yes, but I could add a way to set the sort.

If the uniforms and shader inputs are supposed to be specified in the same load_filter call, should they not go in the same place, with a shader input dictionary containing name, type and (default) value? Or is there a reason it was done this way?

Looking at it again, it's because you can load the shader string from a file but I'll experiment and see which is more ergonomic.

Use the word "sort" instead of "order" for consistency with other parts of the Panda API.

Will do!

Having the order default to None for these filters is a breaking change. There's an order enforced currently, one which makes sense. It shouldn't suddenly make tone mapping go after sRGB encoding if someone happens to call them in a different order, with the default settings.

I was on the fence if I should assign default orders to the or not but you're correct that it could cause a lot of issues if I break that. I'll fix that breaking change.

I will think a bit more about the other aspects.

Yeah if you have any other feedback or ideas please let me know (we can also discuss in the discord if you have time). I'd love having this get merged and I believe that it'd make writing filters a lot more accessible, which in turns brings more people into Panda3D.

Also I have been working on a similar api but at the node level, I can make a pull request if you'd be interested. https://github.com/raytopianprojects/Effects

@rdb
Copy link
Member

rdb commented May 14, 2024

I'm not proposing we add JSON loading to this system—it was just an observation that it would be easy for a user to do this with this structure.

Another thought I have is that people might want to write filters in GLSL going forward, but I'm not quite sure how to address that issue here.

@raytopianprojects
Copy link
Contributor Author

raytopianprojects commented May 14, 2024

Yeah I've been thinking about GLSL authoring too.

I could spend the time converting the CG shaders to GLSL and then add the add the ability to switch between the languages but then again I don't know how many users are creating new projects using CG. So it might be worth just having GLSL as an option.

Ensure that each filter's sort is no longer a breaking change, add examples/test if CommonFilters is ran by itself, add printShader for to easily see internal shader code (should be changed to better work with Panda3D's own debug system), remove newly added filters (will be in separate pr), change how uniforms work so you just need to pass in a uniform as a dict instead of 3 separate args, and add reconfiguring to cleanup so the internal uniforms and textures dicts don't get removed when adding new filters.
@raytopianprojects
Copy link
Contributor Author

I've made changes based on your feedback rdb. I'll be creating a separate new filters pr soon.

While it no longer does anything this should fix the last breaking change.
Fix delHighDynamicRange
Change imports back to being relative
Merge tonemapping code from tonemapping pull request.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants