全部专题

把 Shadertoy 的 mainImage 放进 Three.js:从全屏画布到模型表面

Shadertoy 的 mainImage 不是 GLSL 的入口函数。移入 Three.js 后,需要补上 main,并从 JavaScript 更新 iTime 和 iResolution。先确认全屏平面运行正常,再为模型更换坐标来源,以便区分入口错误与 UV 错误。

先固定问题

Shadertoy 的 mainImage 不是 GLSL 内置入口。把函数复制到 Three.js 之后,还需要一个 main 调用它,并由 JavaScript 更新 iTime、iResolution。先用全屏平面验证颜色随时间变化,再去处理模型 UV,能把入口错误和坐标错误分开。

下面的模块用于已安装 three 的浏览器构建项目。它使用自己编写的正弦颜色示例,不依赖复制第三方作品。画布的 drawing buffer 尺寸包含像素比,iResolution 必须取同一个尺寸,不能直接使用 CSS 宽高。

关键代码

import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer();
document.body.append(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
const uniforms = { iTime: { value: 0 }, iResolution: { value: new THREE.Vector3(1, 1, 1) } };
const material = new THREE.ShaderMaterial({
  uniforms,
  vertexShader: `void main(){gl_Position=vec4(position.xy,0.0,1.0);}`,
  fragmentShader: `precision highp float;
uniform float iTime; uniform vec3 iResolution;
void mainImage(out vec4 color, in vec2 fragCoord){
  vec2 uv=fragCoord/iResolution.xy;
  color=vec4(0.5+0.5*sin(iTime+uv.xyx*6.0),1.0);
}
void main(){mainImage(gl_FragColor,gl_FragCoord.xy);}`
});
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material);
scene.add(mesh);
function resize(){
  renderer.setPixelRatio(Math.min(window.devicePixelRatio,2));
  renderer.setSize(window.innerWidth,window.innerHeight);
  const size=renderer.getDrawingBufferSize(new THREE.Vector2());
  uniforms.iResolution.value.set(size.x,size.y,1);
}
window.addEventListener('resize',resize);resize();
renderer.setAnimationLoop(ms=>{uniforms.iTime.value=ms*0.001;renderer.render(scene,camera);});
// On teardown: stop the loop, remove resize, dispose geometry/material/renderer.

按顺序验证

先检查全屏颜色是否变化。给顶点着色器增加 vUv=uv,片段中改用 vUv*iResolution.xy 后,再用透视相机和模型变换矩阵将材质放到模型表面。全屏版的 gl_Position 故意忽略模型矩阵,不能直接沿用到三维网格。

适用范围和常见错误

有 Buffer A/B、立方体输入或特殊纹理的作品必须迁移全部通道和资源。只复制 Image 通道不等于移植完成;也要检查原作品的授权条件。

继续实验

参考

官方文档

运行相关示例