Three.js后期处理简明教程

后期处理(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,    // strength25,   // kernel size4,    // sigma ?256,  // blur render target resolution
);
composer.addPass(bloomPass);

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

const filmPass = new FilmPass(0.35,   // noise intensity0.025,  // scanline intensity648,    // scanline countfalse,  // 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.xdnf.cn/news/142038.html

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

相关文章

人机逻辑中的家族相似性与非家族相似性

维特根斯坦的家族相似性理论是他在《哲学研究》中提出的一个重要概念。他认为,语言游戏是一种人们使用语言的方式,不同的语言游戏之间可能存在相似性,就像一个家族的成员之间存在相似性一样。维特根斯坦认为,相似性不是通过一个共…

Quartz 体系结构

Quartz的体系结构 Quartz的重要组件 Scheduler 用于与调度程序交互的主程序接口。 Scheduler调度程序-任务执行计划表,只有安排进执行计划的任务Job(通过scheduler.scheduleJob方法安排进执行计划),当它预先定义的执行时间到了的时…

mac 解决 vscode 权限不足问题,Insufficient permissions

commod 空格,输入终端并打开写入指令 sudo chown -R xxxxxx1 xxxxx2(例如我的sudo chown -R admin Desktop,具体参数查看下方) x1: 用户名,可通过左上角查看 x2: 目标文件夹。可以另起一个终端,用cd 和 l…

Unity当中的灯光类型

文章目录 前言一、Directional平行光二、Point点灯三、Spot 聚光灯四、Area面光灯,只用于烘培 前言 Unity当中的灯光类型 一、Directional平行光 Unity当中最重要的灯管类型,类似现实中的太阳光 二、Point点灯 类似现实中的灯泡,萤火虫&a…

内存对齐--面试常问问题和笔试常考问题

1.内存对齐的意义 C 内存对齐的主要意义可以简练概括为以下几点: 提高访问效率:内存对齐可以使数据在内存中以更加紧凑的方式存储,从而提高了数据的访问效率。处理器通常能够更快地访问内存中对齐的数据,而不需要额外的字节偏移计…

oracle

title: “Oracle” createTime: 2021-12-13T16:35:4108:00 updateTime: 2021-12-13T16:35:4108:00 draft: false author: “name” tags: [“oracle”] categories: [“db”] description: “测试的” 时间字段分析 timestamp 精确到秒后面6位 createTime: 2021-12-13T16:35:…

vue3基础

141.用vite创建vue3项目 142.项目目录 vue3中,直接导入组件就能用 不再要求唯一根元素 //createApp(App)是创建实例,.mount(#app)是在将实例往id为app的盒子上挂载 createApp(App).mount(#app)//挂载就是让实例接管一片区域 assets是存放图片或样式的…

uniapp——实现二维码生成+保存二维码图片——基础积累

最近在做二维码推广功能,自从2020年下半年到今天,大概有三年没有用过uniapp了,而且我之前用uniapp开发的程序还比较少,因此很多功能都浪费了很多时间去查资料,现在把功能记录一下。 这里写目录标题 效果图1.根据接口返…

Vue Mock.js介绍和使用与首页导航栏左侧菜单搭建

前言: 因为使用Vue开发,组件写的太多,组件与组件之间的传递数据复杂,所以要用到Mock和Bus事件 一,关于Mock 1.1.什么是Mock.js Mock.js是一个模拟数据的生成器,用来帮助前端调试开发、进行前后端的原型分离…

ctfshow 命令执行 (29-39)

学习参考的 https://www.cnblogs.com/NPFS/p/13279815.html 说的很全面 web29 命令执行,需要严格的过滤 源码 error_reporting(0); if(isset($_GET[c])){$c $_GET[c];if(!preg_match("/flag/i", $c)){eval($c);}}else{highlight_file(__FILE__); } …

深入浅出Java的多线程编程——第一篇

目录 1. 认识线程(Thread) 1.1 概念 1.1.1 线程是什么 1.1.2 为啥需要线程 1.1.3 进程和线程的区别 1.1.4 Java的线程和操作系统线程的关系 1.2 第一个多线程程序 1.3 创建线程的方式(5种) 1.3.1 继承Thread类 1.3.2 实现…

Spring面试题21:说一说Spring的@Required注解和@Qualifier注解

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:说一说Spring的@Required注解 @Required ,用于标记在注入的属性上。它表示被注解的属性在配置 Bean 的时候是必需的,如果没有正确配置,则会抛出…

go字符串拼接方式及性能比拼

在golang中字符串的拼接方式有多种,本文将会介绍比较常用的几种方式,并且对各种方式进行压测,以此来得到在不同场景下更适合使用的方案。 文章目录 1、go字符串的几种拼接方式1.1 fmt.Sprintf1.2 运算符拼接1.3 strings.Join1.4 strings.Bui…

【从0学习Solidity】45. 时间锁

【从0学习Solidity】45. 时间锁 博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。关注我们的主页,探索全栈开…

关于vantUI的导航组件tab标签页在ios和安卓中运用遇到的坑

vantTab的默认值 应用场景问题描述原始代码更正代码 应用场景 根据路由传值设置默认tab页,获取不同的数据并进行展示 问题描述 ios可正常按照路由传值默认tab页,安卓始终默认tabList的第一个value值,疑安卓系统中不接受dataMap.tabActive为…

YOLOv5如何训练自己的数据集(生活垃圾数据集为例)

文章目录 前言1、数据标注说明2、定义自己模型文件3、训练模型参考文献 前言 本文主要介绍如何利用YOLOv5训练自己的数据集 1、数据标注说明 以生活垃圾数据集为例子 生活垃圾数据集(YOLO版)点击这里直接下载本文生活垃圾数据集 生活垃圾数据集组成&…

Python开发与应用实验2 | Python基础语法应用

*本文是博主对学校专业课Python各种实验的再整理与详解,除了代码部分和解析部分,一些题目还增加了拓展部分(⭐)。拓展部分不是实验报告中原有的内容,而是博主本人自己的补充,以方便大家额外学习、参考。 &a…

【JavaEE】多线程案例-线程池

文章目录 1. 什么是线程池2. 为什么要使用线程池(线程池有什么优点)3. 如何使用Java标准库提供的线程池3.1 创建一个线程池对象3.2 什么是工厂模式3.3 为什么要使用工厂模式3.4 Executors 创建不同具有不同特性的线程池3.5 ThreadPool 类的构造方法3.6 线…

C++之list成员函数应用总结(二百三十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…

Neo4j 与 Cypher 基础

更好的阅读体验 \huge{\color{red}{更好的阅读体验}} 更好的阅读体验 简介 Neo4j 是用 Java 实现的开源 NoSQL 图数据库。从2003年开始开发,2007年正式发布第一版,其源码托管于 GitHub。 与常见的关系型数据库不同,Neo4j 基于图图结构来表示…