1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
# -*- coding: utf-8 -*-
"""
Jump flooding algoritm for EDT using GLSL code:
Author: Stefan Gustavson (stefan.gustavson@gmail.com)
2010-08-24. This code is in the public domain.
Adapted to `vispy` by Eric Larson <larson.eric.d@gmail.com>.
"""
import numpy as np
from ...gloo import (Program, FrameBuffer, VertexBuffer, Texture2D,
set_viewport, set_state)
vert_seed = """
attribute vec2 a_position;
attribute vec2 a_texcoord;
varying vec2 v_uv;
void main( void )
{
v_uv = a_texcoord.xy;
gl_Position = vec4(a_position.xy, 0., 1.);
}
"""
vert = """
uniform float u_texw;
uniform float u_texh;
uniform float u_step;
attribute vec2 a_position;
attribute vec2 a_texcoord;
varying float v_stepu;
varying float v_stepv;
varying vec2 v_uv;
void main( void )
{
v_uv = a_texcoord.xy;
v_stepu = u_step / u_texw; // Saves a division in the fragment shader
v_stepv = u_step / u_texh;
gl_Position = vec4(a_position.xy, 0., 1.);
}
"""
frag_seed = """
uniform sampler2D u_texture;
varying vec2 v_uv;
void main( void )
{
float pixel = texture2D(u_texture, v_uv).r;
vec4 myzero = vec4(128. / 255., 128. / 255., 0., 0.); // Zero
vec4 myinfinity = vec4(0., 0., 0., 0.); // Infinity
// Pixels >= 0.5 are objects, others are background
gl_FragColor = pixel >= 0.5 ? myzero : myinfinity;
}
"""
frag_flood = """
uniform sampler2D u_texture;
varying float v_stepu;
varying float v_stepv;
varying vec2 v_uv;
vec2 remap(vec4 floatdata) {
vec2 scaleddata = vec2(floatdata.x * 65280. + floatdata.z * 255.,
floatdata.y * 65280. + floatdata.w * 255.);
return scaleddata / 32768. - 1.0;
}
vec4 remap_inv(vec2 floatvec) {
vec2 data = (floatvec + 1.0) * 32768.;
float x = floor(data.x / 256.);
float y = floor(data.y / 256.);
return vec4(x, y, data.x - x * 256., data.y - y * 256.) / 255.;
}
void main( void )
{
// Search for better distance vectors among 8 candidates
vec2 stepvec; // Relative offset to candidate being tested
vec2 newvec; // Absolute position of that candidate
vec3 newseed; // Closest point from that candidate (.xy) and its dist (.z)
vec3 bestseed; // Closest seed so far
bestseed.xy = remap(texture2D(u_texture, v_uv).rgba);
bestseed.z = length(bestseed.xy);
stepvec = vec2(-v_stepu, -v_stepv);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(-v_stepu, 0.0);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(-v_stepu, v_stepv);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(0.0, -v_stepv);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(0.0, v_stepv);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(v_stepu, -v_stepv);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(v_stepu, 0.0);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
stepvec = vec2(v_stepu, v_stepv);
newvec = v_uv + stepvec;
if (all(bvec4(lessThan(newvec, vec2(1.0)), greaterThan(newvec, vec2(0.0))))){
newseed.xy = remap(texture2D(u_texture, newvec).rgba);
if(newseed.x > -0.99999) { // if the new seed is not "indeterminate dist"
newseed.xy = newseed.xy + stepvec;
newseed.z = length(newseed.xy);
if(newseed.z < bestseed.z) {
bestseed = newseed;
}
}
}
gl_FragColor = remap_inv(bestseed.xy);
}
"""
frag_insert = """
uniform sampler2D u_texture;
uniform sampler2D u_pos_texture;
uniform sampler2D u_neg_texture;
varying float v_stepu;
varying float v_stepv;
varying vec2 v_uv;
vec2 remap(vec4 floatdata) {
vec2 scaled_data = vec2(floatdata.x * 65280. + floatdata.z * 255.,
floatdata.y * 65280. + floatdata.w * 255.);
return scaled_data / 32768. - 1.0;
}
void main( void )
{
float pixel = texture2D(u_texture, v_uv).r;
// convert distance from normalized units -> pixels
vec2 rescale = vec2(v_stepu, v_stepv);
float shrink = 8.;
rescale = rescale * 256. / shrink;
// Without the division, 1 RGB increment = 1 px distance
vec2 pos_distvec = remap(texture2D(u_pos_texture, v_uv).rgba) / rescale;
vec2 neg_distvec = remap(texture2D(u_neg_texture, v_uv).rgba) / rescale;
if (pixel <= 0.5)
gl_FragColor = vec4(0.5 - length(pos_distvec));
else
gl_FragColor = vec4(0.5 - (shrink - 1.) / 256. + length(neg_distvec));
}
"""
class SDFRendererGPU(object):
def __init__(self):
self.program_seed = Program(vert_seed, frag_seed)
self.program_flood = Program(vert, frag_flood)
self.program_insert = Program(vert, frag_insert)
self.programs = [self.program_seed, self.program_flood,
self.program_insert]
# Initialize variables
self.fbo_to = [FrameBuffer(), FrameBuffer(), FrameBuffer()]
vtype = np.dtype([('a_position', np.float32, 2),
('a_texcoord', np.float32, 2)])
vertices = np.zeros(4, dtype=vtype)
vertices['a_position'] = [[-1., -1.], [-1., 1.], [1., -1.], [1., 1.]]
vertices['a_texcoord'] = [[0., 0.], [0., 1.], [1., 0.], [1., 1.]]
vertices = VertexBuffer(vertices)
self.program_insert['u_step'] = 1.
for program in self.programs:
program.bind(vertices)
def render_to_texture(self, data, texture, offset, size):
"""Render a SDF to a texture at a given offset and size
Parameters
----------
data : array
Must be 2D with type np.ubyte.
texture : instance of Texture2D
The texture to render to.
offset : tuple of int
Offset (x, y) to render to inside the texture.
size : tuple of int
Size (w, h) to render inside the texture.
"""
assert isinstance(texture, Texture2D)
set_state(blend=False, depth_test=False)
# calculate the negative half (within object)
orig_tex = Texture2D(255 - data, format='luminance',
wrapping='clamp_to_edge', interpolation='nearest')
edf_neg_tex = self._render_edf(orig_tex)
# calculate positive half (outside object)
orig_tex[:, :, 0] = data
edf_pos_tex = self._render_edf(orig_tex)
# render final product to output texture
self.program_insert['u_texture'] = orig_tex
self.program_insert['u_pos_texture'] = edf_pos_tex
self.program_insert['u_neg_texture'] = edf_neg_tex
self.fbo_to[-1].color_buffer = texture
with self.fbo_to[-1]:
set_viewport(tuple(offset) + tuple(size))
self.program_insert.draw('triangle_strip')
def _render_edf(self, orig_tex):
"""Render an EDF to a texture"""
# Set up the necessary textures
sdf_size = orig_tex.shape[:2]
comp_texs = []
for _ in range(2):
tex = Texture2D(sdf_size + (4,), format='rgba',
interpolation='nearest', wrapping='clamp_to_edge')
comp_texs.append(tex)
self.fbo_to[0].color_buffer = comp_texs[0]
self.fbo_to[1].color_buffer = comp_texs[1]
for program in self.programs[1:]: # program_seed does not need this
program['u_texh'], program['u_texw'] = sdf_size
# Do the rendering
last_rend = 0
with self.fbo_to[last_rend]:
set_viewport(0, 0, sdf_size[1], sdf_size[0])
self.program_seed['u_texture'] = orig_tex
self.program_seed.draw('triangle_strip')
stepsize = (np.array(sdf_size) // 2).max()
while stepsize > 0:
self.program_flood['u_step'] = stepsize
self.program_flood['u_texture'] = comp_texs[last_rend]
last_rend = 1 if last_rend == 0 else 0
with self.fbo_to[last_rend]:
set_viewport(0, 0, sdf_size[1], sdf_size[0])
self.program_flood.draw('triangle_strip')
stepsize //= 2
return comp_texs[last_rend]
|