对片段着色器的一点理解

之前做了一个shadertoy上的WebGL示例到桌面OpenGL的移植,当时由于对片段着色器理解不够透彻,导致使用了传递每一个pixel的vertex坐标(共window_width x window_height个点,pixel是经过光栅化的一个像素,相对于窗口平面来说,而vertex是光栅化之前的一个顶点,相对于3D空间来说,当然也可以是2D空间)的笨拙方法,渲染速度很慢。之后又想了想片段着色器的功能,觉得是不是只需要传递6个点(两个三角形)画出铺满整个窗口的矩形就可以了,今天实验了一下,果然如此!

实验过程:

1
2
3
4
5
6
7
GLfloat g_vertex_buffer_data[12] = {
-1,-1,
-1, 1,
1, 1,
-1,-1,
1, 1,
1,-1};

将渲染前的坐标映射简化为只传递6个点(两个三角形,每个点有x和y两个坐标,顶点数组大小为6x2=12)。


其他步骤不变。程序运行正常,渲染效果相同,但速度快了很多。至于为什么会快应该与OpenGL内部机制有关,目前尚不清楚,留待以后学习。但起码明白了片段着色器的渲染方法,即对用户传入的整个片段(三角形)的每一个pixel做一次计算,如果用户不需要逐点计算,则OpenGL会用插值的方法根据顶点的颜色计算出片段上每一个pixel的颜色。这也就解释了纹理是如何发挥作用的,我们只需告诉OpenGL顶点的uv坐标,OpenGL会计算出片段上各点的uv坐标,然后从纹理中获取颜色。

其实之前参考的python移植程序shadertoy-render用的就是这种方法,不过在我移植的过程中由于一些其他错误掩盖了该方法的可行性。


之前是画了一个铺满整个窗口的矩形,那如果只画出窗口的一部分呢?实验如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
GLfloat g_vertex_buffer_data[24] = {
-1,-1,
0,-1,
0, 0,
-1,-1,
0, 0,
-1, 0,
1, 1,
0, 1,
0, 0,
1, 1,
0, 0,
1, 0};

这次我们将窗口分为四个矩形,只画出左下角和右上角的矩形,渲染结果如下:
局部渲染
这就实现了局部渲染,也就是说片段着色器会并且只会对用户传入的片段做处理,会计算片段上每一个点的颜色。理解到这一点就好了,这就是fragment的含义。

为什么到现在才能够理解正确,也许我需要再找一些正式的书籍来看看,或者是我用的还太少。


另外,之前在做移植的时候不明白为什么只通过片段着色器就可以渲染出3D甚至真实感效果,后来看了一点ray tracing(光线跟踪)的东西,才知道这些示例大都用了光线跟踪算法,而光线跟踪算法就是基于片段上的每一个pixel来渲染的,它以每一个pixel为出发点,通过跟踪这个点发出的光线(其实是反过来的)来计算这个点最终的颜色,优点是渲染效果很好,缺点是渲染速度很慢。