哈哈,这个问题真是问对地方了,刚好这个平台集成了ShaderToy。
首先要明白,ShaderToy 是一个编写片元着色器的一个小的IDE。
那么什么是片元着色器?
如果你渲染一个全屏四边形,意味着这四个点分别放置在视口的四个角落,那么这个四边形的所有像素的信息的处理阶段就被称为片元着色器(DirectX称为像素着色器),因为可以说现在每个片元正好对应屏幕上的一个像素。所以片元着色器是一种针对全屏四边形的像素着色器。
因此,属性总是相同的,顶点着色器也是如此:
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 的形式进行图元装配从而渲染。有些人会使用片元着色器的内置变量 gl_FragCoord,然后将其除以屏幕的尺寸,即宽高,得出的结果就是uv(0~1)的取值范围。
简单举一下顶点着色器与片元着色器的例子:
顶点着色器:
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 等于 gl_FragCoord/uScreenResolution
// 这里可以做一些片元着色器其它相关的算法
gl_FragColor = vec3(someColor);
}
而ShaderToy的做法会默认为用户提供一些 uniform,如 iResolution(即 uScreenResolution)、iGlobalTime、iMouse 等,你可以在你的片元着色器中直接对它们进行拿来即用。
对于直接在片元着色器(即像素着色器)中渲染几何体,开发者通常使用一种称为RayMarching(光线行进)的技术。会比较复杂,简单讲:你可以通过一些数学公式来表示你的几何体(通常为SDF),然后在片元着色器中,当你想检查某个像素是否是你几何体的一部分时,你使用那个公式来获取信息。
简单用这个平台运行下ShaderToy,使用方式可以看下参考下平台使用指南:
float opSmoothUnion( float d1, float d2, float k )
{
float h = clamp( 0.5 + 0.5*(d2-d1)/k, 0.0, 1.0 );
return mix( d2, d1, h ) - k*h*(1.0-h);
}
float sdSphere( vec3 p, float s )
{
return length(p)-s;
}
float map(vec3 p)
{
float d = 2.0;
for (int i = 0; i < 16; i++) {
float fi = float(i);
float time = iTime * (fract(fi * 412.531 + 0.513) - 0.5) * 2.0;
d = opSmoothUnion(
sdSphere(p + sin(time + fi * vec3(52.5126, 64.62744, 632.25)) * vec3(2.0, 2.0, 0.8), mix(0.5, 1.0, fract(fi * 412.531 + 0.5124))),
d,
0.4
);
}
return d;
}
vec3 calcNormal( in vec3 p )
{
const float h = 1e-5; // or some other value
const vec2 k = vec2(1,-1);
return normalize( k.xyy*map( p + k.xyy*h ) +
k.yyx*map( p + k.yyx*h ) +
k.yxy*map( p + k.yxy*h ) +
k.xxx*map( p + k.xxx*h ) );
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.xy;
// screen size is 6m x 6m
vec3 rayOri = vec3((uv - 0.5) * vec2(iResolution.x/iResolution.y, 1.0) * 6.0, 3.0);
vec3 rayDir = vec3(0.0, 0.0, -1.0);
float depth = 0.0;
vec3 p;
for(int i = 0; i < 64; i++) {
p = rayOri + rayDir * depth;
float dist = map(p);
depth += dist;
if (dist < 1e-6) {
break;
}
}
depth = min(6.0, depth);
vec3 n = calcNormal(p);
float b = max(0.0, dot(n, vec3(0.577)));
vec3 col = (0.5 + 0.5 * cos((b + iTime * 3.0) + uv.xyx * 2.0 + vec3(0,2,4))) * (0.85 + b * 0.35);
col *= exp( -depth * 0.15 );
// maximum thickness is 2m in alpha channel
fragColor = vec4(col, 1.0 - (depth - 0.5) / 2.0);
}