OpenGL简单的抗锯齿多边形网格着色器

问题描述

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

我记得我发现这很具有挑战性,所以我将在这里为我的未来自我以及希望获得相同效果的任何人提供答案。

该着色器旨在在单独的渲染调用中“渲染”在已纹理化的平面上方。我这样做的原因-是因为在我的程序中,我通过多个渲染调用生成了表面的纹理,并逐渐将其逐层构建。然后,我想在其上制作一个简单的黑色网格,因此我进行了最后一个渲染调用。

这就是为什么这里的基色是(0,0),基本上什么都没有。然后,我可以使用GL混合模式将此着色器的结果覆盖到我的纹理上。

请注意,您不必单独执行此操作。您可以轻松地修改此代码以显示某种颜色(如平滑的灰色),甚至显示您选择的纹理。只需将纹理传递到着色器并相应地修改最后一行。

还请注意,我使用在着色器编译期间设置的常量。基本上,我只是加载着色器字符串,但是在将其传递给着色器编译器之前,我搜索了__CONSTANT_SOMETHING并将其替换为所需的实际值。别忘了这就是全部文字,因此您需要将其替换为文字,例如:

//java code
shaderCode = shaderCode.replaceFirst("__CONSTANT_SQUARE_SIZE",String.valueOf(GlobalSettings.PLANE_SQUARE_SIZE));

解决方法

如果我可以与您分享我用于抗锯齿网格的代码,则可能有助于提高复杂性。我所要做的就是使用纹理坐标在平面上绘制网格。我使用GLSL的genType fract(genType x)来重复纹理空间。然后,我使用绝对值函数从本质上计算了每个像素到网格线的距离。其余操作将其解释为一种颜色。

您可以将其粘贴到新的着色器中,直接在Shadertoy.com上使用此代码。

如果要在代码中使用它,则仅需几行是从gridSize变量开始到以grid变量结尾的部分。

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

gridSizewidth应该提供一个统一变量。

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,1.0);
}

快乐的阴影!

,

这是我的着色器:

顶点:

#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并将其传递给片段着色器。这样,当对象移动时,我的网格便会“移动”。问题是,在我的应用程序中,我基本上已经扎根于主要实体。实体(称为角色或其他角色)不会在平面上移动或更改其相对于平面的位置。但是要创建运动的错觉-我计算坐标偏移(相对于正方形大小),然后使用它来计算顶点位置。

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

因此,基本上,网格曾经在一个正方形内“移动”,但由于它是均匀的-幻觉是,您正在无限网格表面上行走。

还请注意,您可以使多层网格具有相同的功能,例如,每第10行更粗。您真正需要做的就是确保您的coordShift代表网格图案移动的最大距离。

以防万一有人想知道为什么让它循环-这是出于精确的缘故。当然,您可以将原始角色的坐标传递给着色器,它可以在(0,0)周围正常工作,但是当您获得10000个单位时-您会注意到一些严重的精度毛刺,例如线条失真甚至是“模糊”,就像它们是用刷子制成的。

这是片段着色器:

#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;
    }

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

它当前仅用于1像素粗线,但是您可以通过控制“宽度”来控制厚度

这里,第一个重要部分是dfdx / dfdy函数。这些是GLSL函数,我只想简单地说一说,它们使您可以根据飞机上该点在Z轴上的距离确定碎片在屏幕上占据的WORLD坐标空间。 好吧,那是一口。我敢肯定,如果您为他们阅读了文档,便可以弄清楚。

然后我将这些输出的最大值作为宽度。基本上,根据相机的外观,您希望稍微“拉伸”线条的宽度。

remainder-基本上是该片段与我们要在世界坐标中绘制的线之间的距离。如果距离太远-我们不需要填充它。

如果仅在此处采用最大值,则将获得一条非抗锯齿的1像素宽的线。它基本上看起来像MS涂料的完美1像素线条形状。 但是,随着宽度的增加,您可以使这些直线段进一步延伸并重叠。

您可以看到我在这里将余数与线宽进行了比较。宽度越大,剩余的“击中”就越大。我必须从两侧进行比较,因为否则,您只会查看从负坐标一侧靠近线的像素,而对正像素进行折价,这可能仍然会达到目标。

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

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

最后,如果我们没有将任何线条打得足够近,我们将用透明的颜色填充片段。

我不确定这是否是完成任务的最佳代码-但它可以工作。如果您有建议,请告诉我!

p.s。着色器是为Opengl-ES编写的,但它们也应适用于OpenGL。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...