├── pp_magic ├── plugin.cfg ├── pp_magic.gd └── compositor_effects │ └── fancy_kuwahara │ ├── shaders │ ├── kuwahara.glsl.import │ ├── eigenvectors.glsl.import │ ├── generate_sobel.glsl.import │ ├── generate_sobel.glsl │ ├── eigenvectors.glsl │ └── kuwahara.glsl │ └── fancy_kuwahara.gd ├── .gitignore ├── LICENSE └── README.md /pp_magic/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="PPMagic" 4 | description="" 5 | author="prickarz" 6 | version="" 7 | script="pp_magic.gd" 8 | -------------------------------------------------------------------------------- /pp_magic/pp_magic.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | 5 | func _enter_tree() -> void: 6 | # Initialization of the plugin goes here. 7 | pass 8 | 9 | 10 | func _exit_tree() -> void: 11 | # Clean-up of the plugin goes here. 12 | pass 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/shaders/kuwahara.glsl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="glsl" 4 | type="RDShaderFile" 5 | uid="uid://cys83fdymovu8" 6 | path="res://.godot/imported/kuwahara.glsl-53014bdd7aa61f9c8638cfd0dce73654.res" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/pp_magic/compositor_effects/fancy_kuwahara/shaders/kuwahara.glsl" 11 | dest_files=["res://.godot/imported/kuwahara.glsl-53014bdd7aa61f9c8638cfd0dce73654.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/shaders/eigenvectors.glsl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="glsl" 4 | type="RDShaderFile" 5 | uid="uid://bfn30c2uugq7n" 6 | path="res://.godot/imported/eigenvectors.glsl-a07e98d268b84ba5bc3384f963bbb821.res" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/pp_magic/compositor_effects/fancy_kuwahara/shaders/eigenvectors.glsl" 11 | dest_files=["res://.godot/imported/eigenvectors.glsl-a07e98d268b84ba5bc3384f963bbb821.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/shaders/generate_sobel.glsl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="glsl" 4 | type="RDShaderFile" 5 | uid="uid://myapjpogoswt" 6 | path="res://.godot/imported/generate_sobel.glsl-971b7558827a5716c46f04af058d5c36.res" 7 | 8 | [deps] 9 | 10 | source_file="res://addons/pp_magic/compositor_effects/fancy_kuwahara/shaders/generate_sobel.glsl" 11 | dest_files=["res://.godot/imported/generate_sobel.glsl-971b7558827a5716c46f04af058d5c36.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Peter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PPMagic 2 | Godot post processing effects using the new CompositorEffect stack in 4.3 3 | 4 | DISCLAIMER: I'm not a graphics programmer by any means, so this might be unoptimized/doing weird things. I'm just sharing this because it looks cool, and there's very little resources on this new system in Godot. 5 | 6 | Currently, the only effect added is an Anisotropic Kuwahara, with a bunch of exposed settings to achieve a range of painterly looks. 7 | 8 | Also, this is an example of a multi pass shader, with a sobel, eigenvector and kuwahara pass, reading and writing to different buffers. Check the compositor_effects/fancy_kuwahara/fancy_kuwahara.gd to see how it's set up. 9 | 10 | # Installation 11 | Put the pp_magic folder into the res://addons folder of your project 12 | # Usage 13 | * On your Camera3D or WorldEnvironment node, on the Compositor field, add a New Compositor 14 | * Click Add Element 15 | * Select New FancyKuwahara 16 | * Play with parameters to your liking - Note: The Kuwahara Radius can make this shader quite heavy, as the render time grows exponentially with the radius. 17 | # Screenshots 18 | They're intentionally not fullscreen, so you can see the settings used 19 | ## Photo Examples 20 | On a quad in Godot, because the effect works best on high detail scenes, that I currently don't have :( 3D scene examples further down 21 | 22 | ![image1](https://i.ibb.co/bm5Jcgf/photo4.png) 23 | ![image2](https://i.ibb.co/L1y5LMp/photo1.png) 24 | ## Game Scene Examples 25 | ![image1](https://i.ibb.co/bPgLMd5/spaceship-high-alpha1.png) 26 | ![image2](https://i.ibb.co/718m2wc/spaceship-low-alpha1.png) 27 | ![image3](https://i.ibb.co/WGyYWvf/spaceship-high-alpha2.png) 28 | -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/shaders/generate_sobel.glsl: -------------------------------------------------------------------------------- 1 | #[compute] 2 | #version 450 3 | 4 | // Invocations in the (x, y, z) dimension 5 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 6 | 7 | layout(rgba16f, set = 0, binding = 0) uniform image2D color_image; 8 | layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2D sobel_image; 9 | 10 | // Our push constant 11 | layout(push_constant, std430) uniform Params { 12 | vec2 raster_size; 13 | vec2 reserved; 14 | } params; 15 | 16 | // The code we want to execute in each invocation 17 | void main() { 18 | ivec2 uv = ivec2(gl_GlobalInvocationID.xy); 19 | ivec2 size = ivec2(params.raster_size); 20 | 21 | // Prevent reading/writing out of bounds. 22 | if (uv.x >= size.x || uv.y >= size.y) { 23 | return; 24 | } 25 | 26 | // Read from our color buffer. 27 | vec4 color = imageLoad(color_image, uv); 28 | 29 | vec3 Sx = ( 30 | 1.0 * imageLoad(color_image, uv + ivec2(-1, -1)).rgb + 31 | 2.0 * imageLoad(color_image, uv + ivec2(-1, 0)).rgb + 32 | 1.0 * imageLoad(color_image, uv + ivec2(-1, 1)).rgb + 33 | -1.0 * imageLoad(color_image, uv + ivec2(1, -1)).rgb + 34 | -2.0 * imageLoad(color_image, uv + ivec2(1, 0)).rgb + 35 | -1.0 * imageLoad(color_image, uv + ivec2(1, 1)).rgb 36 | ) / 4.0; 37 | 38 | vec3 Sy = ( 39 | 1.0 * imageLoad(color_image, uv + ivec2(-1, -1)).rgb + 40 | 2.0 * imageLoad(color_image, uv + ivec2(0, -1)).rgb + 41 | 1.0 * imageLoad(color_image, uv + ivec2(1, -1)).rgb + 42 | -1.0 * imageLoad(color_image, uv + ivec2(-1, 1)).rgb + 43 | -2.0 * imageLoad(color_image, uv + ivec2(0, 1)).rgb + 44 | -1.0 * imageLoad(color_image, uv + ivec2(1, 1)).rgb 45 | ) / 4.0; 46 | 47 | vec4 sobel = vec4(dot(Sx, Sx), dot(Sy,Sy),dot(Sx,Sy),1.0); 48 | 49 | // uncomment to preview in editor 50 | //imageStore(color_image, uv, clamp(sobel,0,1)); 51 | imageStore(sobel_image, uv, sobel); 52 | } -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/shaders/eigenvectors.glsl: -------------------------------------------------------------------------------- 1 | #[compute] 2 | #version 450 3 | 4 | #define PI 3.14159265358979323846f 5 | 6 | // Invocations in the (x, y, z) dimension 7 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 8 | 9 | layout(rgba16f, set = 0, binding = 0) uniform image2D sobel_image; 10 | layout(rgba16f, set = 1, binding = 0) uniform restrict writeonly image2D eigenvector_image; 11 | 12 | // Our push constant 13 | layout(push_constant, std430) uniform Params { 14 | vec2 raster_size; 15 | float sigma; 16 | float smooth_tensor; 17 | } params; 18 | 19 | vec3 gauss(ivec2 uv, float sigma){ 20 | 21 | float two_sigma_squared = 2.0 * sigma * sigma; 22 | int half_width = int(ceil( 2.0 * sigma )); 23 | 24 | vec3 sum = vec3(0.0); 25 | float norm = 0.0; 26 | if (half_width > 0) { 27 | for ( int i = -half_width; i <= half_width; ++i ) { 28 | for ( int j = -half_width; j <= half_width; ++j ) { 29 | float d = length(vec2(i,j)); 30 | float kernel = exp( -d *d / two_sigma_squared ); 31 | vec3 c = imageLoad(sobel_image, uv + ivec2(i,j) ).rgb; 32 | sum += kernel * c; 33 | norm += kernel; 34 | } 35 | } 36 | } else { 37 | sum = imageLoad(sobel_image, uv).rgb; 38 | norm = 1.0; 39 | } 40 | return sum / norm; 41 | 42 | } 43 | 44 | 45 | // The code we want to execute in each invocation 46 | void main() { 47 | ivec2 uv = ivec2(gl_GlobalInvocationID.xy); 48 | ivec2 size = ivec2(params.raster_size); 49 | 50 | // Prevent reading/writing out of bounds. 51 | if (uv.x >= size.x || uv.y >= size.y) { 52 | return; 53 | } 54 | 55 | vec3 g = gauss(uv, params.sigma); 56 | 57 | float lambda1 = 0.5 * (g.y + g.x + 58 | sqrt(g.y*g.y - 2.0*g.x*g.y + g.x*g.x + 4.0*g.z*g.z)); 59 | float lambda2 = 0.5 * (g.y + g.x - 60 | sqrt(g.y*g.y - 2.0*g.x*g.y + g.x*g.x + 4.0*g.z*g.z)); 61 | 62 | vec2 v = vec2(lambda1 - g.x, -g.z); 63 | vec2 t; 64 | if (length(v) > 0.0) { 65 | t = normalize(v); 66 | } else { 67 | t = vec2(0.0, 1.0); 68 | } 69 | 70 | float phi = atan(t.y, t.x); 71 | 72 | float A = (lambda1 + lambda2 > 0.0)? 73 | (lambda1 - lambda2) / (lambda1 + lambda2) : 0.0; 74 | 75 | 76 | 77 | imageStore(eigenvector_image, uv, vec4(t, phi, A)); 78 | } -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/shaders/kuwahara.glsl: -------------------------------------------------------------------------------- 1 | #[compute] 2 | #version 450 3 | 4 | #define PI 3.14159265358979323846f 5 | 6 | // Invocations in the (x, y, z) dimension 7 | layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; 8 | 9 | layout(rgba16f, set = 0, binding = 0) uniform image2D color_image; 10 | layout(rgba16f, set = 1, binding = 0) uniform image2D blur_pass_2_image; 11 | layout(set = 2, binding = 0) uniform sampler2D depth_sampler; 12 | 13 | // Our push constant 14 | layout(push_constant, std430) uniform Params { 15 | vec2 raster_size; 16 | float radius; 17 | float sharpness; 18 | float hardness; 19 | float alpha; 20 | float depth_scale; 21 | float min_radius; 22 | } params; 23 | 24 | const int N = 8; 25 | const float HALF_SQRT2 = 0.7071067811865475244f; 26 | 27 | 28 | 29 | // The code we want to execute in each invocation 30 | void main() { 31 | ivec2 uv = ivec2(gl_GlobalInvocationID.xy); 32 | ivec2 size = ivec2(params.raster_size); 33 | 34 | // Prevent reading/writing out of bounds. 35 | if (uv.x >= size.x || uv.y >= size.y) { 36 | return; 37 | } 38 | 39 | float alpha = params.alpha; 40 | vec4 t = imageLoad(blur_pass_2_image, uv); 41 | 42 | float radius = params.radius; 43 | 44 | float depth = clamp(textureLod(depth_sampler, vec2(uv)/size, 0).r*params.depth_scale*100,0,1); 45 | radius = clamp(radius*depth,params.min_radius,radius); 46 | 47 | 48 | 49 | float a = float((radius)) * clamp((alpha + t.w) / alpha, 0.1f, 2.0f); 50 | float b = float((radius)) * clamp(alpha / (alpha + t.w), 0.1f, 2.0f); 51 | 52 | float cos_phi = cos(t.z); 53 | float sin_phi = sin(t.z); 54 | 55 | 56 | mat2 SR = mat2(cos_phi/a, -sin_phi/b, sin_phi/a, cos_phi/b); 57 | 58 | float aa = a * a; 59 | float bb = b * b; 60 | float coscos_phi = cos_phi * cos_phi; 61 | float sinsin_phi = sin_phi * sin_phi; 62 | 63 | int max_x = int(sqrt(aa * coscos_phi + bb * sinsin_phi)); 64 | int max_y = int(sqrt(aa * sinsin_phi + bb * coscos_phi)); 65 | 66 | 67 | int k; 68 | vec4 m[8]; 69 | vec3 s[8]; 70 | { 71 | vec3 c = imageLoad(color_image, uv).rgb; 72 | float w = 1.0f / float(N); 73 | for (int k = 0; k < N; ++k) { 74 | m[k] = vec4(c * w, w); 75 | s[k] = c * c * w; 76 | } 77 | } 78 | 79 | for (int j = 0; j <= max_y; ++j) { 80 | for (int i = -max_x; i <= max_x; ++i) { 81 | if ((j !=0) || (i > 0)) { 82 | vec2 v = SR * vec2(i,j); 83 | 84 | float dotv = dot(v,v); 85 | 86 | if (dotv <= 1.0f) { 87 | vec3 c0 = imageLoad(color_image, uv + ivec2(i,j)).rgb; 88 | vec3 c1 = imageLoad(color_image, uv - ivec2(i,j)).rgb; 89 | 90 | vec3 cc0 = c0 * c0; 91 | vec3 cc1 = c1 * c1; 92 | 93 | float sum = 0.0f; 94 | float w[8]; 95 | float z, vxx, vyy; 96 | 97 | vxx = 0.33f - 3.77f * v.x * v.x; 98 | vyy = 0.33f - 3.77f * v.y * v.y; 99 | z = max(0.0f, v.y + vxx); sum += w[0] = z * z; 100 | z = max(0.0f, -v.x + vyy); sum += w[2] = z * z; 101 | z = max(0.0f, -v.y + vxx); sum += w[4] = z * z; 102 | z = max(0.0f, v.x + vyy); sum += w[6] = z * z; 103 | 104 | v = HALF_SQRT2 * vec2( v.x - v.y, v.x + v.y ); 105 | 106 | vxx = 0.33f - 3.77f * v.x * v.x; 107 | vyy = 0.33f - 3.77f * v.y * v.y; 108 | z = max(0.0f, v.y + vxx); sum += w[1] = z * z; 109 | z = max(0.0f, -v.x + vyy); sum += w[3] = z * z; 110 | z = max(0.0f, -v.y + vxx); sum += w[5] = z * z; 111 | z = max(0.0f, v.x + vyy); sum += w[7] = z * z; 112 | 113 | float g = exp(-3.125f * dotv) / sum; 114 | 115 | for (int k = 0; k < N; ++k) { 116 | float wk = w[k] * g; 117 | m[k] += vec4(c0 * wk, wk); 118 | s[k] += cc0 * wk; 119 | m[(k+4)&7] += vec4(c1 * wk, wk); 120 | s[(k+4)&7] += cc1 * wk; 121 | } 122 | } 123 | } 124 | } 125 | } 126 | 127 | vec4 color_output = vec4(0); 128 | for (k = 0; k < N; ++k) { 129 | m[k].rgb /= m[k].w; 130 | s[k] = abs(s[k] / m[k].w - m[k].rgb * m[k].rgb); 131 | 132 | float sigma2 = s[k].r + s[k].g + s[k].b; 133 | float w = 1.0f / (1.0f + pow(params.hardness * 1000.0f * sigma2, 0.5f * params.sharpness)); 134 | 135 | color_output += vec4(m[k].rgb * w, w); 136 | } 137 | 138 | color_output = clamp(color_output/color_output.w,0,1); 139 | 140 | 141 | 142 | imageStore(color_image, uv, color_output); 143 | } -------------------------------------------------------------------------------- /pp_magic/compositor_effects/fancy_kuwahara/fancy_kuwahara.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends CompositorEffect 3 | class_name FancyKuwahara 4 | 5 | @export_range(1,30) var kuwahara_radius: int = 10 6 | @export_range(1,15) var sharpness: float = 10 7 | @export_range(0,10) var hardness: float = 10 8 | @export_range(0,5) var alpha: float = .5 9 | @export_range(0,10) var depth_scale: float = 1 10 | @export_range(1,30) var min_radius: int = 2 11 | @export_range(.5,10) var sigma: float = 2.5 12 | 13 | const RESERVED: float = 0 14 | 15 | var rd: RenderingDevice 16 | 17 | var nearest_sampler : RID 18 | var linear_sampler : RID 19 | 20 | var sobel_shader: RID 21 | var sobel_pipeline: RID 22 | 23 | var eigenvectors_shader: RID 24 | var eigenvectors_pipeline: RID 25 | 26 | var kuwahara_shader: RID 27 | var kuwahara_pipeline: RID 28 | 29 | var context: StringName = "FancyKuwahara" 30 | var sobel: StringName = "Sobel" 31 | var kuwahara: StringName = "Kuwahara" 32 | var eigenvectors: StringName = "EigenVectors" 33 | 34 | 35 | func get_sampler_uniform(image : RID, binding : int = 0, linear : bool = true) -> RDUniform: 36 | var uniform : RDUniform = RDUniform.new() 37 | uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE 38 | uniform.binding = binding 39 | if linear: 40 | uniform.add_id(linear_sampler) 41 | else: 42 | uniform.add_id(nearest_sampler) 43 | uniform.add_id(image) 44 | 45 | return uniform 46 | 47 | 48 | func get_image_uniform(image : RID, binding : int = 0) -> RDUniform: 49 | var uniform : RDUniform = RDUniform.new() 50 | uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE 51 | uniform.binding = binding 52 | uniform.add_id(image) 53 | 54 | return uniform 55 | 56 | 57 | func _init() -> void: 58 | effect_callback_type = EFFECT_CALLBACK_TYPE_POST_TRANSPARENT 59 | rd = RenderingServer.get_rendering_device() 60 | RenderingServer.call_on_render_thread(_initialize_compute) 61 | 62 | 63 | # System notifications, we want to react on the notification that 64 | # alerts us we are about to be destroyed. 65 | func _notification(what: int) -> void: 66 | if what == NOTIFICATION_PREDELETE: 67 | # Freeing our shader will also free any dependents such as the pipeline! 68 | if sobel_shader.is_valid(): 69 | RenderingServer.free_rid(sobel_shader) 70 | if kuwahara_shader.is_valid(): 71 | RenderingServer.free_rid(kuwahara_shader) 72 | if eigenvectors_shader.is_valid(): 73 | RenderingServer.free_rid(eigenvectors_shader) 74 | if nearest_sampler.is_valid(): 75 | rd.free_rid(nearest_sampler) 76 | if linear_sampler.is_valid(): 77 | rd.free_rid(linear_sampler) 78 | 79 | 80 | # Code in this region runs on the rendering thread. 81 | # Compile our shader at initialization. 82 | func _initialize_compute() -> void: 83 | rd = RenderingServer.get_rendering_device() 84 | if not rd: 85 | return 86 | 87 | 88 | var sampler_state : RDSamplerState 89 | 90 | sampler_state = RDSamplerState.new() 91 | sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_NEAREST 92 | sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_NEAREST 93 | nearest_sampler = rd.sampler_create(sampler_state) 94 | 95 | sampler_state = RDSamplerState.new() 96 | sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_LINEAR 97 | sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_LINEAR 98 | linear_sampler = rd.sampler_create(sampler_state) 99 | 100 | 101 | ######### SOBEL 102 | var sobel_shader_file := load("res://addons/pp_magic/compositor_effects/fancy_kuwahara/shaders/generate_sobel.glsl") 103 | var sobel_shader_spirv: RDShaderSPIRV = sobel_shader_file.get_spirv() 104 | sobel_shader = rd.shader_create_from_spirv(sobel_shader_spirv) 105 | if sobel_shader.is_valid(): 106 | sobel_pipeline = rd.compute_pipeline_create(sobel_shader) 107 | 108 | ##### EIGENVECTORS 109 | var eigenvectors_shader_file := load("res://addons/pp_magic/compositor_effects/fancy_kuwahara/shaders/eigenvectors.glsl") 110 | var eigenvectors_shader_spirv: RDShaderSPIRV = eigenvectors_shader_file.get_spirv() 111 | eigenvectors_shader = rd.shader_create_from_spirv(eigenvectors_shader_spirv) 112 | if eigenvectors_shader.is_valid(): 113 | eigenvectors_pipeline = rd.compute_pipeline_create(eigenvectors_shader) 114 | 115 | ###### KUWAHARA 116 | var kuwahara_shader_file := load("res://addons/pp_magic/compositor_effects/fancy_kuwahara/shaders/kuwahara.glsl") 117 | var kuwahara_shader_spirv: RDShaderSPIRV = kuwahara_shader_file.get_spirv() 118 | kuwahara_shader = rd.shader_create_from_spirv(kuwahara_shader_spirv) 119 | if kuwahara_shader.is_valid(): 120 | kuwahara_pipeline = rd.compute_pipeline_create(kuwahara_shader) 121 | 122 | 123 | # Called by the rendering thread every frame. 124 | func _render_callback(p_effect_callback_type: EffectCallbackType, p_render_data: RenderData) -> void: 125 | if ( 126 | !rd or 127 | p_effect_callback_type != EFFECT_CALLBACK_TYPE_POST_TRANSPARENT or 128 | !sobel_pipeline.is_valid() or 129 | !kuwahara_pipeline.is_valid() or 130 | !eigenvectors_pipeline.is_valid() 131 | ): 132 | return 133 | 134 | # Get our render scene buffers object, this gives us access to our render buffers. 135 | # Note that implementation differs per renderer hence the need for the cast. 136 | var render_scene_buffers := p_render_data.get_render_scene_buffers() 137 | if render_scene_buffers: 138 | # Get our render size, this is the 3D render resolution! 139 | var size: Vector2i = render_scene_buffers.get_internal_size() 140 | if size.x == 0 and size.y == 0: 141 | return 142 | 143 | # Calculate x groups("render tiles") 144 | @warning_ignore("integer_division") 145 | var x_groups := (size.x - 1) / 8 + 1 146 | @warning_ignore("integer_division") 147 | var y_groups := (size.y - 1) / 8 + 1 148 | var z_groups := 1 149 | 150 | ## CREATE TEXTURES 151 | var usage_bits : int = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT | RenderingDevice.TEXTURE_USAGE_STORAGE_BIT 152 | ## CREATE SOBEL TEXTURE 153 | render_scene_buffers.create_texture( 154 | context, 155 | sobel, 156 | RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT, 157 | usage_bits, 158 | RenderingDevice.TEXTURE_SAMPLES_1, 159 | size, 1, 1, true) 160 | 161 | ## CREATE EIGENVECTORS TEXTURE 162 | render_scene_buffers.create_texture( 163 | context, 164 | eigenvectors, 165 | RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT, 166 | usage_bits, 167 | RenderingDevice.TEXTURE_SAMPLES_1, 168 | size, 1, 1, true) 169 | 170 | ## CREATE PUSH CONSTANTS 171 | # Must be aligned to 16 bytes and be in the same order as defined in the shader. 172 | var sobel_push_constant := PackedFloat32Array([ 173 | size.x, 174 | size.y, 175 | RESERVED, 176 | RESERVED, 177 | ]) 178 | 179 | var eigenvectors_push_constant := PackedFloat32Array([ 180 | size.x, 181 | size.y, 182 | sigma, 183 | 0.0, 184 | ]) 185 | 186 | var kuwahara_push_constant := PackedFloat32Array([ 187 | size.x, 188 | size.y, 189 | float(kuwahara_radius), 190 | sharpness, 191 | hardness*10, 192 | alpha, 193 | depth_scale, 194 | float(min_radius) 195 | ]) 196 | 197 | ## RUN SHADERS 198 | # Loop through views just in case we're doing stereo rendering(VR). No extra cost if this is mono. 199 | var view_count: int = render_scene_buffers.get_view_count() 200 | for view in view_count: 201 | 202 | var uniform: RDUniform 203 | 204 | ############## SOBEL 205 | uniform = get_image_uniform(render_scene_buffers.get_color_layer(view)) 206 | var sobel_in_color_uniform_set := UniformSetCacheRD.get_cache(sobel_shader, 0, [uniform]) 207 | 208 | uniform = get_image_uniform(render_scene_buffers.get_texture_slice(context, sobel, view, 0, 1, 1)) 209 | var sobel_out_uniform_set := UniformSetCacheRD.get_cache(sobel_shader, 1, [uniform]) 210 | 211 | # Run Shader 212 | var compute_list := rd.compute_list_begin() 213 | rd.compute_list_bind_compute_pipeline(compute_list, sobel_pipeline) 214 | rd.compute_list_bind_uniform_set(compute_list, sobel_in_color_uniform_set, 0) 215 | rd.compute_list_bind_uniform_set(compute_list, sobel_out_uniform_set, 1) 216 | rd.compute_list_set_push_constant(compute_list, sobel_push_constant.to_byte_array(), sobel_push_constant.size() * 4) 217 | rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups) 218 | rd.compute_list_end() 219 | 220 | 221 | 222 | #### EIGENVECTORS 223 | uniform = get_image_uniform(render_scene_buffers.get_texture_slice(context, sobel, view, 0, 1, 1)) 224 | var eigenvectors_in_sobel_uniform_set = UniformSetCacheRD.get_cache(eigenvectors_shader, 1, [uniform]) 225 | 226 | uniform = get_image_uniform(render_scene_buffers.get_texture_slice(context, eigenvectors, view, 0, 1, 1)) 227 | var eigenvectors_out_uniform_set = UniformSetCacheRD.get_cache(eigenvectors_shader, 1, [uniform]) 228 | 229 | # Run Shader 230 | compute_list = rd.compute_list_begin() 231 | rd.compute_list_bind_compute_pipeline(compute_list, eigenvectors_pipeline) 232 | rd.compute_list_bind_uniform_set(compute_list, eigenvectors_in_sobel_uniform_set, 0) 233 | rd.compute_list_bind_uniform_set(compute_list, eigenvectors_out_uniform_set, 1) 234 | rd.compute_list_set_push_constant(compute_list, eigenvectors_push_constant.to_byte_array(), eigenvectors_push_constant.size() * 4) 235 | rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups) 236 | rd.compute_list_end() 237 | 238 | 239 | #### KUWAHARA 240 | uniform = get_image_uniform(render_scene_buffers.get_color_layer(view)) 241 | var kuwahara_color_uniform_set = UniformSetCacheRD.get_cache(kuwahara_shader, 0, [uniform]) 242 | 243 | uniform = get_image_uniform(render_scene_buffers.get_texture_slice(context, eigenvectors, view, 0, 1, 1)) 244 | var kuwahara_in_eigenvectors_uniform_set = UniformSetCacheRD.get_cache(kuwahara_shader, 1, [uniform]) 245 | 246 | uniform = get_sampler_uniform(render_scene_buffers.get_depth_layer(view)) 247 | var kuwahara_in_depth_uniform_set = UniformSetCacheRD.get_cache(kuwahara_shader, 2, [uniform]) 248 | 249 | # Run Shader 250 | compute_list = rd.compute_list_begin() 251 | rd.compute_list_bind_compute_pipeline(compute_list, kuwahara_pipeline) 252 | rd.compute_list_bind_uniform_set(compute_list, kuwahara_color_uniform_set, 0) 253 | rd.compute_list_bind_uniform_set(compute_list, kuwahara_in_eigenvectors_uniform_set, 1) 254 | rd.compute_list_bind_uniform_set(compute_list, kuwahara_in_depth_uniform_set, 2) 255 | rd.compute_list_set_push_constant(compute_list, kuwahara_push_constant.to_byte_array(), kuwahara_push_constant.size() * 4) 256 | rd.compute_list_dispatch(compute_list, x_groups, y_groups, z_groups) 257 | rd.compute_list_end() 258 | --------------------------------------------------------------------------------