Shadertoy是一個流行的在線平臺,用于創建和分享WebGL片段著色器。里面有很多令人驚嘆的畫面,甚至3D場景。本人也移植了幾個ShaderToy上的著色器。本文將詳細介紹移植過程中需要注意的關鍵點。
1. 基本結構差異
想要移植ShaderToy的shader到three.js,需要先了解他們直接的差異。他們之間的差異主要在以下幾個方面:
-
ShaderToy沒有頂點著色器
ShaderTory上面的shader只有片元著色器,沒有幾何,移植到three.js上需要構造一個默認的頂點著色器,同時提供幾何信息。這個也比較簡單,使用兩個三角形鋪面整個視口即可。其實用其他的方式也可以,例如用一個大的三角形,只要有個物體幾何能充滿整個視口即可。 -
ShaderToy提供了很多內置變量
ShaderToy提供了不少內置變量,,在three.js中需要咱們自己用uniform來傳入。ShaderToy內置變量處理方法如下:
全局變量 | 含義 | three.js修改方案核心代碼 |
---|---|---|
uniform vec3 iResolution; | 視口大小,單位像素 | uniforms.iResolution = { value: new THREE.Vector2(options.width, options.hieght) } |
uniform float iTime; | 從開始運行到現在的時間,單位秒 | var clock = new THREE.Clock(); this.uniforms.iTime.value = this.clock.getElapsedTime(); |
uniform float iTimeDelta; | 上一幀渲染到現在的時間,單位秒 | var clock = new THREE.Clock(); this.uniforms.iTimeDelta.value = this.clock.getDelta(); |
uniform int iFrame; | 第幾幀 | this.uniforms.iFrame.value = this.uniforms.iFrame.value + 1; |
uniform float iChannelTime[4]; | / | / |
uniform vec3 iChannelResolution[4]; | 每個通道的分辨率,單位像素 | uniforms[“iChannelResolution”] = { type: “v3v”, value: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), ] }; |
uniform vec4 iMouse; | 是vec4類型,xy是鼠標當前位置與單擊位置,zw是單擊位置 | document.addEventListener(‘mousemove’, (e) => { material.uniforms.iMouse.value.x = e.clientX; material.uniforms.iMouse.value.y = window.innerHeight - e.clientY; }); document.addEventListener(‘mousedown’, (e) => { material.uniforms.iMouse.value.z = e.clientX; material.uniforms.iMouse.value.w = window.innerHeight - e.clientY; }); |
uniform samplerXX iChanneli; | 通道的紋理 | uniforms[“iChannel” + i] = { type: “t”, value: texture } |
- ShaderToy語法的差異
ShaderToy語法核心都是 GLSL,只是在在變量命名、輸入輸出方式、內置函數/變量等方面存在區別。以下是關鍵語法差異的對比:
ShaderToy | three.js |
---|---|
入口函數是:mainImage(out vec4 fragColor, in vec2 fragCoord) | 入口函數是:void main() |
fragCoord當前像素的坐標 | 用的是gl_FragCoord |
用變量fragColor作為輸出 | 通過 gl_FragColor 變量輸出 |
不需要定義變量精度 | 需定義精度:precision highp float; precision highp int;precision highp sampler2D;precision highp samplerCube; |
移植基本模版
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Shadertoy to Three.js</title><style>body {margin: 0;overflow: hidden;}canvas {display: block;}</style>
</head><body><script src="./three.min.js"></script><script>const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);const renderer = new THREE.WebGLRenderer({ antialias: true });renderer.setSize(window.innerWidth, window.innerHeight);document.body.appendChild(renderer.domElement);console.log(renderer.capabilities.isWebGL2)const geometry = new THREE.BufferGeometry();geometry.setAttribute("position", new THREE.Float32BufferAttribute([-1, -1, 0,1, -1, 0,1, 1, 0,-1, 1, 0], 3))geometry.setIndex([0, 1, 2, 0, 2, 3]);const material = new THREE.RawShaderMaterial({uniforms: {iResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },iTime: { value: 0 },mouse: { value: new THREE.Vector4(0, 0, 0, 0) },},// glslVersion: THREE.GLSL3,vertexShader: `#version 300 esin vec3 position;void main() {gl_Position = vec4(position, 1.0);}`,fragmentShader: `#version 300 es
precision highp float;
precision highp int;
precision highp sampler2D;
precision highp samplerCube;
out vec4 fragColor;uniform vec2 iResolution;
uniform float iTime;void main( )
{vec2 uv = gl_FragCoord.xy/iResolution.xy;// [0,1]uv -= 0.5;uv.x *= iResolution.x / iResolution.y;float d = length(uv);float r = 0.3;float c = smoothstep(r, r + 0.002, d);fragColor = vec4(vec3(c), 1.);
}
`});const plane = new THREE.Mesh(geometry, material);scene.add(plane);window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth, window.innerHeight);material.uniforms.iResolution.value.set(window.innerWidth, window.innerHeight);});document.addEventListener('mousemove', (e) => {material.uniforms.iMouse.value.x = e.clientX;material.uniforms.iMouse.value.y = window.innerHeight - e.clientY;});document.addEventListener('mousedown', (e) => {material.uniforms.iMouse.value.z = e.clientX;material.uniforms.iMouse.value.w = window.innerHeight - e.clientY;});function animate() {requestAnimationFrame(animate);material.uniforms.iTime.value = performance.now() / 1000; // 秒為單位renderer.render(scene, camera);}animate();</script>
</body></html>
這個模版是畫圓的實例,效果如下:
有以下幾點需要注意:
- 兩個三角形面片的坐標已經在[-1, 1]之間,不需要在VertexShader進行坐標變換,所有頂點著色器代碼沒有MVP矩陣:
#version 300 es
in vec3 position;
void main() {gl_Position = vec4(position, 1.0);
}
- 使用了RawShaderMaterial而不是ShaderMaterial,這樣可對Shader代碼進行完全掌控
- 語法上使用了WebGL2.0的語法,如果使用1.0的語法,需去掉版本聲明、一些關鍵字
最后展示一個稍微復雜點的Shader效果:
結語
將Shadertoy著色器移植到Three.js需要對兩者之間的差異有清晰理解。正確處理全局變量、語法、渲染管線等關方面的差異,可將絕大多數Shadertoy效果成功移植到Three.js項目中。