refactor stroke drawing

This commit is contained in:
2019-02-11 22:37:02 +01:00
parent 171ab31b47
commit 8ad005de8b
4 changed files with 221 additions and 199 deletions

View File

@@ -272,77 +272,149 @@ void Canvas::stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz)
gl.restore();
}
void Canvas::stroke_draw()
std::array<std::vector<vertex_t>, 6> Canvas::stroke_draw_project(std::array<vertex_t, 4>& B)
{
if (!(m_current_stroke && m_current_stroke->has_sample()))
// intersect P with the current face to clip diverging points from the plane
auto unp_vp = zw(m_box);
auto unp_inv = glm::inverse(m_proj * m_mv);
std::array<std::vector<vertex_t>, 6> ret;
for (int i = 0; i < 6; i++)
{
//stroke_draw_mix({ 0,0 }, { m_mixer.getWidth(), m_mixer.getHeight() });
return;
auto P = poly_intersect(std::data(B), std::data(B) + 4, m_plane_shape[i]);
glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]);
int intersections = 0;
for (int j = 0; j < P.size(); j++)
{
glm::vec3 ray_origin, ray_dir;
//if (s.pos.z == 0)
{
//point_unproject(P[j].pos, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir);
auto clip_space = glm::vec2(P[j].pos.x, unp_vp.y - P[j].pos.y - 1.f) / unp_vp * 2.f - 1.f;
auto wp0 = unp_inv * glm::vec4(clip_space, 0, 1);
auto wp1 = unp_inv * glm::vec4(clip_space, .5, 1);
ray_origin = xyz(wp0 / wp0.w);
ray_dir = glm::normalize(xyz(wp1 / wp1.w) - ray_origin);
}
//else
//{
// auto m = glm::inverse(glm::lookAt({ 0, 0, 0 }, s.pos, { 0, 1, 0 }));
// glm::vec3 off_3d = m * glm::vec4(off[j], 0, 1);
// ray_origin = glm::vec3(0);
// ray_dir = s.pos + off_3d;
//}
glm::vec3 hit;
float hit_t;
if (ray_intersect(ray_origin, ray_dir, m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i], hit, hit_t))
{
glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
//P[j].uvs2 = xy(P[j].pos) / glm::vec2(App::I.width, App::I.height);
P[j].pos.x = -(plane_local.x * 0.5f - 0.5f) * m_width;
P[j].pos.y = (plane_local.y * 0.5f + 0.5f) * m_height;
// Black magic - BEWARE!
// interpolation perspective correction, use the current camera projection to correct the interpolation
// because the new shape will have z fixed with an ortho projection when drawn to the face
// we need to imitate the same perspective as the once in the camera
// see: https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/perspective-correct-interpolation-vertex-attributes
auto hit_cam = m_mv * glm::vec4(hit, 1);
P[j].pos.z = 0;
P[j].pos.w = hit_cam.z;
P[j].uvs *= hit_cam.z;
P[j].uvs2 *= hit_cam.z;
intersections++;
}
else
{
break;
}
}
if (intersections >= 3)
ret[i] = P;
}
return ret;
}
void Canvas::stroke_draw_samples(int i, std::vector<vertex_t>& P)
{
if (!ShaderManager::ext_framebuffer_fetch)
{
glActiveTexture(GL_TEXTURE1);
m_tex[i].bind(); // bg, copy of framebuffer (copied before drawing)
}
m_dirty = true;
glm::vec2 bb_min(m_width, m_height);
glm::vec2 bb_max(0, 0);
for (int j = 0; j < P.size(); j++)
{
bb_min = glm::max({ 0, 0 }, glm::min(bb_min, xy(P[j].pos)));
bb_max = glm::min({ m_width, m_height }, glm::max(bb_max, xy(P[j].pos)));
}
auto bb_sz = bb_max - bb_min;
GLint vp[4];
GLfloat cc[4];
glGetIntegerv(GL_VIEWPORT, vp);
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
float zoom = m_node->root()->m_zoom;
glm::vec2 pad(1);
glm::ivec2 tex_pos = glm::clamp(glm::floor(bb_min) - pad, { 0, 0 }, { m_width, m_height });
glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz) + pad * 2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(m_width, m_height) - tex_pos));
if (!ShaderManager::ext_framebuffer_fetch)
{
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, tex_pos.x, tex_pos.y,
tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
}
m_dirty_box[i] = glm::vec4(glm::min(xy(m_dirty_box[i]), (glm::vec2)tex_pos),
glm::max(zw(m_dirty_box[i]), (glm::vec2)(tex_pos + tex_sz)));
if (P.size() == 4)
{
static vertex_t rect[6];
rect[0] = P[0];
rect[1] = P[1];
rect[2] = P[2];
rect[3] = P[0];
rect[4] = P[2];
rect[5] = P[3];
m_brush_shape.update_vertices(rect, 6);
}
else if (P.size() == 3)
{
m_brush_shape.update_vertices(P.data(), P.size());
}
else
{
P = triangulate_simple(P);
m_brush_shape.update_vertices(P.data(), P.size());
}
m_brush_shape.draw_fill();
if (!ShaderManager::ext_framebuffer_fetch)
{
glActiveTexture(GL_TEXTURE1);
m_tex[i].unbind();
}
}
std::vector<Canvas::StrokeFrame> Canvas::stroke_draw_compute()
{
std::vector<StrokeFrame> ret;
const auto& m_brush = m_current_stroke->m_brush;
auto samples = m_current_stroke->compute_samples();
auto& tex = *m_brush->m_tip_texture;
auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f);
std::vector<vertex_t> B{
std::array<vertex_t, 4> B = {
vertex_t{ {0, 0, 1, 1}, {0, 0}, {0, 0} },
vertex_t{ {0, 0, 1, 1}, {0, 1}, {0, 1} },
vertex_t{ {0, 0, 1, 1}, {1, 1}, {1, 1} },
vertex_t{ {0, 0, 1, 1}, {1, 0}, {1, 0} },
};
glViewport(0, 0, m_width, m_height);
glActiveTexture(GL_TEXTURE0);
tex.bind();
m_sampler_brush.bind(0);
m_sampler_bg.bind(1);
m_sampler_stencil.bind(2);
m_sampler.bind(3);
//m_sampler_linear.bind(5);
glActiveTexture(GL_TEXTURE2);
if (m_brush->m_stencil_texture)
m_brush->m_stencil_texture->bind();
else
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE3);
m_mixer.bindTexture();
glDisable(GL_BLEND);
ShaderManager::use(kShader::Stroke);
ShaderManager::u_int(kShaderUniform::Tex, 0); // brush
if (!ShaderManager::ext_framebuffer_fetch)
ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg
ShaderManager::u_int(kShaderUniform::TexStencil, 2); // stencil
ShaderManager::u_int(kShaderUniform::TexMix, 3); // mixer
//ShaderManager::u_int(kShaderUniform::TexMixA, 4); // mixer
ShaderManager::u_vec2(kShaderUniform::Resolution, { m_width, m_height });
ShaderManager::u_vec2(kShaderUniform::StencilOffset, stencil_offset);
ShaderManager::u_float(kShaderUniform::StencilAlpha, m_brush->m_tip_stencil);
ShaderManager::u_float(kShaderUniform::MixAlpha, m_brush->m_tip_mix);
ShaderManager::u_float(kShaderUniform::Wet, m_brush->m_tip_wet);
ShaderManager::u_float(kShaderUniform::Noise, m_brush->m_tip_noise);
auto unp_vp = zw(m_box);
auto unp_inv = glm::inverse(m_proj * m_mv);
for (const auto& s : samples)
{
if (!s.valid())
continue;
ret.emplace_back();
auto& f = ret.back();
if (m_mixer_idle)
{
m_mixer_sample = s;
@@ -377,154 +449,93 @@ void Canvas::stroke_draw()
B[j].pos = glm::vec4(xy(s.pos) + off[j] * glm::orientate2(-s.angle), 1, 1);
B[j].uvs2 = p / mixer_sz;
}
if (m_brush->m_tip_mix > 0.f)
{
stroke_draw_mix(mixer_bb_min, mixer_bb_max - mixer_bb_min);
}
for (int i = 0; i < 6; i++)
{
/*
// check if plane is even visible
glm::vec4 forward = m_mv * glm::vec4(0, 0, 1, 1);
float dot = glm::dot(xyz(forward), m_plane_normal[i]);
// TODO: use better threshold than 0.3
// some trigonometric shit, tangent and stuff
if (dot < -0.3f)
continue;
*/
int intersected = 0;
// intersect P with the current face to clip diverging points from the plane
auto P = poly_intersect(B, m_plane_shape[i]);
glm::mat4 plane_camera = glm::lookAt(m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i]);
for (int j = 0; j < P.size(); j++)
{
glm::vec3 ray_origin, ray_dir;
if (s.pos.z == 0)
{
//point_unproject(P[j].pos, { 0, 0, zw(m_box) }, m_mv, m_proj, ray_origin, ray_dir);
auto clip_space = glm::vec2(P[j].pos.x, unp_vp.y - P[j].pos.y - 1.f) / unp_vp * 2.f - 1.f;
auto wp0 = unp_inv * glm::vec4(clip_space, 0, 1);
auto wp1 = unp_inv * glm::vec4(clip_space, .5, 1);
ray_origin = xyz(wp0 / wp0.w);
ray_dir = glm::normalize(xyz(wp1 / wp1.w) - ray_origin);
}
else
{
auto m = glm::inverse(glm::lookAt({ 0, 0, 0 }, s.pos, { 0, 1, 0 }));
glm::vec3 off_3d = m * glm::vec4(off[j], 0, 1);
ray_origin = glm::vec3(0);
ray_dir = s.pos + off_3d;
}
glm::vec3 hit;
float hit_t;
if (ray_intersect(ray_origin, ray_dir, m_plane_origin[i], m_plane_normal[i], m_plane_tangent[i], hit, hit_t))
{
glm::vec4 plane_local = plane_camera * glm::vec4(hit, 1);
//P[j].uvs2 = xy(P[j].pos) / glm::vec2(App::I.width, App::I.height);
P[j].pos.x = -(plane_local.x * 0.5f - 0.5f) * m_width;
P[j].pos.y = (plane_local.y * 0.5f + 0.5f) * m_height;
// Black magic - BEWARE!
// interpolation perspective correction, use the current camera projection to correct the interpolation
// because the new shape will have z fixed with an ortho projection when drawn to the face
// we need to imitate the same perspective as the once in the camera
// see: https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/perspective-correct-interpolation-vertex-attributes
auto hit_cam = m_mv * glm::vec4(hit, 1);
P[j].pos.z = 0;
P[j].pos.w = hit_cam.z;
P[j].uvs *= hit_cam.z;
P[j].uvs2 *= hit_cam.z;
intersected++;
}
else
{
break;
}
}
if (intersected < 3)
continue;
m_dirty_face[i] = true;
m_tmp[i].bindFramebuffer();
if (!ShaderManager::ext_framebuffer_fetch)
{
glActiveTexture(GL_TEXTURE1);
m_tex[i].bind(); // bg, copy of framebuffer (copied before drawing)
}
glm::vec2 bb_min(m_width, m_height);
glm::vec2 bb_max(0, 0);
for (int j = 0; j < P.size(); j++)
{
bb_min = glm::max({ 0, 0 }, glm::min(bb_min, xy(P[j].pos)));
bb_max = glm::min({ m_width, m_height }, glm::max(bb_max, xy(P[j].pos)));
}
auto bb_sz = bb_max - bb_min;
glm::vec2 pad(1);
glm::ivec2 tex_pos = glm::clamp(glm::floor(bb_min) - pad , { 0, 0 }, { m_width, m_height });
glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz ) + pad*2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(m_width, m_height) - tex_pos));
if (!ShaderManager::ext_framebuffer_fetch)
{
glCopyTexSubImage2D(GL_TEXTURE_2D, 0, tex_pos.x, tex_pos.y,
tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
}
m_dirty_box[i] = glm::vec4(glm::min(xy(m_dirty_box[i]), (glm::vec2)tex_pos),
glm::max(zw(m_dirty_box[i]), (glm::vec2)(tex_pos + tex_sz)));
ShaderManager::use(kShader::Stroke);
ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj);
ShaderManager::u_vec4(kShaderUniform::Col, glm::vec4(s.col, m_brush->m_tip_color.a));
ShaderManager::u_float(kShaderUniform::Alpha, s.flow);
if (P.size() == 4)
{
static vertex_t rect[6];
rect[0] = P[0];
rect[1] = P[1];
rect[2] = P[2];
rect[3] = P[0];
rect[4] = P[2];
rect[5] = P[3];
m_brush_shape.update_vertices(rect, 6);
}
else if (P.size() == 3)
{
m_brush_shape.update_vertices(P.data(), P.size());
}
else
{
P = triangulate_simple(P);
m_brush_shape.update_vertices(P.data(), P.size());
}
m_brush_shape.draw_fill();
if (!ShaderManager::ext_framebuffer_fetch)
{
glActiveTexture(GL_TEXTURE1);
m_tex[i].unbind();
}
m_tmp[i].unbindFramebuffer();
}
f.m_mixer_rect = { mixer_bb_min, mixer_bb_max - mixer_bb_min };
f.col = glm::vec4(s.col, m_brush->m_tip_color.a);
f.pressure = s.flow;
f.shapes = stroke_draw_project(B);
m_mixer_sample = s;
}
return ret;
}
void Canvas::stroke_draw()
{
if (!(m_current_stroke && m_current_stroke->has_sample()))
{
//stroke_draw_mix({ 0,0 }, { m_mixer.getWidth(), m_mixer.getHeight() });
return;
}
m_dirty = true;
GLint vp[4];
GLfloat cc[4];
glGetIntegerv(GL_VIEWPORT, vp);
glGetFloatv(GL_COLOR_CLEAR_VALUE, cc);
const auto& m_brush = m_current_stroke->m_brush;
auto& tex = *m_brush->m_tip_texture;
auto ortho_proj = glm::ortho(0.f, (float)m_width, 0.f, (float)m_height, -1.f, 1.f);
glViewport(0, 0, m_width, m_height);
glActiveTexture(GL_TEXTURE0);
tex.bind();
m_sampler_brush.bind(0);
m_sampler_bg.bind(1);
m_sampler_stencil.bind(2);
m_sampler.bind(3);
//m_sampler_linear.bind(5);
glActiveTexture(GL_TEXTURE2);
if (m_brush->m_stencil_texture)
m_brush->m_stencil_texture->bind();
else
glBindTexture(GL_TEXTURE_2D, 0);
glActiveTexture(GL_TEXTURE3);
m_mixer.bindTexture();
glDisable(GL_BLEND);
ShaderManager::use(kShader::Stroke);
ShaderManager::u_int(kShaderUniform::Tex, 0); // brush
if (!ShaderManager::ext_framebuffer_fetch)
ShaderManager::u_int(kShaderUniform::TexBG, 1); // bg
ShaderManager::u_int(kShaderUniform::TexStencil, 2); // stencil
ShaderManager::u_int(kShaderUniform::TexMix, 3); // mixer
//ShaderManager::u_int(kShaderUniform::TexMixA, 4); // mixer
ShaderManager::u_vec2(kShaderUniform::Resolution, { m_width, m_height });
ShaderManager::u_vec2(kShaderUniform::StencilOffset, stencil_offset);
ShaderManager::u_float(kShaderUniform::StencilAlpha, m_brush->m_tip_stencil);
ShaderManager::u_float(kShaderUniform::MixAlpha, m_brush->m_tip_mix);
ShaderManager::u_float(kShaderUniform::Wet, m_brush->m_tip_wet);
ShaderManager::u_float(kShaderUniform::Noise, m_brush->m_tip_noise);
ShaderManager::u_mat4(kShaderUniform::MVP, ortho_proj);
auto frames = stroke_draw_compute();
for (auto& f : frames)
{
if (m_brush->m_tip_mix > 0.f)
{
stroke_draw_mix(xy(f.m_mixer_rect), zw(f.m_mixer_rect));
}
//ShaderManager::use(kShader::Stroke);
ShaderManager::u_vec4(kShaderUniform::Col, f.col);
ShaderManager::u_float(kShaderUniform::Alpha, f.pressure);
for (int i = 0; i < 6; i++)
{
auto& P = f.shapes[i];
if (P.size() < 3)
continue;
m_dirty_face[i] = true;
m_tmp[i].bindFramebuffer();
stroke_draw_samples(i, P);
m_tmp[i].unbindFramebuffer();
}
}
glActiveTexture(GL_TEXTURE2);
if (m_brush->m_stencil_texture)

View File

@@ -126,6 +126,14 @@ struct PPIHeader
class Canvas
{
struct StrokeFrame
{
glm::vec4 col;
float pressure;
std::array<std::vector<vertex_t>, 6> shapes;
glm::vec4 m_mixer_rect;
};
public:
Plane m_plane;
Plane m_plane_brush;
@@ -224,6 +232,9 @@ public:
void stroke_start(glm::vec3 point, float pressure, const std::shared_ptr<Brush>& brush);
void stroke_update(glm::vec3 point, float pressure);
void stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz);
std::array<std::vector<vertex_t>, 6> stroke_draw_project(std::array<vertex_t, 4>& B);
void stroke_draw_samples(int i, std::vector<vertex_t>& P);
std::vector<StrokeFrame> stroke_draw_compute();
void stroke_draw();
void stroke_end();
void stroke_cancel();

View File

@@ -99,11 +99,11 @@ bool point_side(glm::vec2 a, glm::vec2 b, glm::vec2 p)
// a is a convex polygon
// a and b are a list of non repeating points
// returns the resulting intersection polygon points
std::vector<vertex_t> poly_intersect(const std::vector<vertex_t>& poly, const std::vector<glm::vec2>& clip)
std::vector<vertex_t> poly_intersect(const vertex_t* poly_begin, const vertex_t* poly_end, const std::vector<glm::vec2>& clip)
{
// implementing the Sutherland-Hodgman algorithm
// see https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
std::vector<vertex_t> ret = poly;
std::vector<vertex_t> ret(poly_begin, poly_end);
for (int i = 0; i < clip.size(); i++)
{
std::vector<vertex_t> tmp;

View File

@@ -53,7 +53,7 @@ bool ray_intersect(glm::vec3 ray_origin, glm::vec3 ray_dir, glm::vec3 plane_orig
bool segments_intersect(const glm::vec2& p0a, const glm::vec2& p0b,
const glm::vec2& p1a, const glm::vec2& p1b, glm::vec2& out_pt, glm::vec2& out_hit_uv);
bool point_side(glm::vec2 a, glm::vec2 b, glm::vec2 p);
std::vector<vertex_t> poly_intersect(const std::vector<vertex_t>& poly, const std::vector<glm::vec2>& clip);
std::vector<vertex_t> poly_intersect(const vertex_t* poly_begin, const vertex_t* poly_end, const std::vector<glm::vec2>& clip);
std::vector<glm::vec2> poly_intersect(const std::vector<glm::vec2>& poly, const std::vector<glm::vec2>& clip);
std::vector<glm::vec3> poly_clip_near(const std::vector<glm::vec3>& poly, float near_plane_distance);
glm::vec4 rand_color();