javascript - 从 Shadertoy 导出到 Three.js

标签 javascript three.js glsl webgl shader

我正在进行编码的第一步。我在网上做了一些类(class),然后我玩了一些 three.js 实验,现在我想继续学习着色器实验。

我找到了 Shadertoy.com,它真的很棒!有很多不同的实验,效果令人难以置信。我正在尝试在 Three.js 中使用这些着色器之一,但并不那么容易。

着色器已经写好了,这是真的。但我不知道如何处理它,我不知道如何使用它。

因为它不仅仅是复制和粘贴代码。我必须写一个关系,可以将这些惊人的效果中的一些应用于 Three.js 几何体。我必须使用制服,但我不知道如何才能知道我可以使用哪些制服,以及如何使用它们。

我开始看到 Shadertoy 中的教程和 Internet 上的一些文章,它看起来非常抽象。我认为在开始理解该语言之前我应该​​学习大量数学。

您有什么建议可以开始吗?

也许比我想象的更简单,我只需复制、粘贴代码并在我的 HTML 文档上进行试验?

最佳答案

Shadertoy 是一个相对复杂的程序。它有音频输入到着色器、视频输入到着色器、从着色器生成音频数据、各种纹理,包括 2d 和立方体贴图。支持所有这些功能并非易事。

也就是说,可以很容易地使用基本着色器,请参见下面的示例。但是 shadertoy 着色器并不是真正设计用作 three.js 中网格的 Material 。

如果您想了解 WebGL 工作的原因和方式,请参阅 http://webglfundamentals.org

const vs = `
attribute vec4 position;
void main()	{
  gl_Position = position;
}
`;
const userShader = `
// FROM: https://www.shadertoy.com/view/4sdXDl
//spikey
#define SHAPE length(z.yz)
//normal
//#define SHAPE length(z.xyz)
//bizarro
//#define SHAPE length(z.yz-z.xx)
//etc...
#define HIGH_QUAL
#ifdef HIGH_QUAL
#define MARCH_STEPS 199
#else
#define MARCH_STEPS 99
#endif
float k=7.0+3.0*sin(iGlobalTime*0.15);
vec3 mcol=vec3(0.0);
void AbsBox(inout vec4 z){//abs box by kali 
 z.xyz=abs(z.xyz+1.0)-1.0; 
 z*=1.5/clamp(dot(z.xyz,z.xyz),0.25,1.0);
}
void Bulb(inout vec4 z, in vec4 c){//mandelBulb by twinbee
 float r = length(z.xyz);
 float zo = asin(z.z / r) * k + iGlobalTime*0.15;
 float zi = atan(z.y, z.x) * 7.0;
 z=pow(r, k-1.0)*vec4(r*vec3(cos(zo)*vec2(cos(zi),sin(zi)),sin(zo)),z.w*k)+c; 
}
float DE(vec3 p){
  vec4 c = vec4(p,1.0),z = c;
  Bulb(z,c);
  float r0=(length(z.xyz)-1.15)/z.w;
  z.xyz-=1.0;
  for(int i=0;i<7;i++)AbsBox(z);
  float r=SHAPE;
  mcol.rgb=vec3(1.0,0.5,0.2)+abs(sin(0.2*r+100.0*z.yxz/z.w));
  return 0.5 * max((r-1.0) / z.w,-r0);
}

vec3 sky(vec3 rd, vec3 L){//modified bananaft's & public_int_i's code
  float d=0.4*dot(rd,L)+0.6;
  //return vec3(d);
  rd.y+=sin(sqrt(clamp(-rd.y,0.0,0.9))*90.0)*0.45*max(-0.1,rd.y);
  rd=abs(rd);
  float y=max(0.,L.y),sun=max(1.-(1.+10.*y+rd.y)*length(rd-L),0.)
    +.3*pow(1.-rd.y,12.)*(1.6-y);
  return d*mix(vec3(0.3984,0.5117,0.7305),vec3(0.7031,0.4687,0.1055),sun)
    *((.5+pow(y,.4))*(1.5-abs(L.y))+pow(sun,5.2)*y*(5.+15.0*y));
}
float rnd;
void randomize(in vec2 p){rnd=fract(float(iFrame)+sin(dot(p,vec2(13.3145,117.7391)))*42317.7654321);}

float ShadAO(in vec3 ro, in vec3 rd){
 float t=0.0,s=1.0,d,mn=0.01;
 for(int i=0;i<12;i++){
  d=max(DE(ro+rd*t)*1.5,mn);
  s=min(s,d/t+t*0.5);
  t+=d;
 }
 return s;
}
vec3 scene(vec3 ro, vec3 rd){
  vec3 L=normalize(vec3(0.4,0.025,0.5));
  vec3 bcol=sky(rd,L);
  vec4 col=vec4(0.0);//color accumulator
  float t=DE(ro)*rnd,d,od=1.0,px=1.0/iResolution.x;
  for(int i=0;i<MARCH_STEPS;i++){
    d=DE(ro);
    if(d<px*t){
      float dif=clamp(1.0-d/od,0.2,1.0);
      vec3 scol=mcol*dif*(1.3-0.3*t);
#ifdef HIGH_QUAL
      	vec2 s=vec2(DE(ro+d*4.0*L),DE(ro+d*16.0*L));
        scol*=clamp(0.5*s.x/d+(s.y/d)/8.0,0.0,1.0);
#endif
      float alpha=(1.0-col.w)*clamp(1.0-d/(px*t),0.0,1.0);
      col+=vec4(clamp(scol,0.0,1.0),1.0)*alpha;
      if(col.w>0.9)break;
    }
    t+=d;ro+=rd*d;od=d;
    if(t>6.0)break;
  }
  col.rgb+=bcol*(1.0-clamp(col.w,0.0,1.0));
  return col.rgb;
}
mat3 lookat(vec3 fw){
 fw=normalize(fw);vec3 rt=normalize(cross(fw,vec3(0.0,1.0,0.0)));return mat3(rt,cross(rt,fw),fw);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
   randomize(fragCoord);
   float tim=iGlobalTime*0.3,r=2.0+cos(tim*0.7);
   vec2 uv=(fragCoord-0.5*iResolution.xy)/iResolution.x;
   vec3 ro=vec3(sin(tim)*r,sin(tim*0.4),cos(tim)*r);
   vec3 rd=lookat(-ro)*normalize(vec3(uv,1.0));
   //rd+=2.0*cross(qrt.xyz,cross(qrt.xyz,rd)+qrt.w*rd);
   fragColor=vec4(scene(ro,rd)*2.0,1.0);
}
`;

// FROM shadertoy.com 
const shadertoyBoilerplate = `
#extension GL_OES_standard_derivatives : enable
//#extension GL_EXT_shader_texture_lod : enable
#ifdef GL_ES
precision highp float;
#endif
uniform vec3      iResolution;
uniform float     iGlobalTime;
uniform float     iChannelTime[4];
uniform vec4      iMouse;
uniform vec4      iDate;
uniform float     iSampleRate;
uniform vec3      iChannelResolution[4];
uniform int       iFrame;
uniform float     iTimeDelta;
uniform float     iFrameRate;
struct Channel
{
    vec3  resolution;
    float time;
};
uniform Channel iChannel[4];
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
void mainImage( out vec4 c,  in vec2 f );

${userShader}

void main( void ){
  vec4 color = vec4(0.0,0.0,0.0,1.0);
  mainImage( color, gl_FragCoord.xy );
  color.w = 1.0;
  gl_FragColor = color;
}
`;

const $ = document.querySelector.bind(document);

const camera = new THREE.Camera();
camera.position.z = 1;

const scene = new THREE.Scene();

const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  -1, -1, 
   1, -1, 
  -1,  1, 
  -1,  1, 
   1, -1, 
   1,  1, 
]);
geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 2 ) );

const uniforms = {
  iGlobalTime: { type: "f", value: 1.0 },
  iResolution: { type: "v3", value: new THREE.Vector3() },
};

const material = new THREE.RawShaderMaterial({
  uniforms: uniforms,
  vertexShader: vs,
  fragmentShader: shadertoyBoilerplate,
});

var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

var renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

resize(true);
render(0);

function resize(force) {
  var canvas = renderer.domElement;
  var dpr    = 1; //window.devicePixelRatio;  // make 1 or less if too slow
  var width  = canvas.clientWidth  * dpr;
  var height = canvas.clientHeight * dpr;
  if (force || width != canvas.width || height != canvas.height) {
    renderer.setSize( width, height, false );
    uniforms.iResolution.value.x = renderer.domElement.width;
    uniforms.iResolution.value.y = renderer.domElement.height;
  }
}

function render(time) {
  resize();
  uniforms.iGlobalTime.value = time * 0.001;
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
canvas {
  border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r74/three.min.js"></script>

上面来自 shadertoy 的代码将 gl_FragCoord 作为输入传递给用户的着色器,它是在 Canvas 中绘制的像素的像素坐标。

对于我们可以传入 UV 坐标的模型,我们只需选择一个分辨率来乘以它们,因为 UV 坐标通常从 0 到 1,而 shadertoy 着色器期望 0 到 canvas.width 和 0 到 canvas。高度

例子:

const vs = `
varying vec2 vUv;

void main()
{
  vUv = uv;
  vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
  gl_Position = projectionMatrix * mvPosition;
}
`;
const userShader = `
// FROM: https://www.shadertoy.com/view/4sdXDl
//spikey
#define SHAPE length(z.yz)
//normal
//#define SHAPE length(z.xyz)
//bizarro
//#define SHAPE length(z.yz-z.xx)
//etc...
#define HIGH_QUAL
#ifdef HIGH_QUAL
#define MARCH_STEPS 199
#else
#define MARCH_STEPS 99
#endif
float k=7.0+3.0*sin(iGlobalTime*0.15);
vec3 mcol=vec3(0.0);
void AbsBox(inout vec4 z){//abs box by kali 
 z.xyz=abs(z.xyz+1.0)-1.0; 
 z*=1.5/clamp(dot(z.xyz,z.xyz),0.25,1.0);
}
void Bulb(inout vec4 z, in vec4 c){//mandelBulb by twinbee
 float r = length(z.xyz);
 float zo = asin(z.z / r) * k + iGlobalTime*0.15;
 float zi = atan(z.y, z.x) * 7.0;
 z=pow(r, k-1.0)*vec4(r*vec3(cos(zo)*vec2(cos(zi),sin(zi)),sin(zo)),z.w*k)+c; 
}
float DE(vec3 p){
  vec4 c = vec4(p,1.0),z = c;
  Bulb(z,c);
  float r0=(length(z.xyz)-1.15)/z.w;
  z.xyz-=1.0;
  for(int i=0;i<7;i++)AbsBox(z);
  float r=SHAPE;
  mcol.rgb=vec3(1.0,0.5,0.2)+abs(sin(0.2*r+100.0*z.yxz/z.w));
  return 0.5 * max((r-1.0) / z.w,-r0);
}

vec3 sky(vec3 rd, vec3 L){//modified bananaft's & public_int_i's code
  float d=0.4*dot(rd,L)+0.6;
  //return vec3(d);
  rd.y+=sin(sqrt(clamp(-rd.y,0.0,0.9))*90.0)*0.45*max(-0.1,rd.y);
  rd=abs(rd);
  float y=max(0.,L.y),sun=max(1.-(1.+10.*y+rd.y)*length(rd-L),0.)
    +.3*pow(1.-rd.y,12.)*(1.6-y);
  return d*mix(vec3(0.3984,0.5117,0.7305),vec3(0.7031,0.4687,0.1055),sun)
    *((.5+pow(y,.4))*(1.5-abs(L.y))+pow(sun,5.2)*y*(5.+15.0*y));
}
float rnd;
void randomize(in vec2 p){rnd=fract(float(iFrame)+sin(dot(p,vec2(13.3145,117.7391)))*42317.7654321);}

float ShadAO(in vec3 ro, in vec3 rd){
 float t=0.0,s=1.0,d,mn=0.01;
 for(int i=0;i<12;i++){
  d=max(DE(ro+rd*t)*1.5,mn);
  s=min(s,d/t+t*0.5);
  t+=d;
 }
 return s;
}
vec3 scene(vec3 ro, vec3 rd){
  vec3 L=normalize(vec3(0.4,0.025,0.5));
  vec3 bcol=sky(rd,L);
  vec4 col=vec4(0.0);//color accumulator
  float t=DE(ro)*rnd,d,od=1.0,px=1.0/iResolution.x;
  for(int i=0;i<MARCH_STEPS;i++){
    d=DE(ro);
    if(d<px*t){
      float dif=clamp(1.0-d/od,0.2,1.0);
      vec3 scol=mcol*dif*(1.3-0.3*t);
#ifdef HIGH_QUAL
      	vec2 s=vec2(DE(ro+d*4.0*L),DE(ro+d*16.0*L));
        scol*=clamp(0.5*s.x/d+(s.y/d)/8.0,0.0,1.0);
#endif
      float alpha=(1.0-col.w)*clamp(1.0-d/(px*t),0.0,1.0);
      col+=vec4(clamp(scol,0.0,1.0),1.0)*alpha;
      if(col.w>0.9)break;
    }
    t+=d;ro+=rd*d;od=d;
    if(t>6.0)break;
  }
  col.rgb+=bcol*(1.0-clamp(col.w,0.0,1.0));
  return col.rgb;
}
mat3 lookat(vec3 fw){
 fw=normalize(fw);vec3 rt=normalize(cross(fw,vec3(0.0,1.0,0.0)));return mat3(rt,cross(rt,fw),fw);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
   randomize(fragCoord);
   float tim=iGlobalTime*0.3,r=2.0+cos(tim*0.7);
   vec2 uv=(fragCoord-0.5*iResolution.xy)/iResolution.x;
   vec3 ro=vec3(sin(tim)*r,sin(tim*0.4),cos(tim)*r);
   vec3 rd=lookat(-ro)*normalize(vec3(uv,1.0));
   //rd+=2.0*cross(qrt.xyz,cross(qrt.xyz,rd)+qrt.w*rd);
   fragColor=vec4(scene(ro,rd)*2.0,1.0);
}
`;

// FROM shadertoy.com 
const shadertoyBoilerplate = `
#extension GL_OES_standard_derivatives : enable
//#extension GL_EXT_shader_texture_lod : enable
#ifdef GL_ES
precision highp float;
#endif
uniform vec3      iResolution;
uniform float     iGlobalTime;
uniform float     iChannelTime[4];
uniform vec4      iMouse;
uniform vec4      iDate;
uniform float     iSampleRate;
uniform vec3      iChannelResolution[4];
uniform int       iFrame;
uniform float     iTimeDelta;
uniform float     iFrameRate;
struct Channel
{
    vec3  resolution;
    float time;
};
uniform Channel iChannel[4];
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;

varying vec2 vUv;

void mainImage( out vec4 c,  in vec2 f );

${userShader}

void main( void ){
  vec4 color = vec4(0.0,0.0,0.0,1.0);
  mainImage( color, vUv * iResolution.xy );
  color.w = 1.0;
  gl_FragColor = color;
}
`;

const $ = document.querySelector.bind(document);

const fieldOfView = 45;
const zNear = .1;
const zFar  = 100;
const camera = new THREE.PerspectiveCamera(fieldOfView, 1, zNear, zFar);
camera.position.z = 3;

const scene = new THREE.Scene();

const geometry = new THREE.BoxGeometry(1, 1, 1);

const uniforms = {
  iGlobalTime: { type: "f", value: 1.0 },
  iResolution: { type: "v3", value: new THREE.Vector3() },
};

// choose a resolution to pass to the shader
uniforms.iResolution.value.x = 100;
uniforms.iResolution.value.y = 100;

const material = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: vs,
  fragmentShader: shadertoyBoilerplate,
});

const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

var renderer = new THREE.WebGLRenderer();
document.body.appendChild(renderer.domElement);

resize(true);
render(0);

function resize(force) {
  const canvas = renderer.domElement;
  const dpr    = 1; //window.devicePixelRatio;  // make 1 or less if too slow
  const width  = canvas.clientWidth  * dpr;
  const height = canvas.clientHeight * dpr;
  if (force || width != canvas.width || height != canvas.height) {
    renderer.setSize( width, height, false );
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
  }
}

function render(time) {
  time *= 0.001;  // seconds
  
  resize();
  
  uniforms.iGlobalTime.value = time;
  mesh.rotation.x = time * 0.5;
  mesh.rotation.y = time * 0.6;
  
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.min.js"></script>

请注意,shadertoy 着色器通常不设计为用作 Material 。它们效率不高,更像是一种有趣的事件,即“仅使用时间和像素位置作为输入,我可以制作出多么酷的图像”。因此,虽然结果可能令人惊叹,但它们通常比传统 Material 技术(使用纹理)慢 10 倍或 100 倍甚至 1000 倍

比较例如this amazing shader它绘制了整个城市,但至少在我的机器上它在小窗口中以 10-18fps 的速度运行,在全屏时以 1fps 的速度运行。 VS 例如侠盗猎车手 5,它也显示了整个城市,但在同一台机器上全屏时设法以 30-60fps 的速度运行。

在 shadertoy.com 上有很多乐趣和很多有趣的技术可以学习,它们可能对您的着色器有用,但不要误认为“生产”技术。它被称为 shaderTOY 是有原因的 😉

关于javascript - 从 Shadertoy 导出到 Three.js,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36983769/

相关文章:

Three.js - 光晕效果的着色器代码,法线需要转换

javascript - 计算 div 内填充输入的百分比

javascript - 用另一个函数修改一个函数的私有(private)变量

javascript - iOS 11 - 独立的网络应用程序链接打开默认反转? (处理 Web 应用程序中的 PDF 显示。)

java - 如何在LWJGL中将 float 组传递给Shader

c++ - 警告 : Variable cannot be bound (it either doesn't exist or has been optimized away)

javascript - Simulate external stroke::before 伪元素:透明文本的问题

javascript - 有没有办法防止自阴影内的面接收光?

3d - 旋转,然后翻译成three.js

javascript - 如何使用 pytherejs 导入外部几何图形