gaussian_blur_generator 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +72 -0
- data/bin/generate +28 -0
- data/lib/gaussian_blur_generator.rb +4 -0
- data/lib/gaussian_blur_generator/base.rb +51 -0
- data/lib/gaussian_blur_generator/glsl.rb +47 -0
- metadata +48 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4c08daccf74b7ab85e80ee4ade6521684f0ffbe2276b7a8b465578be3aad3703
|
4
|
+
data.tar.gz: 783e49eb582aa2bdffc954bda03ffcc4a888e290b14f89614dfdc0b216518d96
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a84070cf8fcc524d4da0be924258e278e444280edfb63bf45c9efcfae1efea02d12a5e7302042305ba9368ac95b80b60ab9f0a28f00c8d9592b87a9bd245ad32
|
7
|
+
data.tar.gz: ded31a9a5b06cb81647411e31cdbf1a393abddd0b6892466e87c6b982891950595a51c9a095cb94524d46f30f26d78e4c848786fde21120677a96fe8e5a9884a
|
data/README.md
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
## Gaussian Blur Generator
|
2
|
+
|
3
|
+
Generates fragment shaders that apply a Gaussian blur in an efficient manner
|
4
|
+
based on
|
5
|
+
[this article](http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/).
|
6
|
+
|
7
|
+
### Overview
|
8
|
+
|
9
|
+
Normally, a blur shader would require N^2 texture reads for a kernel of size N.
|
10
|
+
By splitting the shader in two (horizontal, then vertical) and applying a linear
|
11
|
+
sampling technique, we can reduce the number of texture reads to approximately
|
12
|
+
N, vastly improving the performance of a Gaussian blur. This can be reduced even
|
13
|
+
further by ignoring values at the edges of kernels that virtually no difference
|
14
|
+
to the output image.
|
15
|
+
|
16
|
+
### What is this gem?
|
17
|
+
|
18
|
+
This gem applies the technique from the article but generates many different
|
19
|
+
shaders with different kernel sizes. It writes these shaders to files in the
|
20
|
+
output directory, for example:
|
21
|
+
|
22
|
+
- [output/blur/x/17.frag](output/blur/x/17.frag)
|
23
|
+
- [output/blue/y/17.frag](output/blue/y/17.frag)
|
24
|
+
|
25
|
+
You can run `./bin/generate` yourself by cloning the repository or use one of
|
26
|
+
the pre-generated shaders in the [`output/blur`](output/blur) directory. These
|
27
|
+
have generated with an epsilon value of 0.05 which roughly corresponds to the
|
28
|
+
article, but you can set this yourself if you'd like:
|
29
|
+
|
30
|
+
```sh
|
31
|
+
$ ./bin/generate 0.07
|
32
|
+
```
|
33
|
+
|
34
|
+
This would set the threshold a little higher than the default 0.05.
|
35
|
+
|
36
|
+
### How do I use it?
|
37
|
+
|
38
|
+
1. Copy the x and y fragment shaders into your project for the kernel size you want.
|
39
|
+
2. Compile a shader program for each of the fragment shaders
|
40
|
+
3. Set `u_texture` to the input texture you want to blur
|
41
|
+
4. Set `u_dimensions` to the pixel size of the output: `[width, height, 1/width, 1/height]`
|
42
|
+
5. Draw your scene to a framebuffer/texture instead of directly to the screen
|
43
|
+
6. Apply the first shader program, then draw to the screen with the second
|
44
|
+
|
45
|
+
You can apply the shader to regions of the texture if you'd wish, but the
|
46
|
+
simplest approach is to apply it to a full-screen quad. In which case, a simple
|
47
|
+
vertex shader like this one will suffice:
|
48
|
+
|
49
|
+
```glsl
|
50
|
+
attribute vec4 a_position;
|
51
|
+
|
52
|
+
void main() {
|
53
|
+
gl_Position = a_position;
|
54
|
+
}
|
55
|
+
```
|
56
|
+
|
57
|
+
You may need to edit the [`glsl.rb` file](lib/gaussian_blur_generator/glsl.rb)
|
58
|
+
to produce shader code that's more appropriate for your project.
|
59
|
+
|
60
|
+
### Multiple passes?
|
61
|
+
|
62
|
+
You may find it's more efficient to apply multiple passes of a smaller blur
|
63
|
+
shader than a single pass of a larger one. This depends on a number of factors
|
64
|
+
including GPU memory, fill rate, etc. In general, two blurs of size N is
|
65
|
+
equivalent to a single blur of size `sqrt(2) * N`.
|
66
|
+
|
67
|
+
Also, since Gaussian blur is a low-pass filter, it works very well with
|
68
|
+
downsampling. See the article above for more information.
|
69
|
+
|
70
|
+
### License
|
71
|
+
|
72
|
+
MIT
|
data/bin/generate
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
LIB = File.expand_path(File.join(__dir__, "..", "lib"))
|
4
|
+
$LOAD_PATH.unshift(LIB)
|
5
|
+
|
6
|
+
EPSILON = Float(ARGV[0] || 0.05)
|
7
|
+
|
8
|
+
require "gaussian_blur_generator"
|
9
|
+
require "fileutils"
|
10
|
+
|
11
|
+
FileUtils.mkdir_p("output/blur/x")
|
12
|
+
FileUtils.mkdir_p("output/blur/y")
|
13
|
+
|
14
|
+
GaussianBlurGenerator.generate(EPSILON) do |values|
|
15
|
+
["x", "y"].each do |axis|
|
16
|
+
glsl = GaussianBlurGenerator.glsl(values, axis)
|
17
|
+
path = "output/blur/#{axis}/#{values.texture_reads}.frag"
|
18
|
+
|
19
|
+
File.write(path, glsl)
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "Written output/blur/{x,y}/#{values.texture_reads}.frag"
|
23
|
+
puts " discarded values < #{EPSILON} of peak"
|
24
|
+
puts " number of texture reads = #{values.texture_reads}"
|
25
|
+
puts " effective kernel size = #{values.effective_size}"
|
26
|
+
puts " pascal's triangle row = #{values.row_number}"
|
27
|
+
puts
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class GaussianBlurGenerator
|
2
|
+
Values = Struct.new(:offsets_d, :weights_d, :offsets_l, :weights_l, :texture_reads, :effective_size, :row_number)
|
3
|
+
|
4
|
+
def self.generate(epsilon)
|
5
|
+
row_number = 0
|
6
|
+
last_size = 0
|
7
|
+
|
8
|
+
loop do
|
9
|
+
row_number += 2
|
10
|
+
|
11
|
+
row = pascal(row_number)
|
12
|
+
sum = row.sum
|
13
|
+
peak = row[row.size / 2].to_d / sum
|
14
|
+
|
15
|
+
# Remove weights that make negligible difference to the output image.
|
16
|
+
row = row.reject { |n| n.to_d / sum < peak * epsilon }
|
17
|
+
sum = row.sum
|
18
|
+
|
19
|
+
# If we've already seen a row of this size, skip those that are more truncated.
|
20
|
+
next if row.size == last_size
|
21
|
+
last_size = row.size
|
22
|
+
|
23
|
+
midpoint = row.size / 2
|
24
|
+
next if midpoint.odd?
|
25
|
+
|
26
|
+
weights_d = row.map { |n| n.to_d / sum }[midpoint..]
|
27
|
+
offsets_d = (0..midpoint).map(&:to_d)
|
28
|
+
|
29
|
+
break if weights_d[0] == 0 # Precision limit reached
|
30
|
+
|
31
|
+
weights_l = weights_d[1..].each_slice(2).map(&:sum)
|
32
|
+
offsets_l = offsets_d[1..].each_slice(2).map do |t1, t2|
|
33
|
+
numerator = offsets_d[t1] * weights_d[t1] + offsets_d[t2] * weights_d[t2]
|
34
|
+
denominator = weights_d[t1] + weights_d[t2]
|
35
|
+
|
36
|
+
numerator / denominator
|
37
|
+
end
|
38
|
+
|
39
|
+
weights_l.unshift(weights_d.first)
|
40
|
+
offsets_l.unshift(0.0)
|
41
|
+
texture_reads = offsets_l.size * 2 - 1
|
42
|
+
|
43
|
+
yield Values.new(offsets_d, weights_d, offsets_l, weights_l, texture_reads, row.size, row_number)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.pascal(n)
|
48
|
+
@cache ||= {}
|
49
|
+
@cache[n] ||= (n == 0 ? [1] : [1, *pascal(n - 1).each_cons(2).map(&:sum), 1])
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class GaussianBlurGenerator
|
2
|
+
def self.glsl(values, axis)
|
3
|
+
offsets = values.offsets_l[1..].map.with_index do |offset, index|
|
4
|
+
"float offset#{index + 1} = one_pixel * #{offset.to_f};"
|
5
|
+
end
|
6
|
+
|
7
|
+
if axis == "x"
|
8
|
+
template = "coords.x + offset@, coords.y"
|
9
|
+
else
|
10
|
+
template = "coords.x, coords.y + offset@"
|
11
|
+
end
|
12
|
+
|
13
|
+
positives = values.weights_l[1..].map.with_index do |weight, index|
|
14
|
+
"#{weight.to_f} * texture2D(u_texture, vec2(#{template.sub("@", (index + 1).to_s)}))"
|
15
|
+
end
|
16
|
+
|
17
|
+
negatives = positives.map { |s| s.sub("+", "-") }
|
18
|
+
|
19
|
+
summation = negatives.reverse + [
|
20
|
+
"#{values.weights_l[0].to_f} * texture2D(u_texture, coords)"
|
21
|
+
] + positives
|
22
|
+
|
23
|
+
<<~GLSL
|
24
|
+
#ifdef GL_FRAGMENT_PRECISION_HIGH
|
25
|
+
precision highp float;
|
26
|
+
#else
|
27
|
+
precision mediump float;
|
28
|
+
#endif
|
29
|
+
|
30
|
+
uniform sampler2D u_texture;
|
31
|
+
uniform vec4 u_dimensions;
|
32
|
+
|
33
|
+
// Generated by https://github.com/tuzz/gaussian_blur_generator
|
34
|
+
|
35
|
+
void main() {
|
36
|
+
vec2 coords = gl_FragCoord.xy / u_dimensions.xy;
|
37
|
+
|
38
|
+
float one_pixel = u_dimensions.#{axis == "x" ? "z" : "w"};
|
39
|
+
#{offsets.join("\n ")}
|
40
|
+
|
41
|
+
gl_FragColor = (
|
42
|
+
#{summation.join(" +\n ")}
|
43
|
+
);
|
44
|
+
}
|
45
|
+
GLSL
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gaussian_blur_generator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Patuzzo
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-05-03 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Generates fragment shaders that apply a Gaussian blur in an efficient
|
14
|
+
manner.
|
15
|
+
email: chris@patuzzo.co.uk
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.md
|
21
|
+
- bin/generate
|
22
|
+
- lib/gaussian_blur_generator.rb
|
23
|
+
- lib/gaussian_blur_generator/base.rb
|
24
|
+
- lib/gaussian_blur_generator/glsl.rb
|
25
|
+
homepage: https://github.com/tuzz/gaussian_blur_generator
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.1.2
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Gaussian Blur Generator
|
48
|
+
test_files: []
|