WebGL/GLSL - ShaderToy是如何工作的?

73

我最近一直在逛Shadertoy(https://www.shadertoy.com/),试图学习更多关于OpenGL和GLSL的知识。

据我所了解,OpenGL用户首先必须准备好要使用的所有几何体并配置OpenGL服务器(允许的灯光数量、纹理存储等)。完成这些步骤后,用户必须提供至少一个顶点着色器程序和一个片段着色器程序才能编译一个OpenGL程序。

但是,当我查看Shadertoy上的代码示例时,我只看到了一个着色器程序,并且大多数使用的几何体似乎直接写入了GLSL代码中。

这是怎么工作的?

我的猜测是预先准备了一个顶点着色器,并且可以编辑/示例着色器仅为片段着色器。但这并不能解释一些更复杂示例中的几何体...

有人能解释一下Shadertoy如何工作吗?


1
请查看此问答:Check out this q/a - gman
不熟悉着色器的读者可能会对以下内容感兴趣:https://dev59.com/wmMm5IYBdhLWcg3wMs3R - Ciro Santilli OurBigBook.com
5个回答

91

ShaderToy 是用于编写像素着色器的工具。

什么是像素着色器?

如果您渲染一个全屏幕四边形,意味着每个点放置在视口的四个角落之一,那么该四边形的片元着色器被称为像素着色器,因为现在每个片元可以精确地对应于屏幕上的一个像素。因此,像素着色器是一个全屏幕四边形的片元着色器。

因此,属性始终相同,顶点着色器也是如此:

positions = [ [-1,1], [1,1], [-1,-1], [1,-1] ]
uv = [ [0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0] ]

那个四边形被渲染成了 TRIANGLE_STRIP

而一些人不是显式地设置 UVs,而是使用片段着色器的内置变量 gl_FragCoord,然后将其除以,例如,一个 uniform vec2 uScreenResolution

顶点着色器:

attribute vec2 aPos;
attribute vec2 aUV;
varying vec2 vUV;

void main() {
    gl_Position = vec4(aPos, 0.0, 1.0);
    vUV = aUV;
}

而片段着色器看起来将像这样:

uniform vec2 uScreenResolution;
varying vec2 vUV;

void main() {
    // vUV is equal to gl_FragCoord/uScreenResolution
    // do some pixel shader related work
    gl_FragColor = vec3(someColor);
}

ShaderToy提供了一些默认的uniform变量,包括iResolution(也叫uScreenResolution)、iGlobalTimeiMouse等,你可以在像素着色器中使用它们。

为了直接将几何图形编写到片段着色器(也称为像素着色器)中,开发者会使用一种叫做光线追踪的技术。这是一个相当复杂的编程领域,简单来说: 通过某些数学公式来表达你的几何图形,在像素着色器中,当你想要检测某个像素是否属于你的几何图形时,你可以使用该公式来获取该信息。搜索一下谷歌应该可以找到大量关于什么是光线追踪以及如何构建光线追踪器的资源,这可能会有所帮助: 如何在现代OpenGL中进行光线追踪?

希望这可以帮到你。


1
你知道为什么它以这种方式工作吗?这是一种艺术选择,还是光线追踪通常比使用顶点着色器更高效? - MasterScrat
7
通常情况下,光线追踪是在CPU上完成的,对于图像中的每个像素,会跟踪射向该像素的光线。由于像素着色器是为光栅化基元中的每个像素运行的程序,因此使用片段着色器进行光线追踪比使用顶点着色器更自然和直观。这是很合乎逻辑的事情,对吧?但是,也有像 http://www.vertexshaderart.com/ 这样的东西存在。在考虑某个着色器阶段的实际输出时,选择在GPU上使用VS / FS进行光线追踪。 - Dragan Okanovic
1
惊人的网站,适合那些刚接触OpenGL并且只学过现代OpenGL的人,"attribute = in"用于顶点着色器,"varying = out"用于顶点着色器,"varying = in"用于片段着色器。 - XueYu
如果您想自行实现,这是我使用的解决方案:https://www.shadertoy.com/view/3tX3zl此方案适用于WebGL 1和2版本。 - Rafael Beckel
@AbstractAlgorithm,你好,我知道这是一个关于Shadertoy的旧帖子,但我在@你,希望你能帮我解决一个简单的问题,似乎没有人能够回答我的问题。我在stackoverflow上发布了我的问题链接:https://stackoverflow.com/questions/64499166/glsl-openglshadertoys-fragcoord-xy-value-and-more - B.Castarunza

12

ShaderToy是显示用简单的GLSL编程处理所有光照、几何等内容的工具。它不是基于顶点的几何图形,而是主要用于光线投射来处理大部分三维内容,或者您也可以使用2D着色器等。

在GLSL语言中,任何颜色和空间数学都可以进行编程。高级算法的组合可以制作等值面、形状,然后将纹理投影到等值面上,并进行光线投射,从观察者向距离发送想象的直线,拦截路径中的任何物体,有许多用于3D的光线投射技术。

访问www.iquilezles.org,以了解shadertoy/glsl图形中使用的不同工具的概念。


11

Shadertoy因其类似玩具的特性而被称为“TOY”。它基本上是一款谜题游戏。给定一个函数,该函数的输入是当前像素位置,编写一个生成图像的函数。

该网站设置WebGL以绘制单个四边形,然后让您提供一个函数,该函数将当前正在渲染的像素作为fragCoord传递。接着您可以使用该函数计算一些颜色。

例如,如果您这样做:

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec4 red = vec4(1, 0, 0, 1);
    vec4 green = vec4(0, 1, 0, 1);
    fragColor = mix(red, green, sin(fragCoord.x / 10.0) * 0.5 + 0.5);
}

如果您这样做,则会得到红色和绿色条纹

enter image description here

https://www.shadertoy.com/view/3l2cRz

Shadertoy提供了一些其他的输入。最常见的是正在渲染的分辨率,即iResolution。 如果将fragCoord除以iResolution,则可以获得跨越画布从0到1的值,因此您可以轻松地使您的功能与分辨率无关。

这样我们就可以在中心绘制一个椭圆,如下所示:

void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
    // uv goes from 0 to 1 across and up the canvas
    vec2 uv = fragCoord / iResolution.xy;
    
    vec4 red = vec4(1, 0, 0, 1);
    vec4 green = vec4(0, 1, 0, 1);

    float distanceFromCenter = distance(uv, vec2(0.5));
    fragColor = mix(red, green, step(0.25, distanceFromCenter));
}

这个工具可以生成如下的图像:

enter image description here

第二个最常见的输入是iTime,以秒为单位,您可以在函数中使用animate参数随时间变化。

因此,如果您应用足够的数学知识,就可以生成像这个shadertoy着色器生成的这个图像那样令人惊叹的图像

很惊人,有人找出了只给出上述输入就能生成该图像所需的数学方法。

许多最惊人的shadertoy着色器使用一种称为ray marching的技术和一些称为“signed distance fields”的数学方法,您可以在这里阅读相关信息。

但是,我认为值得指出的是,尽管从shadertoy着色器中可以学习到许多酷炫的内容,但其中许多只适用于“如何使用一个仅由像素位置作为输入、单个颜色作为输出的函数创建美丽图像”的“难题”中。它们并不能回答“我应该如何为高性能应用程序编写着色器”的问题。

将以上的海豚shader与这个speedboat游戏进行比较

enter image description here

https://www.youtube.com/watch?v=7v9gZK9HqqI

当全屏时,海豚shadertoy着色器在我的NVidia GeForce GT 750上以大约2帧每秒的速度运行,而速度艇游戏则以60fps的速度运行。游戏运行快的原因是它使用更传统的技术,在投影三角形时绘制形状。即使是NVidia 1060 GTX也只能以大约10帧每秒的速度运行该海豚shader。


8

这基本上是将GLSL像素着色器源代码直接推送到图形卡中。真正的魔力在于人们使用的极其聪明的算法,例如射线行进、射线投射、射线追踪等,用来创建惊人的效果。最好看一些其他实时GLSL沙盒,如:http://glsl.heroku.com/http://webglplayground.net/。 它基本上创建了一个窗口,通常由两个三角形表示屏幕,然后着色器对每个像素进行处理,就像射线跟踪器一样。
我已经看了一段时间了,人们使用的算法令人惊叹,您需要一些严肃的数学技巧,并查找“演示编码”源代码,以便能够理解它们。许多着色器玩具都会让你大开眼界! 因此,总结一下,您只需要学习GLSL着色器编码和算法。没有简单的解决方案。


5

传统上,在计算机图形学中,使用顶点创建几何图形,并使用某种材料(例如带有光照的纹理)进行渲染。在GLSL中,顶点着色器处理顶点,而片段(像素)着色器处理材料。

但这并不是定义形状的唯一方法。就像可以通过程序定义纹理(而不是查找其纹素)一样,可以通过程序定义形状(而不是查找其几何形状)。

因此,类似于光线跟踪,这些片段着色器能够创建形状,而无需顶点定义其几何形状。

还有更多定义形状的方式。例如,体积数据(体素),曲面曲线等。计算机图形学文本应该涵盖其中的一些方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接