ShaderToy着色器移植到Three.js全过程记录

这篇具有很好参考价值的文章主要介绍了ShaderToy着色器移植到Three.js全过程记录。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

推荐:用 NSDT设计器 快速搭建可编程3D场景。

作为 Publicis Pixelpark Innovationlab 研究的一部分,我们研究了如何将较低底层的语言用于网络技术。 显而易见的选择似乎是 asm.js 或 WebAssembly。

但你也可以使用 WebGL 着色器来解决面向机器的问题。 着色器使用类似于 C/C++ 的语言进行编程,虽然它们主要不是为了解决一般问题,但它们的用途不仅仅是渲染图像和 3D 场景。

第二个动机源于使用着色器可以实现的美观性。 2002 年,威斯康星大学麦迪逊分校的一群学生发布了 NPRQuake(“Non-PhotoRealistic Rendering Quake”),这是著名游戏 Quake 的变体,通过将代码注入到渲染管道中。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

这种变化的美学品质令人惊叹。 我们立即意识到这些效果可能会改变项目的游戏规则。 在 2002 年,这种变化只能通过编写 OpenGL 驱动程序来实现,而现在在 2018 年可以通过着色器来实现——甚至在 Web 浏览器中也是如此。

因此,当我们最近参与一个艺术项目时,我们决定尝试一下着色器。

1、着色器代码的可用性

如果你不太习惯对着色器进行编程,那么显而易见的选择是搜索免费提供的示例并使用它们(仔细查看所涉及的许可证)。 在这方面脱颖而出的一个库是 Shadertoy,另一个例子是 ShaderFrog。

在我们决定在 ThreeJS 中使用 Shadertoy 的后处理着色器发布我们的发现之前,我们已经成功地使用 ThreeJS 了。

2、ThreeJS 中的着色器

ThreeJS 可用于利用后处理着色器(可改变整个渲染图像)以及材质着色器(可改变 3D 对象的材质)。 两种类型都需要顶点和片段着色器部分; 顶点着色器可以更改 3D 中顶点的位置,而片段着色器通常会替换渲染图像的颜色。

该图显示了四种可能的变化。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

在左上角,后处理着色器向渲染图像添加颜色渐变。 在它的右侧,顶点着色器减少了渲染区域。 底部的两个图像显示材质着色器; 左边的仅改变颜色,右边的改变顶点的位置。 由于着色器始终由顶点部分和片段部分组成,因此最后一个示例也会更改颜色。

3、Shadertoy 的简单示例

早在 2014 年,我们就研究了如何将着色器从 Shadertoy 转移到 ThreeJS,第一个结果发布在 StackOverflow 上。 我们发现以下模式很有用:

  • 添加 ShaderToy 特定变量,如 iGlobalTime 等。
  • 将 mainImage(out vec4 z, in vec2 w) 重命名为 main()
  • 将 z 重命名为 gl_FragColor

通过遵循此模式,可以将简单的着色器传输到 ThreeJS。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

4、来自 Shadertoy 的复杂示例

对于更复杂的着色器,你需要做更多的事情,正如我们现在将概述的那样。 作为一个重要的示例,我们选择了 candycat 的 Noise Contour,因为会遇到一些问题。 可以在这里找到它。

此示例还使用着色器语言创建整个场景。 但在 ThreeJS 中,你通常希望控制 3D 对象,因此我们决定在 ThreeJS 中创建场景,同时仍然利用着色器来更改它。

5、了解着色器的结构

我们首先尝试了解着色器的结构; 这可以通过 Shadertoy 的编辑器来实现。 由于可以实时看到对代码的编辑,因此我们可以进行一些小的更改来了解它的工作原理。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

在实际代码下面,我们看到该代码基于名为 iChannel0 的通道,其中 B 表示缓冲区。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

要查看此缓冲区的实际效果,我们注释掉第 37 行并添加以下内容:

// fragColor = mix(EdgeColor, mCol, edge);
fragColor = texture(iChannel0, uv);

结果应该是:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

这个简单的更改会导致显示前一个缓冲区的颜色,而不是该缓冲区的结果。

通过检查前一个缓冲区(Buf B),我们发现这个缓冲区也使用 iChannel0,因此我们仍然没有查看原始场景创建代码。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

使用与之前相同的技巧,我们注释掉第 29 行并添加一行计算 uv 和实际颜色,如下所示:

// fragColor = vec4(edge, sample0.w, 1.0, 1.0);
fragColor = texture(iChannel0, fragCoord / iResolution.xy);

这应该给我们留下:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

这看起来更像是一个普通的场景。 此外,Buf A 不使用其他缓冲区,因此我们正在查看原始场景创建代码。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

6、在ThreeJS 中重构

这里完全免责声明:接下来的代码绝不是“最佳”代码,而只是以最直接的方式解决问题的一种方法。

6.1 创建场景

我们首先创建一个稍微简单的场景,只有一个球体和一个平面。 此外,我们想使用 ThreeJS 中的 MeshNormalMaterial。

此处显示了可能的结果:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

该代码包含在名为 index.html 的 HTML 文件中:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ThreeJS Shader Experiment 1 - Step 0</title>
<style>
    html,
    body {
        height: 100%;
        width: 100%;
        margin: 0;
        padding: 0;
        overflow: hidden;
    }
</style>
</head>
<body>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/dev/build/three.min.js -->
    <script src="three.min.js"></script>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/js/controls/OrbitControls.js -->
    <script src="OrbitControls.js"></script>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/js/shaders/CopyShader.js -->
    <script src="CopyShader.js"></script>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/js/postprocessing/EffectComposer.js -->
    <script src="EffectComposer.js"></script>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/js/postprocessing/ShaderPass.js -->
    <script src="ShaderPass.js"></script>
    <!-- https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/js/postprocessing/RenderPass.js -->
    <script src="RenderPass.js"></script>
    <script src="index.js"></script>
</body>
</html>

我们需要处理对 ThreeJS 库的依赖关系,并且还在 index.js 中添加我们自己的代码:

const container = document.body;
const FOV = 45;
const NEAR = 0.1;
const FAR = 1000;
let height = container.clientHeight;
let width = container.clientWidth;
const ASPECT = width / height;

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio || 1);
renderer.setClearColor(0x000000);

const canvas = renderer.domElement;

const scene = new THREE.Scene();

const camera = new THREE.PerspectiveCamera(FOV, ASPECT, NEAR, FAR);
camera.position.set(-2, 2, 2);
camera.target = new THREE.Vector3(0, 0, 0);

const controls = new THREE.OrbitControls(camera, canvas);

const matNormal = new THREE.MeshNormalMaterial();

const floorGeo = new THREE.PlaneBufferGeometry(2.0, 2.0);
const floor = new THREE.Mesh(floorGeo, matNormal);
floor.position.set(0, -0.5, 0);
floor.rotation.x = -((Math.PI * 90) / 180);

const sphereGeo = new THREE.SphereBufferGeometry(0.5, 32, 32);
const sphere = new THREE.Mesh(sphereGeo, matNormal);

scene.add(floor);
scene.add(sphere);
scene.add(camera);

const resize = (width, height) => {
    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize(width, height);
};

const render = () => {
    const tmpHeight = container.clientHeight;
    const tmpWidth = container.clientWidth;
    if (tmpHeight !== height || tmpWidth !== width) {
        height = tmpHeight;
        width = tmpWidth;
        resize(width, height);
    }

    controls.update();

    renderer.render(scene, camera);

    requestAnimationFrame(render);
};



container.appendChild(canvas);
resize(width, height);
render();

这段 JavaScript 代码创建了一个渲染器、一个相机、一个轨道控件以及带有 MeshNormalMaterial 的平面和球体。 它还负责窗口大小的更改和渲染。

从 Shadertoy 移植场景的第 0 步到此结束。

ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

6.2 重新创建第一个着色器通道

在下一步中,我们尝试在缓冲区中重新创建第一个着色器渲染步骤; 这基本上是将着色器代码复制到 ThreeJS。

这应该是结果:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

为了实现这一目标,我们使用了 ThreeJS 的 EffectComposer,它提供了一种使用后处理着色器的简单方法。

// ...
scene.add(sphere);
scene.add(camera);

const drawShader = {
uniforms: {
tDiffuse: { type: 't', value: null },
},
vertexShader: VERTEX,
fragmentShader: FRAGMENT,
};

const composer = new THREE.EffectComposer(renderer);
composer.addPass(new THREE.RenderPass(scene, camera));

const pass = new THREE.ShaderPass(drawShader);
pass.renderToScreen = true;
composer.addPass(pass);


const resize = (width, height) => {
    camera.aspect = width / height;
// ...

这将创建一个 EffectComposer 实例,其中添加一个普通渲染通道和一个附加着色器通道。 我们将着色器代码复制到变量 VERTEX 和 FRAGMENT 中。 着色器定义还定义了 EffectComposer 使用的称为 tDiffuse 的 Uniform。 它包含来自上一个渲染通道的图像,该图像将在当前通道中更改。

通过这个新的渲染步骤,我们将显示此通道而不是原始场景。 因此我们需要添加一些代码来调整大小,因此我们添加:

const resize = (width, height) => {
camera.aspect = width / height;
camera.updateProjectionMatrix();

composer.setSize(width, height);

renderer.setSize(width, height);
};


const render = () => {
const tmpHeight = container.clientHeight;
const tmpWidth = container.clientWidth;
if (tmpHeight !== height || tmpWidth !== width) {
height = tmpHeight;
width = tmpWidth;
resize(width, height);
}

controls.update();

// renderer.render(scene, camera);
composer.render();

requestAnimationFrame(render);
};

现在我们需要定义常量 VERTEX 和 FRAGMENT。 我们不能使用Shadertoy的顶点着色器,所以我们需要定义自己的:

const VERTEX = `
varying vec2 vUv;
void main() {
    vec4 mvPosition = modelViewMatrix * vec4(position, 1.);
    gl_Position = projectionMatrix * mvPosition;
    vUv = uv;
}

不过,我们确实使用了 Shadertoy 中的片段着色器,并将其添加到 FRAGMENT 中:

const FRAGMENT = `
// Edge detection Pass
#define Sensitivity (vec2(0.3, 1.5) * iResolution.y / 400.0)
float checkSame(vec4 center, vec4 samplef)
{
    vec2 centerNormal = center.xy;
    float centerDepth = center.z;
    vec2 sampleNormal = samplef.xy;
    float sampleDepth = samplef.z;
    vec2 diffNormal = abs(centerNormal - sampleNormal) * Sensitivity.x;
    bool isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
    float diffDepth = abs(centerDepth - sampleDepth) * Sensitivity.y;
    bool isSameDepth = diffDepth < 0.1;
    return (isSameNormal && isSameDepth) ? 1.0 : 0.0;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec4 sample0 = texture(iChannel0, fragCoord / iResolution.xy);
    vec4 sample1 = texture(iChannel0, (fragCoord + vec2(1.0, 1.0)) / iResolution.xy);
    vec4 sample2 = texture(iChannel0, (fragCoord + vec2(-1.0, -1.0)) / iResolution.xy);
    vec4 sample3 = texture(iChannel0, (fragCoord + vec2(-1.0, 1.0)) / iResolution.xy);
    vec4 sample4 = texture(iChannel0, (fragCoord + vec2(1.0, -1.0)) / iResolution.xy);
    float edge = checkSame(sample1, sample2) * checkSame(sample3, sample4);
    fragColor = vec4(edge, sample0.w, 1.0, 1.0);
}
`;

这基本上创建了着色器,但我们仍然需要解决以下问题:

顶点着色器坐标尚未在片段着色器中使用

  • 片段着色器使用当前 WebGL 上下文中未知的纹理
  • mainImage 必须重命名为 main
  • iResolution 尚未设置。

所以着色器还没有工作。

解决第一个问题会产生以下定义:

const FRAGMENT = `
// Edge detection Pass
varying vec2 vUv;
// ...

现在我们可以使用向量 vUv 代替 fragCoord / iResolution.xy。 这导致:

// ...
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec4 sample0 = texture(tDiffuse, vUv);
vec4 sample1 = texture(tDiffuse, vUv + (vec2(1.0, 1.0) / iResolution.xy));
vec4 sample2 = texture(tDiffuse, vUv + (vec2(-1.0, -1.0) / iResolution.xy));
vec4 sample3 = texture(tDiffuse, vUv + (vec2(-1.0, 1.0) / iResolution.xy));
vec4 sample4 = texture(tDiffuse, vUv + (vec2(1.0, -1.0) / iResolution.xy));
// ...

现在我们只需用texture2D 替换所有出现的纹理。

另外,我们将 mainImage 更改为不带参数的 main:

// void mainImage( out vec4 fragColor, in vec2 fragCoord ) {

void main () {
// ...

main 还应该返回 gl_FragColor 而不是 fragColor,它定义了着色器中的颜色。

void main () {
// ...
gl_FragColor = vec4(edge, sample0.w, 1.0, 1.0);
}

最后,我们需要通过将 iResolution 添加到uniforms来设置它。 我们通过定义一个存储宽度和高度的 ThreeJS 向量来做到这一点:

const resolution = new THREE.Vector2(width, height);

现在我们可以将分辨率添加到uniforms中:

const drawShader = {
    uniforms: {
        tDiffuse: { type: 't', value: null },
        iResolution: { type: 'v2', value: resolution },
    },
    vertexShader: VERTEX,
    fragmentShader: FRAGMENT,
};

我们需要增强调整大小功能:

const resize = () => {
// ...
pass.uniforms.iResolution.value.set(width, height);
};

重要的是我们使用实际渲染通道的uniforms。 原版已经被EffectComposer深度克隆; 更改变量分辨率不会产生任何影响。

由于我们确实定义了两个uniform,因此我们需要将它们引入片段着色器,因此我们定义它们:

const FRAGMENT = `
uniform sampler2D tDiffuse;
uniform vec2 iResolution;
// ...

这个着色器通道到此结束,如果一切顺利,我们会看到以下内容:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

从蓝线我们看到它通常可以工作,但粉红色的部分仍然缺失。 让我们改变这一点。

6.3 解决阴影问题

粉色部分缺失,因为 Shadertoy 中的着色器秘密地将阴影渲染到一开始不可见的 Alpha 通道,如下图所示:

ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

有多种方法可以解决这个问题 - 我们使用直接的方法,添加一种可以容纳阴影的材质。 这些必须在额外的渲染通道中处理。

让我们在 ThreeJS 中创建阴影:

// ...
renderer.shadowMap.enabled = true;
renderer.shadowMap.renderReverseSided = false;

// ...
floor.receiveShadow = true;

// ...
sphere.castShadow = true;
sphere.receiveShadow = true;

阴影需要光,在本例中是定向光:

const SHADOW_MAP_SIZE = 1024;

const directionalLight = new THREE.DirectionalLight( 0xffffff, 1.5 );
directionalLight.position.set( -1, 1.75, 1 );
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = SHADOW_MAP_SIZE;
directionalLight.shadow.mapSize.height = SHADOW_MAP_SIZE;
directionalLight.shadow.camera.far = 3500;
directionalLight.shadow.bias = -0.0001;

scene.add(directionalLight);

MeshPhongMaterial 可以容纳阴影。

const matShadow = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    shininess: 0.0,
});

而新的渲染目标会保存它们。

const PARAMETERS = {
    minFilter: THREE.LinearFilter,
    magFilter: THREE.LinearFilter,
    format: THREE.RGBFormat,
    stencilBuffer: false
};

const shadowBuffer = new THREE.WebGLRenderTarget(1, 1, PARAMETERS);

同样,需要一个调整大小的函数:

shadowBuffer.setSize(width, height);

现在我们可以将阴影传输到新的渲染目标并为着色器做好准备:

const render () => {
    const tmpHeight = container.clientHeight;
    const tmpWidth = container.clientWidth;
    if (tmpHeight !== height || tmpWidth !== width) {
        height = tmpHeight;
        width = tmpWidth;
        resize(width, height);
    }
    controls.update();

    floor.material = matShadow;
    sphere.material = matShadow;
    renderer.render(scene, camera, shadowBuffer);
    pass.uniforms.tShadow.value = shadowBuffer.texture;

    floor.material = matNormal;
    sphere.material = matNormal;

    composer.render();

    requestAnimationFrame(render);
}

这些行设置材质、渲染场景、将阴影设置为统一并将材质更改回 MeshNormalMaterial。

现在着色器需要了解阴影才能处理它们,因此我们更改uniforms:

const drawShader = {
    uniforms: {
        tDiffuse: { type: 't', value: null },
        tShadow: { type: 't', value: null },
        iResolution: { type: 'v2', value: resolution },
    },
    vertexShader: VERTEX,
    fragmentShader: FRAGMENT,
};

片段着色器也是如此:

const FRAGMENT = `
uniform sampler2D tDiffuse;
uniform sampler2D tShadow;
uniform vec2 iResolution;
varying vec2 vUv;
//...

然后我们用阴影替换前一行。

// gl_FragColor = vec4(edge, sample0.w, 1.0, 1.0);
float shadow = texture2D(tShadow, vUv).x;
gl_FragColor = vec4(edge, shadow, 1.0, 1.0);

结果应该类似于 Shadertoy 上的第二步。
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

现在我们只差第二个着色器通道来完成此操作。

6.4 最终的着色器通道

对于最终的着色器通道,我们添加另一个 EffectComposer 实例。

让我们定义另一个着色器:

const FRAGMENT_FINAL = `
#define EdgeColor vec4(0.2, 0.2, 0.15, 1.0)
#define BackgroundColor vec4(1,0.95,0.85,1)
#define NoiseAmount 0.01
#define ErrorPeriod 30.0
#define ErrorRange 0.003
// Reference: https://www.shadertoy.com/view/MsSGD1
float triangle(float x)
{
    return abs(1.0 - mod(abs(x), 2.0)) * 2.0 - 1.0;
}
float rand(float x)
{
    return fract(sin(x) * 43758.5453);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    float time = floor(iTime * 16.0) / 16.0;
    vec2 uv = fragCoord.xy / iResolution.xy;
    uv += vec2(triangle(uv.y * rand(time) * 1.0) * rand(time * 1.9) * 0.005,
    triangle(uv.x * rand(time * 3.4) * 1.0) * rand(time * 2.1) * 0.005);
    float noise = (texture(iChannel1, uv * 0.5).r - 0.5) * NoiseAmount;
    vec2 uvs[3];
    uvs[0] = uv + vec2(ErrorRange * sin(ErrorPeriod * uv.y + 0.0) + noise, ErrorRange * sin(ErrorPeriod * uv.x + 0.0) + noise);
    uvs[1] = uv + vec2(ErrorRange * sin(ErrorPeriod * uv.y + 1.047) + noise, ErrorRange * sin(ErrorPeriod * uv.x + 3.142) + noise);
    uvs[2] = uv + vec2(ErrorRange * sin(ErrorPeriod * uv.y + 2.094) + noise, ErrorRange * sin(ErrorPeriod * uv.x + 1.571) + noise);
    float edge = texture(iChannel0, uvs[0]).r * texture(iChannel0, uvs[1]).r * texture(iChannel0, uvs[2]).r;
    float diffuse = texture(iChannel0, uv).g;
    float w = fwidth(diffuse) * 2.0;
    vec4 mCol = mix(BackgroundColor * 0.5, BackgroundColor, mix(0.0, 1.0, smoothstep(-w, w, diffuse - 0.3)));
    fragColor = mix(EdgeColor, mCol, edge);
    fragColor = texture(iChannel0, uv);
    //fragColor = vec4(diffuse);
}`;

const finalShader = {
    uniforms: {
        tDiffuse: { type: 't', value: null},
    },
    vertexShader: VERTEX,
    fragmentShader: FRAGMENT_FINAL
};

const passFinal = new THREE.ShaderPass(finalShader);
passFinal.renderToScreen = true;
composer.addPass(passFinal);

我们停用前一个渲染通道的 renderToScreen:

const pass = new THREE.ShaderPass(drawShader);
// REMOVED FOR FINAL SHADER pass.renderToScreen = true;
composer.addPass(pass);

再次,引入更多变量; 随着时间的推移改变变量的时间和通道 1 添加噪声的时间。

ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

我们为 iTime 使用 ThreeJS 时钟。

const clock = new THREE.Clock();

每次更改时,我们也会更新 iTime:

const render () => {
// ...
const elapsed = clock.getElapsedTime();
passFinal.uniforms.iTime.value = elapsed;

composer.render();

// ....
}

我们在uniforms中添加 iTime 和噪音:

const finalShader = {
    uniforms: {
        tDiffuse: { type: 't', value: null},
        iTime: { type: 'f', value: 0.0},
        tNoise: { type: 't', value: new THREE.TextureLoader().load('noise.png')}
    },
    vertexShader: VERTEX,
    fragmentShader: FRAGMENT_FINAL
};

噪声只是一种噪声纹理(例如来自 Shadertoy 的纹理),我们使用 ThreeJS 将其加载到 tNoise 中。

现在我们需要使片段着色器适应新变量,因此我们应用以下措施:

  • 将 mainImage 更改为 main
  • 定义uniform并调整变量
  • 定义 vUv 坐标
  • 将返回结果改为gl_FragColor
  • 用texture2D替换纹理

这给了我们:

const FRAGMENT_FINAL = `
uniform sampler2D tDiffuse;
uniform sampler2D tNoise;
uniform float iTime;
varying vec2 vUv;
#define EdgeColor vec4(0.2, 0.2, 0.15, 1.0)
#define BackgroundColor vec4(1,0.95,0.85,1)
#define NoiseAmount 0.01
#define ErrorPeriod 30.0
#define ErrorRange 0.003
// Reference: https://www.shadertoy.com/view/MsSGD1
float triangle(float x)
{
    return abs(1.0 - mod(abs(x), 2.0)) * 2.0 - 1.0;
}
float rand(float x)
{
    return fract(sin(x) * 43758.5453);
}
void main()
{
    float time = floor(iTime * 16.0) / 16.0;
    vec2 uv = vUv;
    uv += vec2(triangle(uv.y * rand(time) * 1.0) * rand(time * 1.9) * 0.005,
    triangle(uv.x * rand(time * 3.4) * 1.0) * rand(time * 2.1) * 0.005);
    float noise = (texture2D(tNoise, uv * 0.5).r - 0.5) * NoiseAmount;
    vec2 uvs[3];
    uvs[0] = uv + vec2(ErrorRange * sin(ErrorPeriod * uv.y + 0.0) + noise, ErrorRange * sin(ErrorPeriod * uv.x + 0.0) + noise);
    uvs[1] = uv + vec2(ErrorRange * sin(ErrorPeriod * uv.y + 1.047) + noise, ErrorRange * sin(ErrorPeriod * uv.x + 3.142) + noise);
    uvs[2] = uv + vec2(ErrorRange * sin(ErrorPeriod * uv.y + 2.094) + noise, ErrorRange * sin(ErrorPeriod * uv.x + 1.571) + noise);
    float edge = texture2D(tDiffuse, uvs[0]).r * texture2D(tDiffuse, uvs[1]).r * texture2D(tDiffuse, uvs[2]).r;
    float diffuse = texture2D(tDiffuse, uv).g;
    float w = fwidth(diffuse) * 2.0;
    vec4 mCol = mix(BackgroundColor * 0.5, BackgroundColor, mix(0.0, 1.0, smoothstep(-w, w, diffuse - 0.3)));
    gl_FragColor = mix(EdgeColor, mCol, edge);
}
`;

在这些更改之后,着色器仍然无法编译,因为该着色器需要特定的 WebGL 扩展。 值得庆幸的是,这很容易在 ThreeJS 中添加:

const passFinal = new THREE.ShaderPass(finalShader);
passFinal.renderToScreen = true;
passFinal.material.extensions.derivatives = true;
composer.addPass(passFinal);

这给了我们以下结果:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

这与原始 Shadertoy 非常接近:
ShaderToy着色器移植到Three.js全过程记录,着色器,javascript,开发语言

7、结束语

我们通过以下步骤成功地将复杂的 Shadertoy 着色器转移到 ThreeJS:

  • 了解具体shader的结构
  • 实施着色器通道
  • 解决可能的 GLSL 不兼容问题
  • 创建可选的着色器通道和/或材质
  • 激活可选扩展

我们预计,随着 ThreeJS 中即将推出的 WebGL2 支持,这些挑战将得到缓解,因为可能的 GLSL 不兼容性应该会消失。

完整的源代码在这里。


原文链接:ShaderToy移植到Three.js — BimAnt文章来源地址https://www.toymoban.com/news/detail-636440.html

到了这里,关于ShaderToy着色器移植到Three.js全过程记录的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 记录在linux上使用科大讯飞的语音识别的全过程

    记录在linux上使用科大讯飞的语音识别的全过程

            1.网址讯飞开放平台-以语音交互为核心的人工智能开放平台 (xfyun.cn)。         2.登录上述网站。         3.点击控制台,创建应用。         4.再在网页中找到语音听写功能,就是本次需要使用到的语音转文字功能,有很多入口可以进入,进入后请自行查看相关介

    2024年04月13日
    浏览(6)
  • 记录一次云原生线上服务数据迁移全过程

    记录一次云原生线上服务数据迁移全过程

    校园 e 站,一群大学生在毕业前夕,为打破信息差而开发的一个校园论坛。一个从零到一全靠一群大学生的满腔热忱而打造的一个前后端分离以小程序为最终展示载体的一个微服务架构体系的 App。并发量的初始定位为 w 级,使用到多级缓存、数据分库等等前沿技术,当然这也

    2024年02月04日
    浏览(10)
  • (小白全过程记录)Ubuntu下伪分布式Hadoop环境搭建

    (小白全过程记录)Ubuntu下伪分布式Hadoop环境搭建

    目录 0.准备 1.Hadoop伪分布式环境搭建 2.安装ssh,配置ssh无密码登录 3.通过拖拽的方式将文件从windows传到linux桌面 5.安装hadoop 6.修改hadoop环境变量 7.修改配置文件 core-site.xml 8.修改配置文件hdfs-site.xml文件 9.执行NameNode的格式化 10.开启NameNode和DataNode守护进程 11.访问web页面 12.关闭

    2024年02月03日
    浏览(18)
  • 记录--『uni-app、小程序』蓝牙连接、读写数据全过程

    记录--『uni-app、小程序』蓝牙连接、读写数据全过程

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 这是一次真实的 蓝牙收发数据 的全过程讲解。 本文使用 uni-app + Vue3 的方式进行开发,以手机app的方式运行(微信小程序同样可行)。 uni-app 提供了 蓝牙 和 低功耗蓝牙 的 api ,和微信小程序提供的 api 是一样

    2024年01月21日
    浏览(17)
  • Python Apex YOLO V5 6.2 目标检测 全过程记录

    Python Apex YOLO V5 6.2 目标检测 全过程记录

    博文目录 效果展示 Python YOLO V5 实时截屏与目标检测 GitHub Windows Python PyCharm 开发环境搭建 Windows Python PyTorch CUDA 11.7 TensorRT 环境配置 先根据上述两篇文章将开发环境和虚拟环境都创建好, 然后下载 YOLO V5 6.2 或 YOLO V5 7.0 (最新) 的源码, 用 PyCharm 打开, 选择刚刚创建的虚拟环境 W

    2024年02月03日
    浏览(43)
  • 研一小白记录第一次在实验室服务器上跑深度学习的代码全过程(实验室服务器上跑代码详细全过程哦)

    研一小白记录第一次在实验室服务器上跑深度学习的代码全过程(实验室服务器上跑代码详细全过程哦)

    你在服务器上跑过代码吗?哇~你跑过!是啥样的...每回见别人跑都会问并且羡慕会在大服务器上跑代码的哈哈哈在研究生刚开学前还甚至不知道什么是服务器,更是无法想象在除了自己能看得见摸得着的电脑屏幕之外跑代码的样子。直到有天开会自己坐在了一个大大的“黑箱

    2024年02月02日
    浏览(14)
  • 在CentOS上用yum方式安装MySQL8真实全过程记录(顺利版本)

    在CentOS上用yum方式安装MySQL8真实全过程记录(顺利版本)

    此文参考我前面的文章《在CentOS上用yum方式安装MySQL8过程记录》,之前比较曲折,现在再安装一台mysql。 因为之前很多坑已经走过,加上这台Linux之前没安装过MYSQL,所以整个过程算是非常顺利。 安装环境:centos7 mysql版本:8.0.35 (为方便理解,以下为完整过程 ) 然后直接使

    2024年02月07日
    浏览(15)
  • 【基于Ubuntu20.04的Autoware.universe安装过程】方案二:双系统 | 详细记录 | 全过程图文 by.Akaxi

    【基于Ubuntu20.04的Autoware.universe安装过程】方案二:双系统 | 详细记录 | 全过程图文 by.Akaxi

    目录 一、Autoware.universe背景 Part-1:安装双系统教程 二、查看Windows引导方式 三、制作安装盘 四、设置电脑配置 1.关闭bitlocker 2.压缩硬盘分区 3.关闭Secure Boot 4.关闭intel RST 5.BIOS设置U盘引导 五、安装Ubuntu20.04 1.ventoy引导 2.安装配置 3.安装类型 4.完成安装 Part-2:在Ubuntu20.04上安装

    2024年04月11日
    浏览(17)
  • 【基于Ubuntu20.04的Autoware.universe安装过程】方案三:Docker | 详细记录 | 全过程图文 by.Akaxi

    【基于Ubuntu20.04的Autoware.universe安装过程】方案三:Docker | 详细记录 | 全过程图文 by.Akaxi

    目录 一、Autoware.universe背景 二、安装说明 三、安装git 四、克隆autoware 五、安装cuda 六、安装Docker软件 七、安装Nvidia Container Toolkit 八、安装Rocker 九、拷贝Autoware的Docker镜像 十、创建autoware_map文件夹 十一、启动docker 十二、源码拷贝 十三、安装依赖 十四、编译Autoware.universe 十

    2024年04月10日
    浏览(115)
  • 【已解决】win10系统 Docker 提示Docker Engine stopped解决全过程记录

    【已解决】win10系统 Docker 提示Docker Engine stopped解决全过程记录

    【已解决】win10系统 Docker 提示Docker Engine stopped解决全过程记录  找到 【Docker Desktop Service】,然后,启动他; 你也可以直接设置为“自动” 找到服务,右键》属性》启动类型:自动》点击“确定” 在底部菜单找到搜索,输入:服务 如果底部没有“搜索”,右键底部》搜索》

    2024年04月26日
    浏览(16)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包