shadertoy上有许多着色器的炫酷示例,充分展现了着色器和可编程管线的强大之处。网站的名字起的很有意思,真的感觉上面的大牛写着色器就像是在玩玩具,随心所欲,天马行空。我什么时候可以达到写着色器犹如玩玩具的水平啊!
上面的示例都是基于WebGL写的,而且都是只用了fragment shader,这让我感觉很诧异,只用fragment shader就可以写出各种3D效果,甚至是真实感渲染!
更新:其实是使用了ray tracing(光线跟踪)算法,该算法就是基于片段上的每一个pixel来进行渲染的,它以每一个pixel为出发点,通过跟踪这个点发出的光线(其实是反过来的)来计算这个点最终的颜色,优点是渲染效果很好,缺点是渲染速度很慢
目前我还不会WebGL,所以并不能理解其中的奥秘,只能硬生生的将这些fragment shader直接移植到桌面OpenGL的fragment shader中,通过uniform传递一些全局参数。vertex shader只起到传递作用。调用程序中我先建一个从(0, 0)-(window_width, window_height)映射到(-1, -1)-(1, 1)的数组,然后送入vertex buffer中,让fragment shader对每一个点做一次运算。运行了几个示例,都是很卡,毕竟是逐点运算,不过WebGL怎么就做到运行地那么流畅呢?我很是好奇啊,等以后学了WebGL再说。
移植的代码参考了shadertoy-render,这是一个用python写的移植程序并可输出视频文件。
下面上代码:
调用程序
1 | GLfloat g_vertex_buffer_data[window_width][window_height][2]; |
渲染前先做从pixel到vertex的坐标映射。pixel是经过光栅化的一个像素,相对于窗口平面来说,而vertex是光栅化之前的一个顶点,相对于3D空间来说,当然也可以是2D空间。
更新:这个繁琐的映射过程其实是不需要的,只需要画出画出铺满整个窗口的矩形即可,代码如下:
1
2
3
4
5
6
7
8
9
10
11 GLfloat g_vertex_buffer_data[12] = {
-1,-1,
-1, 1,
1, 1,
-1,-1,
1, 1,
1,-1};
GLuint vertexbuffer;
glGenBuffers(1, &vertexbuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);
详见另一篇博文《对片段着色器的一点理解》
1 | glUniform1f(glGetUniformLocation(programID, "iGlobalTime"), glfwGetTime()); |
渲染过程中需要传递两个全局参数,为了简单,目前只用到了窗口大小和时间两个参数。
1 | glUseProgram(programID); |
准备工作做好就可以渲染了。
vertex shader
1 |
|
简单的传递顶点。
fragment shader
1 |
|
前面的几个uniform是WebGL中会用到的全局参数,目前只用到了窗口大小和时间。
WebGL的主函数为mainIgame(),所以在main()中调用一下就好了,函数的第一个参数是一个传出参数,是经过计算后的当前片段的颜色,第二个参数是一个传入参数,是当前片段的坐标,范围为0-window_width和0-window_height。