如何在不剪裁基础纹理的情况下将纹理叠加在另一个纹理上?
How to overlay texture over another, without clipping the base texture?
我编写了这个简单的着色器来将纹理覆盖在另一个(基础)纹理上 -
varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;
void main()
{
mediump vec4 base = texture2D(inputImageTexture, textureCoordinate);
mediump vec4 overlay = texture2D(inputImageTexture2, textureCoordinate2);
mediump float ra = (overlay.a) * overlay.r + (1.0 - overlay.a) * base.r;
mediump float ga = (overlay.a) * overlay.g + (1.0 - overlay.a) * base.g;
mediump float ba = (overlay.a) * overlay.b + (1.0 - overlay.a) * base.b;
gl_FragColor = vec4(ra, ga, ba, 1.0);
}
问题 - 除一个问题外,此方法有效。如果叠加图像小于基础图像,则叠加图像的外部区域的 alpha 值为 1.0,即 overlay.a == 1.0
。因此,基本图像被叠加图像裁剪。叠加层外的区域显示为黑色。
我是 opengl 的新手,我期待在它的边界之外,纹理的 alpha 应该显示为 0.0?如何修复我的着色器代码以实现所需的行为?或者我需要修改我的图形管道吗?
编辑 下面的顶点着色器-
attribute vec4 inputTextureCoordinate2;
varying vec2 textureCoordinate;
varying vec2 textureCoordinate2;
void main()
{
gl_Position = pos;
textureCoordinate = uv;
textureCoordinate2 = inputTextureCoordinate2.xy;
}
I was expecting that outside its bounds, the texture's alpha should appear as 0.0
您如何对边界外的纹理进行采样?对纹理进行采样时,uv 坐标的范围应为 0 到 1。如果坐标超出此范围,则会发生以下两种情况之一:
- 如果设置了
GL_CLAMP_TO_EDGE
,那么坐标将被限制在(0, 1)范围内,你将采样一个边缘像素
- 如果设置了
GL_REPEAT
,那么坐标的小数部分将被取走,你会在纹理中间的某个地方采样
有关 glTexParameter 的详细信息,请参阅 the docs。
如果您的用例只是简单地叠加图像,也许您应该尝试编写 pixel shader。
- 将视口设置为基本图像尺寸并从 (-1, 1) 绘制四边形。
- 您的片段着色器现在将对每个像素(称为纹素)进行操作。使用
gl_FragCoord
获取纹素
- 通过纹素采样基础和叠加层,例如使用
texelFetch
- 如果纹素在叠加层之外,将叠加层的 rgba 值设置为 0
例如
//fragment shader
uniform ivec2 overlayDim;
uniform sampler2D baseTexture;
uniform sampler2D overlayTexture;
void main() {
vec2 texelf = floor(gl_FragCoord.xy);
ivec2 texel = (int(texelf.x), int(texelf.y));
vec4 base = texelFetch(baseTexture, texel, 0);
vec4 overlay = texelFetch(overlayTexture, texel, 0);
float overlayIsValid = float(texel.x < overlayDim.x && texel.y < overlayDim.y);
overlay *= overlayIsValid;
//rest of code
}
如果您在纹理范围之外进行采样,会发生什么情况由您使用 glTexParameteri()
为 GL_TEXTURE_WRAP_S
和 GL_TEXTURE_WRAP_T
设置的值控制。
在完整的 OpenGL 中,您可以将值设置为 GL_CLAMP_TO_BORDER
,将边框颜色设置为 alpha 0.0 的值,然后完成。但是纹理边框在OpenGL ES 2.0中不可用(该选项在ES 3.2中引入,但在早期版本中没有)。
没有这个,我可以想到两个选择:
- 如果您可以控制纹理数据,则可以将一个像素的边框设置为透明值。
GL_CLAMP_TO_EDGE
然后在范围外采样时为您提供透明值。
- 检查片段着色器中的范围。
第二个选项的片段着色器代码可能如下所示(未经测试):
mediump vec3 col = texture2D(inputImageTexture, textureCoordinate).xyz;
if (all(greaterThan(textureCoordinate2, vec2(0.0))) &&
all(lessThan(textureCoordinate2, vec2(1.0))))
{
mediump vec3 overlay = texture2D(inputImageTexture2, textureCoordinate2).xyz;
col = mix(col, overlay, overlay.a);
}
gl_FragColor = vec4(col, 1.0);
与您的原始代码相比,还要注意矢量运算的使用。只要有对向量进行操作的好方法,它就会使代码更简单。它还将使具有向量运算的 GPU 的优化器工作更容易。
我在我的代码中发现了问题。我有
GLES20.glClearColor(0, 0, 0, 1);
在我的代码中。将其更改为 -
GLES20.glClearColor(0, 0, 0, 0);
已解决问题。
另外,正如@Reto 所提到的,我已将片段着色器更改为使用矢量操作进行优化。
void main()
{
mediump vec4 overlay = texture2D(inputImageTexture2, textureCoordinate2);
mediump vec3 col = texture2D(inputImageTexture, textureCoordinate).xyz;
col = mix(col, overlay.xyz, overlay.a);
gl_FragColor = vec4(col, 1.0);
}
我编写了这个简单的着色器来将纹理覆盖在另一个(基础)纹理上 -
varying highp vec2 textureCoordinate;
varying highp vec2 textureCoordinate2;
uniform sampler2D inputImageTexture;
uniform sampler2D inputImageTexture2;
void main()
{
mediump vec4 base = texture2D(inputImageTexture, textureCoordinate);
mediump vec4 overlay = texture2D(inputImageTexture2, textureCoordinate2);
mediump float ra = (overlay.a) * overlay.r + (1.0 - overlay.a) * base.r;
mediump float ga = (overlay.a) * overlay.g + (1.0 - overlay.a) * base.g;
mediump float ba = (overlay.a) * overlay.b + (1.0 - overlay.a) * base.b;
gl_FragColor = vec4(ra, ga, ba, 1.0);
}
问题 - 除一个问题外,此方法有效。如果叠加图像小于基础图像,则叠加图像的外部区域的 alpha 值为 1.0,即 overlay.a == 1.0
。因此,基本图像被叠加图像裁剪。叠加层外的区域显示为黑色。
我是 opengl 的新手,我期待在它的边界之外,纹理的 alpha 应该显示为 0.0?如何修复我的着色器代码以实现所需的行为?或者我需要修改我的图形管道吗?
编辑 下面的顶点着色器-
attribute vec4 inputTextureCoordinate2;
varying vec2 textureCoordinate;
varying vec2 textureCoordinate2;
void main()
{
gl_Position = pos;
textureCoordinate = uv;
textureCoordinate2 = inputTextureCoordinate2.xy;
}
I was expecting that outside its bounds, the texture's alpha should appear as 0.0
您如何对边界外的纹理进行采样?对纹理进行采样时,uv 坐标的范围应为 0 到 1。如果坐标超出此范围,则会发生以下两种情况之一:
- 如果设置了
GL_CLAMP_TO_EDGE
,那么坐标将被限制在(0, 1)范围内,你将采样一个边缘像素 - 如果设置了
GL_REPEAT
,那么坐标的小数部分将被取走,你会在纹理中间的某个地方采样
有关 glTexParameter 的详细信息,请参阅 the docs。
如果您的用例只是简单地叠加图像,也许您应该尝试编写 pixel shader。
- 将视口设置为基本图像尺寸并从 (-1, 1) 绘制四边形。
- 您的片段着色器现在将对每个像素(称为纹素)进行操作。使用
gl_FragCoord
获取纹素
- 通过纹素采样基础和叠加层,例如使用
texelFetch
- 如果纹素在叠加层之外,将叠加层的 rgba 值设置为 0
例如
//fragment shader
uniform ivec2 overlayDim;
uniform sampler2D baseTexture;
uniform sampler2D overlayTexture;
void main() {
vec2 texelf = floor(gl_FragCoord.xy);
ivec2 texel = (int(texelf.x), int(texelf.y));
vec4 base = texelFetch(baseTexture, texel, 0);
vec4 overlay = texelFetch(overlayTexture, texel, 0);
float overlayIsValid = float(texel.x < overlayDim.x && texel.y < overlayDim.y);
overlay *= overlayIsValid;
//rest of code
}
如果您在纹理范围之外进行采样,会发生什么情况由您使用 glTexParameteri()
为 GL_TEXTURE_WRAP_S
和 GL_TEXTURE_WRAP_T
设置的值控制。
在完整的 OpenGL 中,您可以将值设置为 GL_CLAMP_TO_BORDER
,将边框颜色设置为 alpha 0.0 的值,然后完成。但是纹理边框在OpenGL ES 2.0中不可用(该选项在ES 3.2中引入,但在早期版本中没有)。
没有这个,我可以想到两个选择:
- 如果您可以控制纹理数据,则可以将一个像素的边框设置为透明值。
GL_CLAMP_TO_EDGE
然后在范围外采样时为您提供透明值。 - 检查片段着色器中的范围。
第二个选项的片段着色器代码可能如下所示(未经测试):
mediump vec3 col = texture2D(inputImageTexture, textureCoordinate).xyz;
if (all(greaterThan(textureCoordinate2, vec2(0.0))) &&
all(lessThan(textureCoordinate2, vec2(1.0))))
{
mediump vec3 overlay = texture2D(inputImageTexture2, textureCoordinate2).xyz;
col = mix(col, overlay, overlay.a);
}
gl_FragColor = vec4(col, 1.0);
与您的原始代码相比,还要注意矢量运算的使用。只要有对向量进行操作的好方法,它就会使代码更简单。它还将使具有向量运算的 GPU 的优化器工作更容易。
我在我的代码中发现了问题。我有
GLES20.glClearColor(0, 0, 0, 1);
在我的代码中。将其更改为 -
GLES20.glClearColor(0, 0, 0, 0);
已解决问题。
另外,正如@Reto 所提到的,我已将片段着色器更改为使用矢量操作进行优化。
void main()
{
mediump vec4 overlay = texture2D(inputImageTexture2, textureCoordinate2);
mediump vec3 col = texture2D(inputImageTexture, textureCoordinate).xyz;
col = mix(col, overlay.xyz, overlay.a);
gl_FragColor = vec4(col, 1.0);
}