Three.js后期处理简明教程

news2024/11/24 14:24:57

后期处理(Post Processing)通常是指对 2D 图像应用某种效果或滤镜。 在 THREE.js 中我们有一个包含一堆网格物体的场景。 我们将该场景渲染为 2D 图像。 通常,该图像会直接渲染到画布中并显示在浏览器中,但我们可以将其渲染到渲染目标,然后在将结果绘制到画布之前对结果应用一些后处理效果。 之所以称为后处理,是因为它发生在主场景处理之后(后)。

后期处理的例子有 Instagram 滤镜、Photoshop 滤镜等……

在这里插入图片描述

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

1、THREE.js后期处理管道

THREE.js 有一些示例类来帮助设置后处理管道。 它的工作方式是创建一个 EffectComposer 并向其中添加多个 Pass 对象。 然后,调用 EffectComposer.render,它将场景渲染到渲染目标,然后应用每个通道。

每个通道(Pass)都可以是一些后期处理效果,例如添加晕影、模糊、应用光晕、应用胶片颗粒、调整色调、饱和度、对比度等…最后将结果渲染到画布上。

了解 EffectComposer 的功能非常重要。 它创建两个渲染目标。 我们称它们为 rtA 和 rtB。

然后,调用 EffectComposer.addPass 按照你想要应用的顺序添加每个通道。 然后像这样应用通道。
在这里插入图片描述

首先,你传递到 RenderPass 的场景被渲染到 rtA,然后 rtA 被传递到下一个通道,无论它是什么(BloomPass、FilmPass…)。 该过程使用 rtA 作为输入来执行其操作并将结果写入 rtB。 然后 rtB 被传递到下一个通道,该通道使用 rtB 作为输入并写回 rtA。 这贯穿所有的通道。

每个通道有 4 个基本选项

  • enabled:是否使用此通道
  • needSwap:完成本次pass后是否交换rtA和rtB
  • clear:渲染此通道之前是否清除
  • renderToScreen:是否渲染到画布而不是当前目标渲染目标。 通常,你需要在添加到 EffectComposer 的最后一个通道中将其设置为 true。

2、THREE.js后期处理快速上手

让我们举一个基本的例子。 我们将从有关响应性的文章中的示例开始。

为此,我们首先创建一个 EffectComposer。

const composer = new EffectComposer(renderer);

然后,作为第一个通道,我们添加一个 RenderPass,它将使用相机将场景渲染到第一个渲染目标中。

composer.addPass(new RenderPass(scene, camera));

接下来我们添加一个 BloomPass。 BloomPass 将其输入渲染到通常较小的渲染目标并模糊结果。 然后,它将模糊结果添加到原始输入之上。 这使得场景具有绽放效果。

const bloomPass = new BloomPass(
    1,    // strength
    25,   // kernel size
    4,    // sigma ?
    256,  // blur render target resolution
);
composer.addPass(bloomPass);

最后,我们有一个 FilmPass,可以在其输入之上绘制噪声和扫描线。

const filmPass = new FilmPass(
    0.35,   // noise intensity
    0.025,  // scanline intensity
    648,    // scanline count
    false,  // grayscale
);
filmPass.renderToScreen = true;
composer.addPass(filmPass);

由于 filmPass 是最后一个通道,我们将其 renderToScreen 属性设置为 true 以告诉它渲染到画布。 如果不设置此项,它将渲染到下一个渲染目标。

要使用这些类,我们需要导入一堆脚本。

import {EffectComposer} from '/examples/jsm/postprocessing/EffectComposer.js';
import {RenderPass} from '/examples/jsm/postprocessing/RenderPass.js';
import {BloomPass} from '/examples/jsm/postprocessing/BloomPass.js';
import {FilmPass} from '/examples/jsm/postprocessing/FilmPass.js';

对于几乎任何后期处理,都需要 EffectComposer.js 和 RenderPass.js。

我们需要做的最后一件事是使用 EffectComposer.render 而不是 WebGLRenderer.render 并告诉 EffectComposer 匹配画布的大小。

-function render(now) {
-  time *= 0.001;
+let then = 0;
+function render(now) {
+  now *= 0.001;  // convert to seconds
+  const deltaTime = now - then;
+  then = now;

  if (resizeRendererToDisplaySize(renderer)) {
    const canvas = renderer.domElement;
    camera.aspect = canvas.clientWidth / canvas.clientHeight;
    camera.updateProjectionMatrix();
+    composer.setSize(canvas.width, canvas.height);
  }

  cubes.forEach((cube, ndx) => {
    const speed = 1 + ndx * .1;
-    const rot = time * speed;
+    const rot = now * speed;
    cube.rotation.x = rot;
    cube.rotation.y = rot;
  });

-  renderer.render(scene, camera);
+  composer.render(deltaTime);

  requestAnimationFrame(render);
}

EffectComposer.render 采用 deltaTime,它是自渲染最后一帧以来的时间(以秒为单位)。 它将它传递给各种效果,以防它们中的任何一个被动画化。 在本例中, FilmPass 是动画的。

3、运行时设置效果参数

要在运行时更改效果参数通常需要设置 uniform的值。 让我们添加一个 gui 来调整一些参数。 弄清楚可以轻松调整哪些值以及如何调整它们需要深入研究该效果的代码。

查看 BloomPass.js 内部,我发现了这一行:

this.copyUniforms[ "opacity" ].value = strength;

所以我们可以通过如下代码来设置强度:

bloomPass.copyUniforms.opacity.value = someValue;
同样地,在 FilmPass.js 中我发现了这些行:

if ( grayscale !== undefined )    this.uniforms.grayscale.value = grayscale;
if ( noiseIntensity !== undefined ) this.uniforms.nIntensity.value = noiseIntensity;
if ( scanlinesIntensity !== undefined ) this.uniforms.sIntensity.value = scanlinesIntensity;
if ( scanlinesCount !== undefined ) this.uniforms.sCount.value = scanlinesCount;

这样就很清楚如何设置它们了。

让我们制作一个快速 GUI 来设置这些值

import {GUI} from '/examples/jsm/libs/lil-gui.module.min.js';

const gui = new GUI();
{
  const folder = gui.addFolder('BloomPass');
  folder.add(bloomPass.copyUniforms.opacity, 'value', 0, 2).name('strength');
  folder.open();
}
{
  const folder = gui.addFolder('FilmPass');
  folder.add(filmPass.uniforms.grayscale, 'value').name('grayscale');
  folder.add(filmPass.uniforms.nIntensity, 'value', 0, 1).name('noise intensity');
  folder.add(filmPass.uniforms.sIntensity, 'value', 0, 1).name('scanline intensity');
  folder.add(filmPass.uniforms.sCount, 'value', 0, 1000).name('scanline count');
  folder.open();
}

现在我们可以调整这些设置了。

这是实现我们自己的效果的一小步。

4、实现自己的后期处理通道

后期处理效果使用着色器(Shader)。 着色器是用一种称为 GLSL(图形库着色语言)的语言编写的。 对于这些文章来说,回顾整个语言是一个太大的主题。 一些可以开始使用的资源可能是这篇文章,也可能是《着色器之书》。

我认为一个帮助你入门的示例会很有帮助,所以让我们制作一个简单的 GLSL 后处理着色器。 我们将制作一个可以将图像乘以颜色的图像。

对于后期处理,THREE.js 提供了一个有用的助手,称为 ShaderPass。 它需要一个带有定义顶点着色器、片段着色器和默认输入信息的对象。 它将处理设置从哪个纹理读取以获取上一个通道的结果以及渲染到何处:渲染目标或屏幕画布。

这是一个简单的后处理着色器,它将前一个通道的结果乘以颜色。

const colorShader = {
  uniforms: {
    tDiffuse: { value: null },
    color:    { value: new THREE.Color(0x88CCFF) },
  },
  vertexShader: `
    varying vec2 vUv;
    void main() {
      vUv = uv;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1);
    }
  `,
  fragmentShader: `
    varying vec2 vUv;
    uniform sampler2D tDiffuse;
    uniform vec3 color;
    void main() {
      vec4 previousPassColor = texture2D(tDiffuse, vUv);
      gl_FragColor = vec4(
          previousPassColor.rgb * color,
          previousPassColor.a);
    }
  `,
};

上面的 tDiffuse 是 ShaderPass 用于传递前一个通道的结果纹理的名称,因此我们几乎总是需要它。 然后我们将颜色声明为 THREE.js的 Color 类型。

接下来我们需要一个顶点着色器。 对于后期处理,此处显示的顶点着色器几乎是标准的,很少需要更改。 无需赘述太多细节(请参阅上面链接的文章),变量 uv、 projectionMatrix、 modelViewMatrix 和 position 均由 THREE.js 神奇地添加。

最后我们创建一个片段着色器。 在其中,我们使用此行从上一次传递中获取像素颜色

vec4 previousPassColor =texture2D(tDiffuse, vUv);
我们将它乘以我们的颜色并将 gl_FragColor 设置为结果

gl_FragColor = vec4(
    previousPassColor.rgb * color,
    previousPassColor.a);

添加一些简单的 GUI 来设置颜色的 3 个值:

const gui = new GUI();
gui.add(colorPass.uniforms.color.value, 'r', 0, 4).name('red');
gui.add(colorPass.uniforms.color.value, 'g', 0, 4).name('green');
gui.add(colorPass.uniforms.color.value, 'b', 0, 4).name('blue');

这就为我们提供了乘以颜色的简单后处理效果。

正如前面提到的,关于如何编写 GLSL 和自定义着色器的所有细节对于这些文章来说太多了。 如果你确实想了解 WebGL 本身如何工作,请查看这些文章。 另一个很棒的资源是阅读 THREE.js 存储库中现有的后处理着色器。 有些比其他更复杂,但如果你从较小的开始,就有望了解它们的工作原理。

不幸的是,THREE.js 存储库中的大多数后期处理效果都没有文档,因此要使用它们,你必须通读示例或效果本身的代码。 希望这些简单的示例和有关渲染目标的文章提供足够的上下文来开始。


原文链接:THREE.js后期处理入门 — BimAnt

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1034639.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI写文章软件-怎么选择不同的AI写文章软件

在如今信息爆炸的时代,无论是学生、职场人士,还是创作者和企业家,写文章都是一项常见而又重要的任务。然而,随着科技的不断进步,AI写文章的软件也逐渐走进了人们的视野。 147GPT批量文章生成工具​www.147seo.com/post…

Java 基于微信小程序的学生选课系统

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 第一章: 简介第二章 技术栈第三章: 功能分析第四章 系统设计第五章 系统功…

Intel酷睿和AMD锐龙

Intel酷睿系列,主要分i3、i5、i7、i9 如:Intel 酷睿i5 10210U i5:品牌修饰符。 10:代次指示符。 210:sku编号。 常见后缀: G1-G7:集显等级。 U:低功耗。 H:标压版…

通俗易懂了解大语言模型LLM发展历程

1.大语言模型研究路程 NLP的发展阶段大致可以分为以下几个阶段: 词向量词嵌入embedding句向量和全文向量理解上下文超大模型与模型统一 1.1词向量 将自然语言的词使用向量表示,一般构造词语字典,然后使用one-hot表示。   例如2个单词&…

GeekRUN-7芯片跑分表

前两个字母是芯片简写,如麒麟,是QL,骁龙是XL,天玑是TJ,第一串数字是最高值,第二串是最低值,省电模式差不多这个水平。QL9K是麒麟9000,QL9S

aix360-gec

目录 分组条件期望(GroupedCE)解释程序创建虚拟环境导包加载数据集训练模型计算独立条件期望ICEplot_ice_explanation 计算分组条件期望 (GCE)plot_gce_explanation 记录一下学习过程,官方的代码在https://github.com/Trusted-AI/AIX360/tree…

High-Resolution Side Channels for Untrusted Operating Systems【ATC‘17】

目录 摘要引言贡献• 一个不受信任的操作系统的两个新的高分辨率侧通道来攻击受保护的应用程序;• 对 libjpeg 的显着改进攻击和针对 VC3 的新攻击;• 侧通道攻击对不受信任的操作系统的重要性增加。 系统模型背景Intel SGX页面错误通道时间限制空间限制…

微信小程序案例2-3:婚礼邀请函

文章目录 一、运行效果二、知识储备(一)导航栏设置1、导航栏的相关配置项2、利用导航栏组件2、在页面配置文件中对导航栏进行配置3、在全局配置文件中对导航栏进行配置 三、实现步骤 一、运行效果 “婚礼邀请函”微信小程序由4个页面组成,分别…

Springboot整合分页插件pagehelper

首先需要有一定的springbootmybatis的基础&#xff0c;才能使用顺畅 项目结构如下 引入依赖&#xff0c;springboot版本选的是2.7.16版本&#xff0c;jdk选的17&#xff0c; <!--分页插件--> <dependency><groupId>com.github.pagehelper</groupId><…

基于微信小程序的房屋租赁系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户微信小程序端的主要功能有&#xff1a;户主微信小程序端的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文…

OceanBase 数据文件缩容实践

本文章介绍了OceanBase集群关于数据文件的缩容场景&#xff0c;并提供一种缩容方案予以参考。 作者&#xff1a;关炳文&#xff0c;爱可生 DBA 团队成员&#xff0c;负责数据库相关技术支持&#xff0c;一步两阶梯&#xff0c;兼具勤奋与慵懒。 爱可生开源社区出品&#xff0c;…

浅谈单元测试:测试和自动化中的利用

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 浅谈单元测试是一件棘手的事情。我很确定测试人员在某个时候会抱怨开发人员没有正确地进行单元测试&…

Idea中使用Service管理微服务

前言 如何在本地一键启动很多个微服务&#xff0c;下面介绍下IDEA开发工具中得Services管理管理功能 一、第一步 1、在IDEA中下栏bar中如果存在Services,请看第二步。 2、如果没有请按照以下步骤打开 View -> Tool Windows -> Services 二、第二步 刚创建好的窗口是空…

论文浅尝 | 基于知识增强的多行为对比推荐

笔记整理&#xff1a;吴飞跃&#xff0c;东南大学硕士&#xff0c;研究方向为推荐系统 链接&#xff1a;https://doi.org/10.1145/3539597.3570386 动机 在实际推荐场景中&#xff0c;用户和物品之间存在多种类型的交互行为&#xff0c;如在线购物平台上的点击、标记为喜欢和购…

【算法专题突破】二分查找 - 704. 二分查找(16)

目录 1. 题目解析 2. 算法原理 3. 代码编写 写在最后&#xff1a; 1. 题目解析 题目链接&#xff1a;704. 二分查找 - 力扣&#xff08;LeetCode&#xff09; 题目非常简单&#xff0c;就是查找一个 target。 2. 算法原理 根据最基本的二分查找算法&#xff1a; 在一个…

Java代码随想录第一章:-数组理论基础,704. 二分查找,27. 移除元素 ,

一、数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标下对应的数据。 举一个字符数组的例子如图&#xff1a; 需要两点注意的是 数组下标都是从0开始的。数组内存空间的地址是连续的 正是因为数组的在内存空间…

Unity 发布WebGL平台,C#与JavaScript交互

发布H5平台&#xff0c;接入SDK&#xff0c;比如微信等&#xff0c;涉及到C#与JS的交互。 jslib&#xff08;JavaScript Library&#xff09;是Unity的一种机制&#xff0c;允许你在C#中通过JavaScript代码来执行一些操作。这是一种高级的技巧&#xff0c;主要用于一些特殊情况…

【QT】Qt的随身笔记(持续更新...)

目录 Qt 获取当前电脑桌面的路径Qt 获取当前程序运行路径Qt 创建新的文本文件txt&#xff0c;并写入内容如何向QPlainTextEdit 写入内容QTimerQMessageBox的使用QLatin1StringQLayoutC在c头文件中写#include类的头文件与直接写class加类名有何区别mutable关键字前向声明 QFontQ…

Android AMS——APP启动流程

Android 应用启动方式主要有两种 , 冷启动和热启动。 冷启动:后台没有应用进程 , 需要先创建进程 , 然后启动 Activity ;热启动:后台有应用进程 , 不创建进程 , 直接启动 Activity ; 其实,还有一种问起动的方式,就是用户按了返回键退出应用,随后又从新启动,可是活…

【Python】{已解决}在命令行窗口查看的版本号与安装版本不一致问题

今天在使用一个新的第三方库的时候&#xff1a;先pip安装了一下&#xff0c;然后导入的时候报错了 然后以为是没有安装成功&#xff0c;就又pip了一下&#xff0c;发现已经成功安装了。 那是为什么呢&#xff1f; 是解释器出现了问题吗&#xff1f;于是我就去查看了一下pychar…