新手求教:如何在Three.js ShaderMaterial中实现ShaderToy?
ShaderToy代码转Three.js ShaderMaterial 教程
1. 基础转换步骤
1.1 搭建Three.js基础场景
先创建最基本的Three.js运行环境,用来承载Shader效果:
import * as THREE from 'three'; // 初始化核心组件 const scene = new THREE.Scene(); const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 创建全屏平面(Shader会渲染在这个平面上) const plane = new THREE.PlaneBufferGeometry(2, 2);
1.2 编写顶点着色器
顶点着色器只需传递UV坐标给片元着色器,逻辑非常简单:
varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }
1.3 转换片元着色器
将ShaderToy代码适配Three.js规范,按以下步骤修改:
- 开头添加精度声明,避免渲染异常
- 定义对应ShaderToy输入的uniform变量
- 复制原ShaderToy中所有自定义函数
- 修改
mainImage里的变量引用,替换为uniform变量 - 添加
main函数作为Three.js的片元入口
示例结构如下:
precision highp float; // 对应ShaderToy的输入变量,自定义参数也在这里声明 uniform float time; // 替换ShaderToy的iTime uniform vec2 resolution; // 替换ShaderToy的iResolution uniform sampler2D envMap; // 替换ShaderToy的iChannel2 uniform float bounceTime; // 自定义动态参数:反弹时间 // 复制原ShaderToy中的所有自定义函数(比如hash3、map、norm等) vec3 hash3(vec2 p) { vec3 q = vec3(dot(p,vec2(127.1,311.7)), dot(p,vec2(269.5,183.3)), dot(p,vec2(419.2,371.9))); return fract(sin(q)*43758.5453); } // ... 粘贴原ShaderToy的其他函数 ... // 保留原ShaderToy的mainImage函数,替换变量引用 void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 p = (2.0*fragCoord.xy-resolution.xy)/max(resolution.x,resolution.y); // 把原代码中固定的反弹时间换成uniform变量 float bounce = bounceTime; // 把原代码中的texture(iChannel2, ...)替换为texture(envMap, ...) // ... 其他原ShaderToy逻辑 ... } // Three.js片元着色器入口函数 void main() { vec4 fragColor; mainImage(fragColor, gl_FragCoord.xy); gl_FragColor = fragColor; }
1.4 创建ShaderMaterial并挂载
定义uniform对象,创建材质并绑定到平面:
// 初始化uniform变量 const uniforms = { time: { value: 0 }, resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, envMap: { value: new THREE.TextureLoader().load('你的环境贴图路径') }, bounceTime: { value: 1.5 } // 默认反弹时间 }; // 修正纹理上下颠倒问题 uniforms.envMap.value.flipY = false; // 创建ShaderMaterial const shaderMaterial = new THREE.ShaderMaterial({ uniforms: uniforms, vertexShader: document.getElementById('vertexShader').textContent, fragmentShader: document.getElementById('fragmentShader').textContent }); // 将材质绑定到平面并添加到场景 const mesh = new THREE.Mesh(plane, shaderMaterial); scene.add(mesh);
2. 动态化参数实现
2.1 实时更新时间
在动画循环中更新time变量,模拟ShaderToy的时间流逝:
function animate() { requestAnimationFrame(animate); // 用时间戳计算更准确的流逝时间 uniforms.time.value = performance.now() / 1000; renderer.render(scene, camera); } animate();
2.2 动态调整反弹时间
直接修改bounceTime的value即可,可配合GUI工具可视化调整:
// 示例:用dat.GUI做控制面板(需提前引入dat.GUI) import * as dat from 'dat.gui'; const gui = new dat.GUI(); gui.add(uniforms.bounceTime, 'value', 0.5, 5.0) .name('反弹时间') .onChange(() => { // 参数修改后自动生效,无需额外操作 });
2.3 动态更换环境贴图
更换纹理时必须设置needsUpdate通知Three.js更新:
// 示例:点击按钮切换贴图 document.getElementById('changeEnvBtn').addEventListener('click', () => { const loader = new THREE.TextureLoader(); loader.load('新的环境贴图路径', (newTexture) => { newTexture.flipY = false; uniforms.envMap.value = newTexture; uniforms.envMap.value.needsUpdate = true; }); });
2.4 适配窗口大小变化
监听窗口resize事件,更新分辨率uniform:
window.addEventListener('resize', () => { uniforms.resolution.value.set(window.innerWidth, window.innerHeight); renderer.setSize(window.innerWidth, window.innerHeight); });
内容的提问来源于stack exchange,提问作者Pallab
相关产品推荐
相关产品推荐

