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);
}