前言
前阵子再忙着调研第三方相机应用的滤镜实现,并将这些作为滤镜做成AE插件的形式供设计师使用,以便做出更唯美的滤镜效果。在开发AE 插件过程中,发现有的着色器代码,在AE环境中和vscode的 shader toy 中的输出完全不一致,通过一段时间的摸索最终得到的结论是OpenGL 与OpenGL ES 的纹理坐标系统不一致导致的问题。
WebGL与OpenGL的差异
在开发滤镜效果的时候,为了方便实时预览, 在 vscode中开发着色器,借助shader toy 插件实现实时预览。经过调研(面向百度和google的搜索引擎调研)得知 shader toy 是基于 WebGL (浏览器图形库),而 WebGL 是OpenGL ES 的web实现,下面详细说明一下 WebGL 和 OpenGL 之间的差异。
WebGL
WebGL 为web图形库的缩写。它主要用于渲染而为和交互式三维图形库。它是可以与HTML5一起使用的Javascript API。它支持跨平台,并且仅以英语提供。WebGL程序由用JavaScript编写的控制代码组成
- Web GL 是基于OpenGL ES 2.0 的javascript API,而不是纯OpenGL(ES代表嵌入式系统)
- WebGL 通过HTML5的Canvas来喝Dom打交道。因此也和OpenGL ES 2.0 一样,使用 GLSL作为Shading language作为着色器语言
- WebGL 2.0 基于 OpenGL ES3.0 确保了提供多选型的 Web GL 1.0 扩展,并引入新的 API。
OpenGL
- OpenGL ES 本质上是OpenGL的一个子集。
- OpenGL ES是Khronos协会从OpenGL裁剪定制而来的,转为手机,与其他等嵌入式设备而设计。他的接口其实和OpenGL 很类似。
- OpenGL ES相对OpenGL删减了一切定效能的操作方式,与高性能的绝不留低效能的,即只求效能不求兼容性。
主要区别
WebGL基于OpenGL ES,它缺少常规OpenGL具有的许多功能,例如仅支持顶点和片段着色器。
OpenGL具有WebGL所不具备的功能,例如几何体着色器,镶嵌细分着色器和计算着色器。
WebGL主要用于浏览器。OpenGL确实需要本机驱动程序,并且主要用于安装软件。
WebGL用于Web应用程序,而OpenGL用于许多视频游戏。
WebGL更易于学习和开发应用程序。如果您熟悉WebGL,则可以轻松学习OpenGL。
在WebGL中,它可以使用2D纹理来伪造3D纹理。在OpenGL中,不需要这样做,因为它具有很多功能,例如几何和着色器。
OpenGL、OpenGL ES、WebGL纹理坐标映射原理
OpenGL ES
摘录至《OpenGL ES 3.x 游戏开发(上卷)》第七章
启用纹理映射功能后,如果吧一幅纹理应用到相应的集合图源,就必须告知渲染系统如何惊醒纹理的映射。告知的方式就是为图源中的顶点指定恰当的纹理坐标,纹理坐标用浮点数来表示,范围一般从0.0 到1.0,下面的图7-1 就给出了纹理映射的基本原理。
左侧试衣服纹理图,其位于纹理坐标系中。纹理坐标系远点在左上侧,向右为S轴,向下为T轴,两个周的取值范围都是0.0~1.0.也就是说不论实际纹理图的尺寸如何,其横、纵坐标最大值都为1.
右侧为三角形的图元,其3个顶点 A、B、C都指定了纹理坐标,3组纹理坐标正好在右侧的纹理中确定了需要映射的三角形纹理区域。
WebGL
摘录至《WebGL3D开发实战详解第2版》第6章,第1节
启用纹理映射功能后,如果想把一幅纹理应用到相应的集合图元中,就必须告知渲染系统如何进行纹理映射。告知方式就是为图元中的顶点指定恰当的纹理坐标,纹理坐标用浮点数来表示,范围一般为0.0~1.0.图6-1中给出了纹理映射的基本原理。
纹理坐标的原点在左上侧,向右为S轴,向下为T,两个周的取值范围都是 0.0~1.0。也就是说其横向、纵向坐标的最大值都是1。右侧是一个三角形图元,其中3个顶点A、B、C都指定了纹理坐标,3组纹理坐标正好在右侧的纹理图中确定了需要映射的三角形区域。
OpenGL
摘录至 https://learnopengl-cn.github.io 入门->纹理
我们已经了解到,我们可以为每个顶点添加颜色来增加图形的细节,从而创建出有趣的图像。但是,如果想让图形看起来更真实,我们就必须有足够多的顶点,从而指定足够多的颜色。这将会产生很多额外开销,因为每个模型都会需求更多的顶点,每个顶点又需求一个颜色属性。
艺术家和程序员更喜欢使用纹理(Texture)。纹理是一个2D图片(甚至也有1D和3D的纹理),它可以用来添加物体的细节;你可以想象纹理是一张绘有砖块的纸,无缝折叠贴合到你的3D的房子上,这样你的房子看起来就像有砖墙外表了。因为我们可以在一张图片上插入非常多的细节,这样就可以让物体非常精细而不用指定额外的顶点。
下面你会看到之前教程的那个三角形贴上了一张砖墙图片。
为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样(译注:采集片段颜色)。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。
纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样(Sampling)。纹理坐标起始于(0, 0),也就是纹理图片的左下角,终始于(1, 1),即纹理图片的右上角。下面的图片展示了我们是如何把纹理坐标映射到三角形上的。
我们为三角形指定了3个纹理坐标点。如上图所示,我们希望三角形的左下角对应纹理的左下角,因此我们把三角形左下角顶点的纹理坐标设置为(0, 0);三角形的上顶点对应于图片的上中位置所以我们把它的纹理坐标设置为(0.5, 1.0);同理右下方的顶点设置为(1, 0)。我们只要给顶点着色器传递这三个纹理坐标就行了,接下来它们会被传片段着色器中,它会为每个片段进行纹理坐标的插值。
将OpenGL ES 着色器代码移植到OpenGL 着色器
因为shader toy是基于 WebGL,而WebGL是基于OpenGL ES的浏览器实现,所以我们要想清楚的了解shader toy 渲染与AE 插件渲染的区别,只需要了解OpenGL和OpenGL ES根本上的差异。下图为OpenGL和OpenGLES坐标空间和纹理空间的差异:
从上图中可以看出,OpenGL 和 OpenGL ES 两个环境的空间坐标系统完全一致;纹理坐标系系统的u方向一致,而v方向相反。
图像的点运算
因为OpenGL ES 空间坐标系统和OpenGL 空间坐标系统是一致的,虽然 纹理坐标系统并不一致,但是只要不对纹理坐标做运算,OpenGL ES 和 OpenGL 两个环境输出效果相同,示例图如下:
图像的几何运算
在做图形渲染的开发过程中,通常会用到一个颜色映射表这么一个东西,颜色映射表是一个固定尺寸的纹理图片。在片段着色器中具体使用步骤如下:
- 获取输入纹理的 rgba 原始色值。
- 通过 rgba 原始色值执行一系列运算最终得到一个 uv 坐标。
- 通过uv坐标去颜色映射表中去取出颜色。
- 将取得的颜色色值与原始色值进行混合得到最终输出片段色值。
运行流程图如下:
从上面的流程图中可以看出,在第三部通过uv坐标取颜色映射表中取颜色的时候,OpenGL 和OpenGL ES 环境取到了不同的颜色,从而直接导致了输出颜色的不一致。这儿有一个问题,在实际预览效果中,OpenGL 和OpenGL ES 预览效果跟理论恰好相反,希望大佬帮忙解决一下这个问题。
为使得OpenGL 纹理坐标中取出来的颜色与OpenGL ES 纹理坐标取出来的颜色一致,有如下两个方案:
- 颜色表保持不变,纹理的 UV.y = 1 - UV.y;这种变换是使得OpenGL 的纹理坐标与OpenGL ES 的纹理坐标一致。
- UV 坐标保持不变,在 OpenGL 环境中将颜色映射表进行上下翻转;这也能使得OpenGL 环境与OpenGL ES 环境纹理坐标在颜色映射表中取到同一块颜色。
总结
通过上面的描述,可以得知,导致同一着色器代码在 浏览器端(WebGL)、手机端(OpenGL ES)、电脑端(OpenGL)出现不同的效果。根本原因是纹理坐标系统的不一致,如果需要将一个端的着色器代码移植到另一个端,那么就需要考虑这种坐标系统的不一致性,一些简单的纹理坐标运算使用文中的方法即可,但是对于一些相对复杂空域变换的效果,需要做一些新的思考。有疑问可以评论区留言,大家相互学习交流。