GLSL ES - 将纹理从矩形坐标映射到极坐标,并进行重复。

7
我需要将一个矩形贴图变成极坐标格式的贴图。为了更清楚地说明,我将进行如下阐述:
我有这张图片: original image 然后我要使用着色器将其变形成如下所示的样子: result 然后我将把它映射到一个平面上。如何实现这个过程呢?非常感谢您提供的帮助!
2个回答

7
这并不特别难。您只需要将纹理坐标转换为极坐标,并将半径用于纹理的 s 方向,将方位角用于 t 方向。
假设您想以这种方式纹理一个四边形,并且还假设您使用标准的纹理坐标,因此左下顶点将具有(0,0),右上顶点将具有(1,1)作为纹理坐标。
因此,在片段着色器中,您只需将插值的纹理坐标(使用 tc 进行)转换为极坐标即可。由于中心位于(0.5,0.5),因此我们首先必须进行偏移。
 vec2 x=tc - vec2(0.5,0.5);
 float radius=length(x);
 float angle=atan(x.y, x.x);

现在您需要做的就是将范围映射回[0,1]纹理空间。这里的最大半径将为0.5,因此您只需使用2 * radius作为s坐标,而角度将在[-pi,pi]之间,因此您应该将其映射到t坐标的[0,1]区间。 更新1 到目前为止,我留下了一些细节。从您的图像中可以清楚地看出,您不希望将内圆映射到纹理上。但是这很容易被纳入考虑。我假设这里有两个半径:r_inner,它是内圆的半径,和r_outer,它是您想要将外部部分映射到的半径。让我简述一个简单的片段着色器:
#version ...
precision ...

varying vec2 tc; // texcoords from vertex shader
uniform sampler2D tex;

#define PI 3.14159265358979323844

void main ()
{
    const float r_inner=0.25; 
    const float t_outer=0.5; 

    vec2 x = v_tex - vec2(0.5);
    float radius = length(x);
    float angle = atan(x.y, x.x);

    vec2 tc_polar; // the new polar texcoords
    // map radius so that for r=r_inner -> 0 and r=r_outer -> 1
    tc_polar.s = ( radius - r_inner) / (r_outer - r_inner);

    // map angle from [-PI,PI] to [0,1]
    tc_polar.t = angle * 0.5 / PI + 0.5;

    // texture mapping
    gl_FragColor = texture2D(tex, tc_polar);
}

现在还有一个细节缺失。上面生成的映射会生成超出 [0,1] 范围的纹理坐标,对于任何具有黑色的位置,纹理采样都不会自动给出黑色。最简单的解决方案是仅使用 GL_CLAMP_TO_BORDER 模式来设置 GL_TEXTURE_WRAP_S(默认边框颜色为 (0,0,0,0),因此您可能不需要指定它或者您可以将 GL_TEXTURE_BORDER_COLOR 明确设置为 (0,0,0,1) 如果您使用 alpha 混合并且不希望通过这种方式获得任何透明度)。这样,你就可以免费得到黑色了。其他选项是使用 GL_CLAMP_TO_EDGE 并在纹理的左右两端添加一个黑色像素列。另一种方法是向着色器添加一个分支,并检查 tc_polar.s 是否低于 0 或高于 1,但我不建议在这种情况下这样做。

你知道如何更改这个着色器以使结果来自段吗?因此,我需要重复给定的纹理,而不是拉伸。 - Nolesh
@Nolesh:你能详细说明一下吗?你想要重复什么? - derhass
我需要添加由哪些线段组成圆的计数。例如,我将此计数分配为180。因此,整个纹理在每2度上延伸。这个过程重复180次,直到整个圆被填满。通过这个动作,我期望沿着t增加分辨率。 - Nolesh
1
@Nolesh:如果我理解正确的话,您只想在t维度上将纹理重复180次以形成完整的圆。所以您需要做的就是将GL_TEXTURE_WRAP_T模式设置为GL_REPEAT,并将t纹理坐标(现在在[0,1]范围内)乘以180。 - derhass
这正是我想要的!非常感谢你! - Nolesh

1
对于那些想要更灵活的着色器但实现相同效果的人:
uniform float Angle; // range 2pi / 100000.0 to 1.0 (rounded down), exponential
uniform float AngleMin; // range -3.2 to 3.2
uniform float AngleWidth; // range 0.0 to 6.4
uniform float Radius; // range -10000.0 to 1.0
uniform float RadiusMin; // range 0.0 to 2.0
uniform float RadiusWidth; // range 0.0 to 2.0
uniform vec2 Center; // range: -1.0 to 3.0

uniform sampler2D Texture;

void main()
{
    // Normalised texture coords
    vec2 texCoord = gl_TexCoord[0].xy;
    // Shift origin to texture centre (with offset)
    vec2 normCoord;
    normCoord.x = 2.0 * texCoord.x – Center.x;
    normCoord.y = 2.0 * texCoord.y – Center.y;
    // Convert Cartesian to Polar coords
    float r = length(normCoord);
    float theta = atan(normCoord.y, normCoord.x);

    // The actual effect
    r = (r < RadiusMin) ? r : (r > RadiusMin + RadiusWidth) ? r : ceil(r / Radius) * Radius;
    theta = (theta < AngleMin) ? theta : (theta > AngleMin + AngleWidth) ? theta : floor(theta / Angle) * Angle;

    // Convert Polar back to Cartesian coords
    normCoord.x = r * cos(theta);
    normCoord.y = r * sin(theta);
    // Shift origin back to bottom-left (taking offset into account)
    texCoord.x = normCoord.x / 2.0 + (Center.x / 2.0);
    texCoord.y = normCoord.y / 2.0 + (Center.y / 2.0);

    // Output
    gl_FragColor = texture2D(Texture, texCoord);
}

来源:polarpixellate glsl

Shadertoy示例


请问您能否在StackOverflow上使用代码片段或者在CodePen上展示完整的代码? - Zibri
@Zibri,您可以在“polarpixellate”网站上查看完整的代码。 - Nolesh
我已经尝试了很多方法,但都没有成功。你能否在stackoverflow上提供一个示例,以便我可以通过“运行脚本”进行检查?或者编辑这个codepen,因为我无法使它工作。https://codepen.io/zibri-the-sans/pen/wvxgpMX(将代码更改为您发布的代码) - Zibri
@Zibri,曾经我和这个着色器一起工作过,但现在已经忘得差不多了。我建议你先在shadertoy.com上玩弄这个着色器,而不是直接在WebGL中实现它。看一下https://www.shadertoy.com/view/Dt23DW - Nolesh
我知道Shadertoy,但我想知道如何在画布上使用该着色器而不使用three.js或任何其他库来处理图像。 - Zibri
@Zibri,我在LibGDX库中使用了这个着色器,但我相信你可以根据环境进行一些更改后在任何地方使用它。 - Nolesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接