基于 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 坐标来指定 borderThickness 和 radius,因此我没有得到一致的边界。如果您在图像中看到,水平边框比垂直边框稍宽。我更愿意使用 borderThickness 和 radius 像素大小。想法是在矩形周围获得一致的边框,而不管屏幕尺寸如何。
问题 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()
该应用程序基于 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 坐标来指定 borderThickness 和 radius,因此我没有得到一致的边界。如果您在图像中看到,水平边框比垂直边框稍宽。我更愿意使用 borderThickness 和 radius 像素大小。想法是在矩形周围获得一致的边框,而不管屏幕尺寸如何。
问题 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()