基于 GLSL、SDF 的圆角矩形

GLSL, SDF based Rounding Rectangle

该应用程序基于 PyOpenGL(核心配置文件)并使用正交投影。我必须在四边形(2 个三角形)上绘制几个不同的二维形状。

我找到了一个非常棒的 article on rendering 2d/3d shapes using SDF. The first shape I'm trying is a rounded rectangle with border. This Shadertoy 示例,完全符合我的要求。这是我的两个着色器:

顶点着色器

#version 330 core
// VERTEX SHADER
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 tex_coord;
uniform mat4 mvp;

void main()
{
    gl_Position = mvp * vec4(aPos, 1.0);
    tex_coord = aTexCoord;
}

片段着色器

#version 330 core
// FRAGMENT SHADER 

uniform vec4 in_color;
in vec2 tex_coord;
vec2 resolution = vec2(800, 600);
float aspect = resolution.x / resolution.y;
const float borderThickness = 0.01;
const vec4 borderColor = vec4(1.0, 1.0, 0.0, 1.0);
const vec4 fillColor = vec4(1.0, 0.0, 0.0, 1.0);
const float radius = 0.05;

float RectSDF(vec2 p, vec2 b, float r)
{
    vec2 d = abs(p) - b + vec2(r);
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;   
}

void main() {
    
    // https://www.shadertoy.com/view/ltS3zW
    vec2 centerPos = tex_coord - vec2(0.5, 0.5); // <-0.5,0.5>
    //vec2 centerPos = (tex_coord/resolution - vec2(0.5)) * 2.0;
    //centerPos *= aspect; // fix aspect ratio
    //centerPos = (centerPos - resolution.xy) * 2.0;
    
    float fDist = RectSDF(centerPos, vec2(0.5, 0.5), radius);
    
    vec4 v4FromColor = borderColor; // Always the border color. If no border, this still should be set
    vec4 v4ToColor = vec4(0.0, 0.0, 1.0, 1.0); // Outside color
    
    if (borderThickness > 0.0)
    {
        if (fDist < 0.0)
        {
            v4ToColor = fillColor;   
        } 
        
        fDist = abs(fDist) - borderThickness;
    }
    
    float fBlendAmount = smoothstep(-0.01, 0.01, fDist);
    
    // final color
    gl_FragColor = mix(v4FromColor, v4ToColor, fBlendAmount);
}

以及两个输出的区别:

问题 1 在Shadertoy的例子中,边框很整齐,没有模糊,我的是模糊的。

问题2 我使用 ndc 坐标来指定 borderThicknessradius,因此我没有得到一致的边界。如果您在图像中看到,水平边框比垂直边框稍宽。我更愿意使用 borderThicknessradius 像素大小。想法是在矩形周围获得一致的边框,而不管屏幕尺寸如何。

问题 3 使外面的蓝色透明。

问题4 正如我提到的,我最近开始学习 GLSL,我在某些地方读到过多的“如果”条件会极大地影响着色器性能,并且您可能会不必要地使用它们。此代码中已经存在两个“if”条件,我不确定是否可以省略它们。

使用 Uniform (rectSize) 指定矩形的大小(以像素为单位)。纹理坐标 (tex_coord) 需要在 [0.0, 1.0] 范围内。计算矩形中的像素位置 (rectSize * tex_coord)。现在您可以以像素为单位指定半径和边缘厚度:

in vec2 tex_coord;
uniform vec2 rectSize;

const float borderThickness = 10.0;
const float radius = 30.0;

// [...]

void main() 
{
    vec2 pos = rectSize * tex_coord;
        
    float fDist = RectSDF(pos-rectSize/2.0, rectSize/2.0 - borderThickness/2.0-1.0, radius);
    float fBlendAmount = smoothstep(-1.0, 1.0, abs(fDist) - borderThickness / 2.0);

    vec4 v4FromColor = borderColor;
    vec4 v4ToColor = (fDist < 0.0) ? fillColor : vec4(0.0);
    gl_FragColor = mix(v4FromColor, v4ToColor, fBlendAmount);
}
rect_loc = glGetUniformLocation(program, "rectSize")
glUniform2f(rect_loc, width, height)

使用Blending使外部透明。为此,外部颜色的 alpha 通道必须为 0。(例如 vec4(0.0)

glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

最小示例:

from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
import OpenGL.GL.shaders
from ctypes import c_void_p
import glm

sh_vert = """
#version 330 core
// VERTEX SHADER
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 tex_coord;
uniform mat4 mvp;

void main()
{
    gl_Position = mvp * vec4(aPos, 1.0);
    tex_coord = aTexCoord;
}
"""

sh_frag = """
#version 330 core
// FRAGMENT SHADER 

in vec2 tex_coord;
uniform vec2 rectSize;

const vec4 borderColor = vec4(1.0, 1.0, 0.0, 1.0);
const vec4 fillColor = vec4(1.0, 0.0, 0.0, 1.0);
const float borderThickness = 10.0;
const float radius = 30.0;

float RectSDF(vec2 p, vec2 b, float r)
{
    vec2 d = abs(p) - b + vec2(r);
    return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - r;   
}

void main() 
{
    vec2 pos = rectSize * tex_coord;
        
    float fDist = RectSDF(pos-rectSize/2.0, rectSize/2.0 - borderThickness/2.0-1.0, radius);
    float fBlendAmount = smoothstep(-1.0, 1.0, abs(fDist) - borderThickness / 2.0);

    vec4 v4FromColor = borderColor;
    vec4 v4ToColor = (fDist < 0.0) ? fillColor : vec4(0.0);
    gl_FragColor = mix(v4FromColor, v4ToColor, fBlendAmount);
}
"""

def display():
    glClear(GL_COLOR_BUFFER_BIT)
    
    glEnable(GL_BLEND)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)

    glutSwapBuffers()
    glutPostRedisplay()

def reshape(width, height):
    glViewport(0, 0, width, height)
    

resolution = (640, 480)
rect = (50, 50, 350, 250)
attributes = (GLfloat * 20)(*[rect[0],rect[1],0,0,1, rect[2],rect[1],0,1,1, rect[2],rect[3],0,1,0, rect[0],rect[3],0,0,0])
indices = (GLuint * 6)(*[0,1,2, 0,2,3])

glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowSize(*resolution)
glutCreateWindow(b"OpenGL Window")
glutDisplayFunc(display)
glutReshapeFunc(reshape)

vao = glGenVertexArrays(1)
vbo = glGenBuffers(1)
ebo = glGenBuffers(1)
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, attributes, GL_STATIC_DRAW)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
glVertexAttribPointer(0, 3, GL_FLOAT, False, 5 * 4, None)
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 2, GL_FLOAT, False, 5 * 4, c_void_p(3 * 4))
glEnableVertexAttribArray(1)

program = OpenGL.GL.shaders.compileProgram(
    OpenGL.GL.shaders.compileShader(sh_vert, GL_VERTEX_SHADER),
    OpenGL.GL.shaders.compileShader(sh_frag, GL_FRAGMENT_SHADER)
)
glUseProgram(program)
mvp_loc = glGetUniformLocation(program, "mvp")
mvp = glm.ortho(0, *resolution, 0, -1, 1)
glUniformMatrix4fv(mvp_loc, 1, GL_FALSE, glm.value_ptr(mvp))
rect_loc = glGetUniformLocation(program, "rectSize")
glUniform2f(rect_loc, rect[2]-rect[0], rect[3]-rect[1])

glClearColor(0.5, 0.5, 0.5, 0.0)
glutMainLoop()