面试官:纹理贴图必须要输入顶点坐标或纹理坐标吗

字节流动

共 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.00.01.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, 03);

由于 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.00.01.0);

渲染结果



-- END --


进技术交流群,扫码添加我的微信:Byte-Flow



获取相关资料和源码


学习音视频、OpenGL ES、Vulkan 、Metal、图像滤镜、视频特效及相关渲染技术的付费社群,面试指导,1v1 简历服务,职业规划。


我的付费社群


推荐:

Android FFmpeg 实现带滤镜的微信小视频录制功能

全网最全的 Android 音视频和 OpenGL ES 干货,都在这了

一文掌握 YUV 图像的基本处理

抖音传送带特效是怎么实现的?

所有你想要的图片转场效果,都在这了

面试官:如何利用 Shader 实现 RGBA 到 NV21 图像格式转换?

我用 OpenGL ES 给小姐姐做了几个抖音滤镜


浏览 70
1点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
1点赞
评论
收藏
分享

手机扫一扫分享

分享
举报