three.js使用Shadertoy的着色器
shadertor上有许多非常有趣的着色器,我们可以借鉴并用在自己的three的项目上。
1 在shadertoy上新建一个着色器,默认有一个主函数,如下图所示。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord/iResolution.xy;
// Time varying pixel color
vec3 col = 0.5 + 0.5*cos(iTime+uv.xyx+vec3(0,2,4));
// Output to screen
fragColor = vec4(col,1.0);
}
我们先修改这个基本的demo,然后再去借鉴别人的着色器。
观察一下shadertoy的着色器代码,发现和three中常用的有些区别:
- 没有顶点着色器
- 没有main函数,而是mainImage。
- 没有gl_FragColor,而是fragColor 。
- iTime 和 iResolution 、fragCoord的问题
处理方式:
1. 没有顶点着色器,这问题不大,先不关心。
2. 把mainImage改造成main函数,out和in的两个参数就不要了。
3. 把fragColor 换成 gl_FragColor。
4. iTime是一个变量(shadertoy自带的变量),在three中可以通过uniform的方式传入。
5. iResolution是canvas的分辨率,因为shadertoy是‘满屏’显示的,相当于three中用了正交相机然后用一个平面铺满整个canvas的。在three中,可以设置成新建的平面的分辨率。同时fragCoord我们对应的也改成uv的坐标。这样我们就需要引入顶点着色器了,然后传入uv给片元着色器。
关于着色器内置变量:
顶点着色器可用的内置变量如下表:
名称 | 类型 | 描述 |
---|---|---|
gl_Color | vec4 | 输入属性-表示顶点的主颜色 |
gl_SecondaryColor | vec4 | 输入属性-表示顶点的辅助颜色 |
gl_Normal | vec3 | 输入属性-表示顶点的法线值 |
gl_Vertex | vec4 | 输入属性-表示物体空间的顶点位置 |
gl_MultiTexCoordn | vec4 | 输入属性-表示顶点的第n个纹理的坐标 |
gl_FogCoord | float | 输入属性-表示顶点的雾坐标 |
gl_Position | vec4 | 输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。所有的顶点着色器都必须写这个值。 |
gl_ClipVertex | vec4 | 输出坐标,用于用户裁剪平面的裁剪 |
gl_PointSize | float | 点的大小 |
gl_FrontColor | vec4 | 正面的主颜色的varying输出 |
gl_BackColor | vec4 | 背面主颜色的varying输出 |
gl_FrontSecondaryColor | vec4 | 正面的辅助颜色的varying输出 |
gl_BackSecondaryColor | vec4 | 背面的辅助颜色的varying输出 |
gl_TexCoord[] | vec4 | 纹理坐标的数组varying输出 |
gl_FogFragCoord | float | 雾坐标的varying输出 |
片段着色器的内置变量如下表:
名称 | 类型 | 描述 |
---|---|---|
gl_Color | vec4 | 包含主颜色的插值只读输入 |
gl_SecondaryColor | vec4 | 包含辅助颜色的插值只读输入 |
gl_TexCoord[] | vec4 | 包含纹理坐标数组的插值只读输入 |
gl_FogFragCoord | float | 包含雾坐标的插值只读输入 |
gl_FragCoord | vec4 | 只读输入,窗口的x,y,z和1/w |
gl_FrontFacing | bool | 只读输入,如果是窗口正面图元的一部分,则这个值为true |
gl_PointCoord | vec2 | 点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。 |
gl_FragData[] | vec4 | 使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。 |
gl_FragColor | vec4 | 输出的颜色用于随后的像素操作 |
gl_FragDepth | float | 输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替 |
var vertexShader = `
varying vec2 vUv;
void main(){
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
var fragmentShader = `
varying vec2 vUv;
uniform float iTime;
uniform vec3 iResolution;
void main(){
vec3 col = 0.5 + 0.5*cos(iTime+vUv.xyx+vec3(0,2,4));
gl_FragColor = vec4(col,1.0);
}
`;
var uniforms = {
iTime:{value:0},
iResolution: { value: new THREE.Vector3( 40,40,1) },
};
var shadertoy = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms,
side:2,
});
var plane = new THREE.Mesh(planeGeo, shadertoy);
setInterval(()=>{
uniforms.iTime.value += 0.1;
},20);
运行结果如下:
修改原理我们已经理解了,下面找个复杂点的例子试一下。
先来个这个吧:https://www.shadertoy.com/view/wtfXW2 ,看起来像细胞分裂。
shadertoy源码
const int n = 800;
const float rate = 7.;
const float lineThickness = 2.2;
const float colours = 0.05; // proportion of cells to colour in
const bool zoom = true;
const float phi = 1.6180339887498948;
const float tau = 6.2831853071795865;
void mainImage( out vec4 fragColour, in vec2 fragCoord )
{
vec2 uv = (fragCoord-iResolution.xy*.5)/iResolution.y;
float penOut = lineThickness/iResolution.y;
float penIn = (lineThickness-2.8)/iResolution.y;
float t = iTime*rate;
fragColour = vec4(0,0,0,1);
float scale = sqrt(float(n));
if ( zoom ) scale = min( scale, pow((iTime+7.)*rate*.5,.6) ); // keep the edgemost points in shot as we zoom
float closest = 1e38;
float closest2 = 1e38;
for ( int i=0; i < n; i++ )
{
float f = float(i);
f += fract(t);
float r = sqrt(f/128.);
r *= 13./scale;
float a = fract((f-t)*phi)*tau;
vec2 pos = r*vec2(sin(a),cos(a));
vec3 col = sin(vec3(3,1,6)*(float(i)-floor(t)))*.5+.5;
if ( fract(col.y*64.) > colours ) col = vec3(1);
float l = length(pos-uv);
// add a ring to help me track size (so it doesn't look like we're zooming out)
//col *= smoothstep(penIn,penOut,abs(l/scale-.001)*scale);
if ( i == 0 ) l += smoothstep(1.,0.,fract(t))*1.2/scale; // grow the new point
if ( l < closest )
{
if ( closest < closest2 ) closest2 = closest;
closest = l;
fragColour.rgb = col; // *(1.-l*sqrt(float(n)));
}
else if ( l < closest2 )
{
closest2 = l;
}
fragColour.rgb = mix(fragColour.rgb,vec3(0),smoothstep(penOut,penIn,length(pos-uv)));
}
// cell borders
fragColour.rgb *= smoothstep(penIn,penOut,(closest2-closest));//*scale);
}
修改后的代码:
var vertexShader = `
varying vec2 vUv;
void main(){
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`;
var fragmentShader = `
const int n = 800;
const float rate = 7.;
const float lineThickness = 2.2;
const float colours = 0.05; // proportion of cells to colour in
const bool zoom = true;
const float phi = 1.6180339887498948;
const float tau = 6.2831853071795865;
varying vec2 vUv;
uniform vec3 iResolution;
uniform float iTime;
void main()
{
vec2 uv = vUv-0.5;
float penOut = lineThickness/iResolution.y;
float penIn = (lineThickness-2.8)/iResolution.y;
float t = iTime*rate;
gl_FragColor = vec4(0,0,0,1);
float scale = sqrt(float(n));
if ( zoom ) scale = min( scale, pow((iTime+7.)*rate*.5,.6) ); // keep the edgemost points in shot as we zoom
float closest = 1e38;
float closest2 = 1e38;
for ( int i=0; i < n; i++ )
{
float f = float(i);
f += fract(t);
float r = sqrt(f/128.);
r *= 13./scale;
float a = fract((f-t)*phi)*tau;
vec2 pos = r*vec2(sin(a),cos(a));
vec3 col = sin(vec3(3,1,6)*(float(i)-floor(t)))*.5+.5;
if ( fract(col.y*64.) > colours ) col = vec3(1);
float l = length(pos-uv);
// add a ring to help me track size (so it doesn't look like we're zooming out)
//col *= smoothstep(penIn,penOut,abs(l/scale-.001)*scale);
if ( i == 0 ) l += smoothstep(1.,0.,fract(t))*1.2/scale; // grow the new point
if ( l < closest )
{
if ( closest < closest2 ) closest2 = closest;
closest = l;
gl_FragColor.rgb = col; // *(1.-l*sqrt(float(n)));
}
else if ( l < closest2 )
{
closest2 = l;
}
gl_FragColor.rgb = mix(gl_FragColor.rgb,vec3(0),smoothstep(penOut,penIn,length(pos-uv)));
}
// cell borders
gl_FragColor.rgb *= smoothstep(penIn,penOut,(closest2-closest));//*scale);
}
`;
var uniforms = {
iTime:{value:0},
iResolution: { value: new THREE.Vector3( 100,100,1) },
};
var shadertoy = new THREE.ShaderMaterial({
vertexShader,
fragmentShader,
uniforms,
side:2,
});
var box = new THREE.Mesh(
new THREE.BoxGeometry(100,100,100),
shadertoy
);
scene.add(box);
setInterval(()=>{
uniforms.iTime.value += 0.1;
},100);
运行的效果:
看起来不错吧,这次我把效果应用在立方体上了。