Shadertoy着色器移植到Three.js经验总结

Shadertoy是一个流行的在线平台,用于创建和分享WebGL片段着色器。里面有很多令人惊叹的画面,甚至3D场景。本人也移植了几个ShaderToy上的着色器。本文将详细介绍移植过程中需要注意的关键点。

1. 基本结构差异

想要移植ShaderToy的shader到three.js,需要先了解他们直接的差异。他们之间的差异主要在以下几个方面:

  1. ShaderToy没有顶点着色器 ShaderTory上面的shader只有片元着色器,没有几何,移植到three.js上需要构造一个默认的顶点着色器,同时提供几何信息。这个也比较简单,使用两个三角形铺面整个视口即可。其实用其他的方式也可以,例如用一个大的三角形,只要有个物体几何能充满整个视口即可。

  2. 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 }
  1. 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;

2. 移植基本模版

html 复制代码
<!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 es
                in 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, 1]之间,不需要在VertexShader进行坐标变换,所有顶点着色器代码没有MVP矩阵:
plain 复制代码
#version 300 es
in vec3 position;
void main() {
    gl_Position = vec4(position, 1.0);
}
  1. 使用了RawShaderMaterial而不是ShaderMaterial,这样可对Shader代码进行完全掌控
  2. 语法上使用了WebGL2.0的语法,如果使用1.0的语法,需去掉版本声明、一些关键字

最后展示一个稍微复杂点的Shader效果:

结语

将Shadertoy着色器移植到Three.js需要对两者之间的差异有清晰理解。正确处理全局变量、语法、渲染管线等关方面的差异,可将绝大多数Shadertoy效果成功移植到Three.js项目中。

相关推荐
偷光15 分钟前
现代 CSS 高阶技巧:实现平滑内凹圆角的工程化实践
前端·css·小程序
Blossom.11833 分钟前
人工智能在智能供应链中的创新应用与未来趋势
前端·人工智能·深度学习·安全·机器学习
无限大61 小时前
《计算机“十万个为什么”》之前端与后端
前端·后端·程序员
JuneXcy1 小时前
Vue 核心技术与实战day07
前端·javascript·vue.js
shibin1 小时前
基于axios 二次封装:构建强大的 HTTP 请求层
前端·typescript
xianshenglu1 小时前
我的 Angular 总结:创建一个通用测试模块,简化单元测试
前端·javascript·angular.js
粥里有勺糖1 小时前
视野修炼-技术周刊第121期 | Rolldown-Vite
前端·javascript·github
帅夫帅夫1 小时前
四道有意思的考题
前端·javascript·面试
tonytony1 小时前
useRequest如何避免Race condition
前端·react.js
白柚Y1 小时前
小程序跳转H5或者其他小程序
前端·小程序