首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >OpenGL简单抗锯齿多边形栅格着色器

OpenGL简单抗锯齿多边形栅格着色器
EN

Stack Overflow用户
提问于 2020-08-14 15:09:01
回答 2查看 1.4K关注 0票数 0

如何在片段着色器中使用抗锯齿线制作测试网格图案?

我记得我发现这很有挑战性,所以我会在这里为我未来的自己和任何想要同样效果的人张贴答案。

此着色器旨在通过单独的渲染调用在已设置纹理的平面上渲染。我这样做的原因是-因为在我的程序中,我通过几次渲染调用来生成表面的纹理,慢慢地逐层构建它。然后我想在它上面创建一个简单的黑色网格,所以我进行了最后一次渲染调用来完成此操作。

这就是为什么这里的基色是(0,0,0,0),基本上是零。然后我可以使用GL混合模式来覆盖这个着色器的结果,无论我的纹理是什么。

请注意,您不需要单独执行这些操作。您可以很容易地修改此代码,以显示特定的颜色(如平滑的灰色),甚至是您选择的纹理。只需将纹理传递给着色器,并相应地修改最后一行。

另请注意,我使用的常量是我在着色器压缩过程中设置的。基本上,我只是加载着色器字符串,但在将其传递给着色器编译器之前-我搜索__CONSTANT_SOMETHING并将其替换为我想要的实际值。不要忘记,这些都是文本,所以您需要将其替换为文本,例如:

代码语言:javascript
复制
//java code
shaderCode = shaderCode.replaceFirst("__CONSTANT_SQUARE_SIZE", String.valueOf(GlobalSettings.PLANE_SQUARE_SIZE));
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2020-08-14 15:09:01

以下是我的着色器:

顶点:

代码语言:javascript
复制
#version 300 es

precision highp float;
precision highp int;

layout (location=0) in vec3 position;

uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
uniform vec2 coordShift;
uniform mat4 modelMatrix;

out highp vec3 vertexPosition;

const float PLANE_SCALE = __CONSTANT_PLANE_SCALE;   //assigned during shader compillation

void main()
{
    // generate position data for the fragment shader
    // does not take view matrix or projection matrix into account
    // TODO: +3.0 part is contingent on the actual mesh. It is supposed to be it's lowest possible coordinate.
    // TODO: the mesh here is 6x6 with -3..3 coords. I normalize it to 0..6 for correct fragment shader calculations
    vertexPosition = vec3((position.x+3.0)*PLANE_SCALE+coordShift.x, position.y, (position.z+3.0)*PLANE_SCALE+coordShift.y);

    // position data for the OpenGL vertex drawing
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

请注意,我在这里计算VertexPosition并将其传递给片段着色器。这就是说,当对象移动时,我的网格也会“移动”。问题是,在我的应用程序中,我基本上坚持主要实体。实体(称为character或其他什么)不会在平面上移动,也不会相对于平面改变其位置。但为了创造运动的错觉-我计算坐标移动(相对于正方形大小),并使用它来计算顶点位置。

这有点复杂,但我想我应该把它包括进来。基本上,如果正方形大小设置为5.0 (即,我们有一个5x5米的正方形网格),则(0,0)的coordShift将意味着字符位于正方形的左下角;(2.5,2.5)的coordShift将位于正方形的中间,而(5,5)将是右上角。在超过5之后,移位循环回到0。低于0-它循环到5。

因此,基本上网格总是在一个正方形内“移动”,但因为它是均匀的-错觉是你走在无限的网格表面上。

还要注意的是,你可以让同样的事情在多层网格上工作,例如,每10行都更粗。你真正需要做的就是确保你的coordShift代表你的网格模式移动的最大距离。

以防有人想知道我为什么要做循环--这是为了精确起见。当然,你可以将原始字符的坐标传递给着色器,它在(0,0)附近工作得很好,但是当你得到10000个单位的距离时-你会注意到一些严重的精度问题,比如你的线条被扭曲了,甚至像是用笔刷做的一样“模糊”。

这是片段着色器:

代码语言:javascript
复制
#version 300 es

precision highp float;

in highp vec3 vertexPosition;

out mediump vec4 fragColor;

const float squareSize = __CONSTANT_SQUARE_SIZE;
const vec3 color_l1 = __CONSTANT_COLOR_L1;

void main()
{
    // calculate deriviatives
    // (must be done at the start before conditionals)
    float dXy = abs(dFdx(vertexPosition.z)) / 2.0;
    float dYy = abs(dFdy(vertexPosition.z)) / 2.0;
    float dXx = abs(dFdx(vertexPosition.x)) / 2.0;
    float dYx = abs(dFdy(vertexPosition.x)) / 2.0;

    // find and fill horizontal lines
    int roundPos = int(vertexPosition.z / squareSize);
    float remainder = vertexPosition.z - float(roundPos)*squareSize;
    float width = max(dYy, dXy) * 2.0;

    if (remainder <= width)
    {
        float diff = (width - remainder) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    if (remainder >= (squareSize - width))
    {
        float diff = (remainder - squareSize + width) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    // find and fill vertical lines
    roundPos = int(vertexPosition.x / squareSize);
    remainder = vertexPosition.x - float(roundPos)*squareSize;
    width = max(dYx, dXx) * 2.0;

    if (remainder <= width)
    {
        float diff = (width - remainder) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    if (remainder >= (squareSize - width))
    {
        float diff = (remainder - squareSize + width) / width;
        fragColor = vec4(color_l1, diff);
        return;
    }

    // fill base color
    fragColor = vec4(0,0,0, 0);
    return;
}

它目前只为1像素粗线构建,但你可以通过控制“宽度”来控制粗细。

这里,第一个重要的部分是dfdx / dfdy函数。这些是GLSL函数,我简单地说,它们允许您根据平面上该点的Z距离来确定碎片在屏幕上占据的世界坐标中的多少空间。嗯,那是一张很长的嘴。我相信如果你读了他们的文档,你会明白的。

然后我取这些输出中的最大值作为宽度。基本上,根据你的相机的外观,你想要“拉伸”你的线条的宽度一点。

余数-基本上是这个片段离我们想要在世界坐标中绘制的线有多远。如果它太远-我们不需要填满它。

如果你简单地在这里取最大值,你将得到一个非抗锯齿的第1行宽度。它基本上看起来就像MS paint中完美的1像素线条形状。但随着宽度的增加,这些直线段会进一步伸展并重叠。

你可以看到我在这里把余数和线宽做了比较。宽度越大,剩下的部分就越能“击中”它。我必须从两边进行比较,因为否则你只会看到离负坐标线很近的像素,而不考虑正数,因为正数可能仍在命中它。

现在,对于简单的抗锯齿效果,我们需要让这些重叠的线段在接近其末端时“淡出”。为此,我计算了分数,以查看余数在线内的深度。当分数等于1时,这意味着我们想要画的线基本上直接穿过我们当前正在绘制的片段的中间。随着分数接近0,这意味着片段离直线越来越远,因此应该变得越来越透明。

最后,我们分别从水平线和垂直线的两侧执行此操作。我们必须将它们分开,因为垂直线和水平线的dFdX / dFdY需要不同,所以我们不能在一个公式中完成它们。

最后,如果我们没有命中任何一条足够近的线-我们用透明颜色填充片段。

我不确定这是不是最适合这项任务的代码--但它是有效的。如果你有什么建议,请让我知道!

附注:着色器是为Opengl-ES编写的,但它们也应该适用于OpenGL。

票数 0
EN

Stack Overflow用户

发布于 2020-08-15 05:17:36

如果我能与你分享我用在抗锯齿网格上的代码,它可能会增加复杂性。我所做的就是使用纹理坐标在平面上绘制网格。我使用GLSL的genType fract(genType x)来重复纹理空间。然后我使用绝对值函数来计算每个像素到网格线的距离。其余的操作是将其解释为颜色。

通过将代码粘贴到新着色器中,可以直接在Shadertoy.com上使用该代码。

如果想在代码中使用它,只需要从gridSize变量开始到grid变量结束的部分。

iResolution.y是屏幕高度,uv是平面的纹理坐标。

gridSizewidth可能应该提供一个统一的变量。

代码语言:javascript
复制
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
    // aspect correct pixel coordinates (for shadertoy only)
    vec2 uv = fragCoord / iResolution.xy * vec2(iResolution.x / iResolution.y, 1.0);
    // get some diagonal lines going (for shadertoy only)
    uv.yx += uv.xy * 0.1;

    // for every unit of texture space, I want 10 grid lines
    float gridSize = 10.0;
    // width of a line on the screen plus a little bit for AA
    float width = (gridSize * 1.2) / iResolution.y;

    // chop up into grid
    uv = fract(uv * gridSize);
    // abs version
    float grid = max(
        1.0 - abs((uv.y - 0.5) / width),
        1.0 - abs((uv.x - 0.5) / width)
    );

    // Output to screen (for shadertoy only)
    fragColor = vec4(grid, grid, grid, 1.0);
}

快乐着色!

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63408121

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档