correct glsl affine texture mapping
Asked Answered
F

7

7

i'm trying to code correct 2D affine texture mapping in GLSL.

Explanation:

...NONE of this images is correct for my purposes. Right (labeled Correct) has perspective correction which i do not want. So this: Getting to know the Q texture coordinate solution (without further improvements) is not what I'm looking for.

I'd like to simply "stretch" texture inside quadrilateral, something like this:

enter image description here

but composed from two triangles. Any advice (GLSL) please?

Fajardo answered 13/9, 2012 at 20:59 Comment(2)
Are the upper vertices behind the lower ones, or are you just scaling the quad's vertex positions?Unbeatable
scaling, all coordinates are in 2D (i've just amend my post)Fajardo
F
-2

thanks for answers, but after experimenting i found a solution.

two triangles on the left has uv (strq) according this and two triangles on the right are modifed version of this perspective correction.

Numbers and shader:

tri1 = [Vec2(-0.5, -1), Vec2(0.5, -1), Vec2(1, 1)]
tri2 = [Vec2(-0.5, -1), Vec2(1, 1), Vec2(-1, 1)]

d1 = length of top edge = 2
d2 = length of bottom edge = 1

tri1_uv = [Vec4(0, 0, 0, d2 / d1), Vec4(d2 / d1, 0, 0, d2 / d1), Vec4(1, 1, 0, 1)]
tri2_uv = [Vec4(0, 0, 0, d2 / d1), Vec4(1, 1, 0, 1), Vec4(0, 1, 0, 1)]

only right triangles are rendered using this glsl shader (on left is fixed pipeline):

void main()
{
    gl_FragColor = texture2D(colormap, vec2(gl_TexCoord[0].x / glTexCoord[0].w, gl_TexCoord[0].y);
}

so.. only U is perspective and V is linear.

Fajardo answered 17/9, 2012 at 16:37 Comment(1)
That's exactly what I showed you, except it doesn't account for as many cases. There's no use in having six vertices.Mcphail
M
7

This works well as long as you have a trapezoid, and its parallel edges are aligned with one of the local axes. I recommend playing around with my Unity package.

GLSL:

varying vec2 shiftedPosition, width_height;

#ifdef VERTEX
void main() {
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
    shiftedPosition = gl_MultiTexCoord0.xy; // left and bottom edges zeroed.
    width_height = gl_MultiTexCoord1.xy;
}
#endif

#ifdef FRAGMENT
uniform sampler2D _MainTex;
void main() {
    gl_FragColor = texture2D(_MainTex, shiftedPosition / width_height);
}
#endif

C#:

// Zero out the left and bottom edges, 
// leaving a right trapezoid with two sides on the axes and a vertex at the origin.
var shiftedPositions = new Vector2[] {
    Vector2.zero,
    new Vector2(0, vertices[1].y - vertices[0].y),
    new Vector2(vertices[2].x - vertices[1].x, vertices[2].y - vertices[3].y),
    new Vector2(vertices[3].x - vertices[0].x, 0)
};
mesh.uv = shiftedPositions;

var widths_heights = new Vector2[4];
widths_heights[0].x = widths_heights[3].x = shiftedPositions[3].x;
widths_heights[1].x = widths_heights[2].x = shiftedPositions[2].x;
widths_heights[0].y = widths_heights[1].y = shiftedPositions[1].y;
widths_heights[2].y = widths_heights[3].y = shiftedPositions[2].y;
mesh.uv2 = widths_heights;
Mcphail answered 15/9, 2012 at 17:4 Comment(4)
can you write out what vertices contains? I tried vertices = [Vec2(-1, -1), Vec2(-0.5, 1), Vec2(0.5, 1), Vec2(1, -1)] shiftedPositions = [Vec2(0, 0), Vec2(0, 2), Vec2(1, 2), Vec2(2, 0)] and width_heights = [Vec2(2, 2), Vec2(1, 2), Vec2(1, 2), Vec2(2, 2)]. And my fragment looks like: gl_FragColor = texture2D(colormap, gl_TexCoord[0].xy / width_heights.xy); (I have to use gl_TexCoord). And it's simply doesn't work.Fajardo
Your vec2s are all fine. I don't think I can provide you with anything more helpful than a Unity project; that's all I really know right now. Let me know if you need help using it, or if you can think of some other way for me to help you. I definitely get the results you're after. The key is to move the width/height calculations from per-vertex to per-"scan line". The best we can do is per-pixel, which is overkill, but that's what a GPU offers.Mcphail
Trying to get this to work with processing 2.0, not much luck yet. is there anything special about these shaders?Lotte
I don't know anything about processing 2.0. I recommend translating from the Unity package.Mcphail
W
2

I recently managed to come up with a generic solution to this problem for any type of quadrilateral. The calculations and GLSL maybe of help. There's a working demo in java (that runs on Android), but is compact and readable and should be easily portable to unity or iOS: http://www.bitlush.com/posts/arbitrary-quadrilaterals-in-opengl-es-2-0

Wayne answered 8/8, 2014 at 8:41 Comment(2)
good, but it's not what i was looking for. Please read carefully first question. 3rd quad mapping is NOT what i wanted.Fajardo
yes, my bad, didn't cotton on to you needing it without perspective correctionWayne
I
2

In case anyone's still interested, here's a C# implementation that takes a quad defined by the clockwise screen verts (x0,y0) (x1,y1) ... (x3,y3), an arbitrary pixel at (x,y) and calculates the u and v of that pixel. It was originally written to CPU-render an arbitrary quad to a texture, but it's easy enough to split the algorithm across CPU, Vertex and Pixel shaders; I've commented accordingly in the code.

            float Ax, Bx, Cx, Dx, Ay, By, Cy, Dy, A, B, C;

            //These are all uniforms for a given quad. Calculate on CPU.
            Ax = (x3 - x0) - (x2 - x1);
            Bx = (x0 - x1);
            Cx = (x2 - x1);
            Dx = x1;

            Ay = (y3 - y0) - (y2 - y1);
            By = (y0 - y1);
            Cy = (y2 - y1);
            Dy = y1;

            float ByCx_plus_AyDx_minus_BxCy_minus_AxDy = (By * Cx) + (Ay * Dx) - (Bx * Cy) - (Ax * Dy);
            float ByDx_minus_BxDy = (By * Dx) - (Bx * Dy);

            A = (Ay*Cx)-(Ax*Cy);

            //These must be calculated per-vertex, and passed through as interpolated values to the pixel-shader 
            B = (Ax * y) + ByCx_plus_AyDx_minus_BxCy_minus_AxDy - (Ay * x);
            C = (Bx * y) + ByDx_minus_BxDy - (By * x);

            //These must be calculated per-pixel using the interpolated B, C and x from the vertex shader along with some of the other uniforms.
            u = ((-B) - Mathf.Sqrt((B*B-(4.0f*A*C))))/(A*2.0f);
            v = (x - (u * Cx) - Dx)/((u*Ax)+Bx);

Sample output of the algorithm CPU-rendered to a texture

Illuminati answered 19/9, 2018 at 16:0 Comment(1)
Yes. This gives you true affine mapping onto a quad, without the 'fold' you get from affine-mapping two triangles.Illuminati
H
0

Tessellation solves this problem. Subdividing quad vertex adds hints to interpolate pixels.

Check out this link. https://www.youtube.com/watch?v=8TleepxIORU&feature=youtu.be

Hypochondrium answered 19/3, 2016 at 13:38 Comment(0)
A
0

I had similar question ( https://gamedev.stackexchange.com/questions/174857/mapping-a-texture-to-a-2d-quadrilateral/174871 ) , and at gamedev they suggested using imaginary Z coord, which I calculate using the following C code, which appears to be working in general case (not just trapezoids):

//usual euclidean distance
float distance(int ax, int ay, int bx, int by) {
  int x = ax-bx;
  int y = ay-by;
  return sqrtf((float)(x*x + y*y));
}

void gfx_quad(gfx_t *dst //destination texture, we are rendering into
             ,gfx_t *src //source texture
             ,int *quad  // quadrilateral vertices
             )
{
  int *v = quad; //quad vertices
  float z = 20.0;
  float top = distance(v[0],v[1],v[2],v[3]); //top
  float bot = distance(v[4],v[5],v[6],v[7]); //bottom
  float lft = distance(v[0],v[1],v[4],v[5]); //left
  float rgt = distance(v[2],v[3],v[6],v[7]); //right

  // By default all vertices lie on the screen plane
  float az = 1.0;
  float bz = 1.0;
  float cz = 1.0;
  float dz = 1.0;

  // Move Z from screen, if based on distance ratios.
  if (top<bot) {
    az *= top/bot;
    bz *= top/bot;
  } else {
    cz *= bot/top;
    dz *= bot/top;
  }

  if (lft<rgt) {
    az *= lft/rgt;
    cz *= lft/rgt;
  } else {
    bz *= rgt/lft;
    dz *= rgt/lft;
  }

  // draw our quad as two textured triangles
  gfx_textured(dst, src
              , v[0],v[1],az, v[2],v[3],bz, v[4],v[5],cz
              , 0.0,0.0,      1.0,0.0,      0.0,1.0);
  gfx_textured(dst, src
              , v[2],v[3],bz, v[4],v[5],cz, v[6],v[7],dz
              , 1.0,0.0,      0.0,1.0,      1.0,1.0);
}

I'm doing it in software to scale and rotate 2d sprites, and for OpenGL 3d app you will need to do it in pixel/fragment shader, unless you will be able to map these imaginary az,bz,cz,dz into your actual 3d space and use the usual pipeline. DMGregory gave exact code for OpenGL shaders: https://gamedev.stackexchange.com/questions/148082/how-can-i-fix-zig-zagging-uv-mapping-artifacts-on-a-generated-mesh-that-tapers

Aurita answered 23/8, 2019 at 10:29 Comment(0)
T
0

I came up with this issue as I was trying to implement a homography warping in OpenGL. Some of the solutions that I found relied on a notion of depth, but this was not feasible in my case since I am working on 2D coordinates. I based my solution on this article, and it seems to work for all cases that I could try. I am leaving it here in case it is useful for someone else as I could not find something similar. The solution makes the following assumptions:

  • The vertex coordinates are the 4 points of a quad in Lower Right, Upper Right, Upper Left, Lower Left order.
  • The coordinates are given in OpenGL's reference system (range [-1, 1], with origin at bottom left corner).
std::vector<cv::Point2f> points;
// Convert points to homogeneous coordinates to simplify the problem.
Eigen::Vector3f p0(points[0].x, points[0].y, 1);
Eigen::Vector3f p1(points[1].x, points[1].y, 1);
Eigen::Vector3f p2(points[2].x, points[2].y, 1);
Eigen::Vector3f p3(points[3].x, points[3].y, 1);

// Compute the intersection point between the lines described by opposite vertices using cross products. Normalization is only required at the end.
// See https://leimao.github.io/blog/2D-Line-Mathematics-Homogeneous-Coordinates/ for a quick summary of this approach.
auto line1 = p2.cross(p0);
auto line2 = p3.cross(p1);
auto intersection = line1.cross(line2);
intersection = intersection / intersection(2);

// Compute distance to each point.
for (const auto &pt : points) {
  auto distance = std::sqrt(std::pow(pt.x - intersection(0), 2) +
                            std::pow(pt.y - intersection(1), 2));
  distances.push_back(distance);
}

// Assumes same order as above.
std::vector<cv::Point2f> texture_coords_unnormalized = {
  {1.0f, 1.0f},
  {1.0f, 0.0f},
  {0.0f, 0.0f},
  {0.0f, 1.0f}
};

std::vector<float> texture_coords;
for (int i = 0; i < texture_coords_unnormalized.size(); ++i) {
  float u_i = texture_coords_unnormalized[i].x;
  float v_i = texture_coords_unnormalized[i].y;
  float d_i = distances.at(i);
  float d_i_2 = distances.at((i + 2) % 4);
  float scale = (d_i + d_i_2) / d_i_2;

  texture_coords.push_back(u_i*scale);
  texture_coords.push_back(v_i*scale);
  texture_coords.push_back(scale);
}

Pass the texture coordinates to your shader (use vec3). Then:

gl_FragColor = vec4(texture2D(textureSampler, textureCoords.xy/textureCoords.z).rgb, 1.0);
Transude answered 3/12, 2022 at 10:6 Comment(0)
F
-2

thanks for answers, but after experimenting i found a solution.

two triangles on the left has uv (strq) according this and two triangles on the right are modifed version of this perspective correction.

Numbers and shader:

tri1 = [Vec2(-0.5, -1), Vec2(0.5, -1), Vec2(1, 1)]
tri2 = [Vec2(-0.5, -1), Vec2(1, 1), Vec2(-1, 1)]

d1 = length of top edge = 2
d2 = length of bottom edge = 1

tri1_uv = [Vec4(0, 0, 0, d2 / d1), Vec4(d2 / d1, 0, 0, d2 / d1), Vec4(1, 1, 0, 1)]
tri2_uv = [Vec4(0, 0, 0, d2 / d1), Vec4(1, 1, 0, 1), Vec4(0, 1, 0, 1)]

only right triangles are rendered using this glsl shader (on left is fixed pipeline):

void main()
{
    gl_FragColor = texture2D(colormap, vec2(gl_TexCoord[0].x / glTexCoord[0].w, gl_TexCoord[0].y);
}

so.. only U is perspective and V is linear.

Fajardo answered 17/9, 2012 at 16:37 Comment(1)
That's exactly what I showed you, except it doesn't account for as many cases. There's no use in having six vertices.Mcphail

© 2022 - 2025 — McMap. All rights reserved.