面试官:纹理贴图必须要输入顶点坐标或纹理坐标吗
共 7061字,需浏览 15分钟
·
2024-06-12 08:25
最近知识星球的一位同学,面试时被问到:纹理贴图必须要输入顶点坐标或纹理坐标吗?
他一下子被这个问题问蒙了,虽然他知道正确答案是否定的,但是说不上来理由。
这个就引出了文本提到的全屏三角形,它不需要顶点缓冲区,而是利用顶点着色器直接生成所需的顶点坐标和纹理坐标。
这种方法通常用于后处理效果,例如屏幕空间效果(屏幕空间反射、屏幕空间环境光遮蔽等),其中整个屏幕的片段都需要处理。
通过生成全屏三角形,可以避免显式地传递顶点数据,从而简化管线配置。
全屏三角形
全屏三角形实际上是一种讨巧的优化方法,用于渲染全屏四边形或矩形,而不需要使用两个三角形和顶点缓冲区。
通过至少 3 个顶点的索引,在顶点着色器中计算一个覆盖整个屏幕的三角形顶点坐标,可以避免两个三角形之间的接缝问题,并减少顶点处理的开销。
顶点索引 gl_VertexID 是 OpenGL 的内建变量,它在顶点着色器中表示当前顶点的索引。
它不需要显式生成或传递,因为在调用绘制命令(如 glDrawArrays)时,OpenGL 会自动为每个顶点提供该索引。
当你使用 glDrawArrays(GL_TRIANGLES, 0, 3) 来绘制一个包含三个顶点的三角形时,gl_VertexID 会依次被设置为 0、1 和 2。
这个索引值可以用来计算每个顶点的位置和其他属性。
全屏三角形的实现细节
gl_VertexID 是 OpenGL ES 中用于标识顶点索引的内建变量,利用它可以在顶点着色器中生成覆盖整个屏幕的三角形。
以下是顶点着色器的详细说明,其中包括对 gl_VertexID 的使用:
#version 300 es
out vec2 v_texCoord;
void main()
{
// 根据顶点索引计算纹理坐标
// gl_VertexID 的值依次为 0, 1, 2
v_texCoord = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);
// 将纹理坐标转换为标准化设备坐标
// v_texCoord 的值依次为 (0, 0), (2, 0), (0, 2)
gl_Position = vec4(v_texCoord * 2.0 - 1.0, 0.0, 1.0);
}
在这个顶点着色器中,gl_VertexID 的值为 0、1、2,这三次调用对应于三角形的三个顶点。下面是每个顶点的计算细节:
顶点 0 (gl_VertexID = 0):
v_texCoord = vec2((0 << 1) & 2, 0 & 2) -> v_texCoord = vec2(0, 0)
gl_Position = vec4(v_texCoord * 2.0 - 1.0, 0.0, 1.0) -> gl_Position = vec4(-1.0, -1.0, 0.0, 1.0)
顶点 1 (gl_VertexID = 1):
v_texCoord = vec2((1 << 1) & 2, 1 & 2) -> v_texCoord = vec2(2, 0)
gl_Position = vec4(v_texCoord * 2.0 - 1.0, 0.0, 1.0) -> gl_Position = vec4(3.0, -1.0, 0.0, 1.0)
顶点 2 (gl_VertexID = 2):
v_texCoord = vec2((2 << 1) & 2, 2 & 2) -> v_texCoord = vec2(0, 2)
gl_Position = vec4(v_texCoord * 2.0 - 1.0, 0.0, 1.0) -> gl_Position = vec4(-1.0, 3.0, 0.0, 1.0)
通过这三个顶点,生成了一个覆盖整个屏幕的三角形。注意,这个三角形覆盖了标准化设备坐标 (NDC) 空间,从左下角 (-1, -1) 到右上角 (1, 1),因此它可以覆盖整个屏幕。
此时生成的顶点坐标:
此时生成的纹理坐标:
可以看到这个大的三角形超出了屏幕区域,这个没有问题,渲染的时候将会被裁剪,不会影响性能。
对应的顶点着色器:
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_Texture;
void main()
{
outColor = texture(s_Texture, v_texCoord);
}
渲染代码这个时候变得非常简单了:
//指定着色器程序
glUseProgram (m_ProgramObj);
//激活并绑定纹理 id
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_TextureId);
glUniform1i(m_SamplerLoc, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
由于 Android 设备坐标系原点在左上角,OpenGL 纹理坐标系原点在左下角,纹理坐标需要做一下上下镜像:
#version 300 es
layout(location = 0) in vec4 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main()
{
vec2 uv = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2);
v_texCoord = vec2(uv.x, 1.0 - uv.y);//纹理坐标做一下上下镜像(针对 Android 设备)
gl_Position = vec4(uv * 2.0 - 1.0, 0.0, 1.0);
}
渲染结果:
-- END --
进技术交流群,扫码添加我的微信:Byte-Flow
获取相关资料和源码
学习音视频、OpenGL ES、Vulkan 、Metal、图像滤镜、视频特效及相关渲染技术的付费社群,面试指导,1v1 简历服务,职业规划。
我的付费社群
推荐:
全网最全的 Android 音视频和 OpenGL ES 干货,都在这了
面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?