Playcanvas后处理-辉光bloom

(一)Bloom介绍

Bloom(辉光、光晕、泛光)是一种常见的摄像机后处理(PostProcessing)效果,用于再现真实世界相机的成像伪影。这种效果会产生从图像中明亮区域边界延伸的光条纹(或羽毛),从而产生一种极其明亮的光线压倒捕捉场景的相机的错觉。

辉光简单的说法,就是有些地方足够亮,看上去是溢出到周围的区域,下面是playcanvas官方提供的辉光效果对比图

图1 未使用辉光效果

图2 使用辉光效果

(二)Bloom实现原理

bloom的实现原理很简单:就是取camera获取图像的高亮部分,进行高斯模糊,并和原图进行合并就可以实现。

1. 提取较亮区域

通过阈值提取较亮区域的像素点

varying vec2 vUv0;uniform sampler2D uBaseTexture
uniform float uBloomThreshold;float luminance(vec4 color)
{return  0.2125 * color[0] + 0.7154 * color[1] + 0.0721 * color[2]; 
}void main(void)
{vec4 color = texture2D(uBaseTexture, vUv0);// 只保留亮度超过阈值亮度的像素点float val = clamp(luminance(color) - uBloomThreshold, 0.0, 1.0);gl_FragColor = color * val;//这是playcanvas官网提供提取亮度高于某个阈值的算法,但还不知道它的原理//gl_FragColor = clamp((color - uBloomThreshold) / (1.0 - uBloomThreshold), 0.0, 1.0);
}

2. 高斯模糊

接下来,就是如何得到模糊图uBloomTexture

一般模糊图像的算法,我们可以选择常见的高斯模糊,它可以减少图像噪声、降低细节层次

高斯模糊的实现原理,这里不做多赘述,这里提供2篇文章供参考。

原理参考:2D Shader学习——高斯模糊

shader实现参考:基于线性采样的高效高斯模糊实现(译)

参考第二篇文章,我们在JavaScript中,计算我们高斯卷积核的权重和位移

var SAMPLE_COUNT = 15;//高斯曲线
function computeGaussian(n, theta) {return ((1.0 / Math.sqrt(2 * Math.PI * theta)) * Math.exp(-(n * n) / (2 * theta * theta)));
}function calculateBlurValues(sampleWeights, sampleOffsets, dx, dy, blurAmount) {// Create temporary arrays for computing our filter settings.// The first sample always has a zero offset.sampleWeights[0] = computeGaussian(0, blurAmount);sampleOffsets[0] = 0;sampleOffsets[1] = 0;// Maintain a sum of all the weighting values.var totalWeights = sampleWeights[0];// Add pairs of additional sample taps, positioned// along a line in both directions from the center.var i, len;for (i = 0, len = Math.floor(SAMPLE_COUNT / 2); i < len; i++) {// Store weights for the positive and negative taps.var weight = computeGaussian(i + 1, blurAmount);sampleWeights[i * 2] = weight;sampleWeights[i * 2 + 1] = weight;totalWeights += weight * 2;var sampleOffset = i * 2 + 1.5;sampleOffsets[i * 4] = dx * sampleOffset;sampleOffsets[i * 4 + 1] = dy * sampleOffset;sampleOffsets[i * 4 + 2] = -dx * sampleOffset;sampleOffsets[i * 4 + 3] = -dy * sampleOffset;}// Normalize the list of sample weightings, so they will always sum to one.for (i = 0, len = sampleWeights.length; i < len; i++) {sampleWeights[i] /= totalWeights;}
}

在fragment shader中,对图像进行卷积模糊(注意:这里的shader只对水平或垂直一个方向卷积

#define SAMPLE_COUNT 15varying vec2 vUv0;uniform sampler2D uBloomTexture;
uniform vec2 uBlurOffsets[15];
uniform float uBlurWeights[15];void main(void)
{vec4 color = vec4(0.0);for (int i = 0; i < SAMPLE_COUNT; i++){color += texture2D(uBloomTexture, vUv0 + uBlurOffsets[i]) * uBlurWeights[i];}gl_FragColor = color;
}    

最后,我们需要进行2次方向的滤波处理

原理具体参考文章二中,如何将高斯滤波器分为水平方向和垂直方向的滤波器的原理

// Pass 2: draw from rendertarget 1 into rendertarget 2(垂直方向)
calculateBlurValues(this.sampleWeights, this.sampleOffsets, 1.0 / this.targets[1].width, 0, this.blurAmount);
scope.resolve("uBlurWeights[0]").setValue(this.sampleWeights);
scope.resolve("uBlurOffsets[0]").setValue(this.sampleOffsets);
scope.resolve("uBloomTexture").setValue(this.targets[0].colorBuffer);
this.drawQuad(this.targets[1], this.blurShader);// Pass 3: draw from rendertarget 2 back into rendertarget 1(水平方向)
calculateBlurValues(this.sampleWeights, this.sampleOffsets, 0, 1.0 / this.targets[0].height, this.blurAmount);
scope.resolve("uBlurWeights[0]").setValue(this.sampleWeights);
scope.resolve("uBlurOffsets[0]").setValue(this.sampleOffsets);
scope.resolve("uBloomTexture").setValue(this.targets[1].colorBuffer);
this.drawQuad(this.targets[0], this.blurShader);

3. 混合原图和模糊图

那我们最后的fragment shader就可以这样实现,原图+模糊图进行混合

varying vec2 vUv0;//bloom 强度
uniform float uBloomEffectIntensity;uniform sampler2D uBaseTexture;
uniform sampler2D uBloomTexture;void main(void)
{vec4 bloom = texture2D(uBloomTexture, vUv0) * uBloomEffectIntensity;vec4 base = texture2D(uBaseTexture, vUv0);//将原图变暗,防止两图叠加后,像素溢出1base *= (1.0 - clamp(bloom, 0.0, 1.0));//合并原图和模糊图,得到最终的bloom效果gl_FragColor = base + bloom;
}

(三)在playcanvas编辑器中使用

1. 创建脚本bloom.js

// --------------- POST EFFECT DEFINITION --------------- //
var SAMPLE_COUNT = 15;function computeGaussian(n, theta) {return ((1.0 / Math.sqrt(2 * Math.PI * theta)) * Math.exp(-(n * n) / (2 * theta * theta)));
}function calculateBlurValues(sampleWeights, sampleOffsets, dx, dy, blurAmount) {// Look up how many samples our gaussian blur effect supports.// Create temporary arrays for computing our filter settings.// The first sample always has a zero offset.sampleWeights[0] = computeGaussian(0, blurAmount);sampleOffsets[0] = 0;sampleOffsets[1] = 0;// Maintain a sum of all the weighting values.var totalWeights = sampleWeights[0];// Add pairs of additional sample taps, positioned// along a line in both directions from the center.var i, len;for (i = 0, len = Math.floor(SAMPLE_COUNT / 2); i < len; i++) {// Store weights for the positive and negative taps.var weight = computeGaussian(i + 1, blurAmount);sampleWeights[i * 2] = weight;sampleWeights[i * 2 + 1] = weight;totalWeights += weight * 2;// To get the maximum amount of blurring from a limited number of// pixel shader samples, we take advantage of the bilinear filtering// hardware inside the texture fetch unit. If we position our texture// coordinates exactly halfway between two texels, the filtering unit// will average them for us, giving two samples for the price of one.// This allows us to step in units of two texels per sample, rather// than just one at a time. The 1.5 offset kicks things off by// positioning us nicely in between two texels.var sampleOffset = i * 2 + 1.5;// Store texture coordinate offsets for the positive and negative taps.sampleOffsets[i * 4] = dx * sampleOffset;sampleOffsets[i * 4 + 1] = dy * sampleOffset;sampleOffsets[i * 4 + 2] = -dx * sampleOffset;sampleOffsets[i * 4 + 3] = -dy * sampleOffset;}// Normalize the list of sample weightings, so they will always sum to one.for (i = 0, len = sampleWeights.length; i < len; i++) {sampleWeights[i] /= totalWeights;}
}/*** @class* @name BloomEffect* @classdesc Implements the BloomEffect post processing effect.* @description Creates new instance of the post effect.* @augments PostEffect* @param {GraphicsDevice} graphicsDevice - The graphics device of the application.* @property {number} bloomThreshold Only pixels brighter then this threshold will be processed. Ranges from 0 to 1.* @property {number} blurAmount Controls the amount of blurring.* @property {number} bloomIntensity The intensity of the effect.*/
function BloomEffect(graphicsDevice) {pc.PostEffect.call(this, graphicsDevice);// Shadersvar attributes = {aPosition: pc.SEMANTIC_POSITION};// Pixel shader extracts the brighter areas of an image.// This is the first step in applying a bloom postprocess.var extractFrag = ["varying vec2 vUv0;","","uniform sampler2D uBaseTexture;","uniform float uBloomThreshold;","","float luminance(vec4 color)","{","    return  0.2125 * color[0] + 0.7154 * color[1] + 0.0721 * color[2]; ","}","","void main(void)","{",// Look up the original image color."    vec4 color = texture2D(uBaseTexture, vUv0);","",// Adjust it to keep only values brighter than the specified threshold."    float val = clamp(luminance(color) - uBloomThreshold, 0.0, 1.0);","    gl_FragColor = color * val;","}"].join("\n");// Pixel shader applies a one dimensional gaussian blur filter.// This is used twice by the bloom postprocess, first to// blur horizontally, and then again to blur vertically.var gaussianBlurFrag = ["#define SAMPLE_COUNT " + SAMPLE_COUNT,"","varying vec2 vUv0;","","uniform sampler2D uBloomTexture;","uniform vec2 uBlurOffsets[" + SAMPLE_COUNT + "];","uniform float uBlurWeights[" + SAMPLE_COUNT + "];","","void main(void)","{","    vec4 color = vec4(0.0);",// Combine a number of weighted image filter taps."    for (int i = 0; i < SAMPLE_COUNT; i++)","    {","        color += texture2D(uBloomTexture, vUv0 + uBlurOffsets[i]) * uBlurWeights[i];","    }","","    gl_FragColor = color;","}"].join("\n");// Pixel shader combines the bloom image with the original// scene, using tweakable intensity levels.// This is the final step in applying a bloom postprocess.var combineFrag = ["varying vec2 vUv0;","","uniform float uBloomEffectIntensity;","uniform sampler2D uBaseTexture;","uniform sampler2D uBloomTexture;","","void main(void)","{",// Look up the bloom and original base image colors."    vec4 bloom = texture2D(uBloomTexture, vUv0) * uBloomEffectIntensity;","    vec4 base = texture2D(uBaseTexture, vUv0);","",// Darken down the base image in areas where there is a lot of bloom,// to prevent things looking excessively burned-out."    base *= (1.0 - clamp(bloom, 0.0, 1.0));","",// Combine the two images."    gl_FragColor = base + bloom;","}"].join("\n");this.extractShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, extractFrag, 'BloomExtractShader', attributes);this.blurShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, gaussianBlurFrag, 'BloomBlurShader', attributes);this.combineShader = pc.createShaderFromCode(graphicsDevice, pc.PostEffect.quadVertexShader, combineFrag, 'BloomCombineShader', attributes);this.targets = [];// Effect defaultsthis.bloomThreshold = 0.25;this.blurAmount = 4;this.bloomIntensity = 1.25;// Uniformsthis.sampleWeights = new Float32Array(SAMPLE_COUNT);this.sampleOffsets = new Float32Array(SAMPLE_COUNT * 2);
}BloomEffect.prototype = Object.create(pc.PostEffect.prototype);
BloomEffect.prototype.constructor = BloomEffect;BloomEffect.prototype._destroy = function () {if (this.targets) {var i;for (i = 0; i < this.targets.length; i++) {this.targets[i].destroyTextureBuffers();this.targets[i].destroy();}}this.targets.length = 0;
};BloomEffect.prototype._resize = function (target) {var width = target.colorBuffer.width;var height = target.colorBuffer.height;if (width === this.width && height === this.height)return;this.width = width;this.height = height;this._destroy();// Render targetsvar i;for (i = 0; i < 2; i++) {var colorBuffer = new pc.Texture(this.device, {name: "Bloom Texture" + i,format: pc.PIXELFORMAT_RGBA8,width: width >> 1,height: height >> 1,mipmaps: false});colorBuffer.minFilter = pc.FILTER_LINEAR;colorBuffer.magFilter = pc.FILTER_LINEAR;colorBuffer.addressU = pc.ADDRESS_CLAMP_TO_EDGE;colorBuffer.addressV = pc.ADDRESS_CLAMP_TO_EDGE;colorBuffer.name = 'pe-bloom-' + i;var bloomTarget = new pc.RenderTarget({name: "Bloom Render Target " + i,colorBuffer: colorBuffer,depth: false});this.targets.push(bloomTarget);}
};Object.assign(BloomEffect.prototype, {render: function (inputTarget, outputTarget, rect) {this._resize(inputTarget);var device = this.device;var scope = device.scope;// Pass 1: draw the scene into rendertarget 1, using a// shader that extracts only the brightest parts of the image.scope.resolve("uBloomThreshold").setValue(this.bloomThreshold);scope.resolve("uBaseTexture").setValue(inputTarget.colorBuffer);this.drawQuad(this.targets[0], this.extractShader);// Pass 2: draw from rendertarget 1 into rendertarget 2,// using a shader to apply a horizontal gaussian blur filter.calculateBlurValues(this.sampleWeights, this.sampleOffsets, 1.0 / this.targets[1].width, 0, this.blurAmount);scope.resolve("uBlurWeights[0]").setValue(this.sampleWeights);scope.resolve("uBlurOffsets[0]").setValue(this.sampleOffsets);scope.resolve("uBloomTexture").setValue(this.targets[0].colorBuffer);this.drawQuad(this.targets[1], this.blurShader);// Pass 3: draw from rendertarget 2 back into rendertarget 1,// using a shader to apply a vertical gaussian blur filter.calculateBlurValues(this.sampleWeights, this.sampleOffsets, 0, 1.0 / this.targets[0].height, this.blurAmount);scope.resolve("uBlurWeights[0]").setValue(this.sampleWeights);scope.resolve("uBlurOffsets[0]").setValue(this.sampleOffsets);scope.resolve("uBloomTexture").setValue(this.targets[1].colorBuffer);this.drawQuad(this.targets[0], this.blurShader);// Pass 4: draw both rendertarget 1 and the original scene// image back into the main backbuffer, using a shader that// combines them to produce the final bloomed result.scope.resolve("uBloomEffectIntensity").setValue(this.bloomIntensity);scope.resolve("uBloomTexture").setValue(this.targets[0].colorBuffer);scope.resolve("uBaseTexture").setValue(inputTarget.colorBuffer);this.drawQuad(outputTarget, this.combineShader, rect);}
});// ----------------- SCRIPT DEFINITION ------------------ //
var Bloom = pc.createScript('bloom');Bloom.attributes.add('bloomIntensity', {type: 'number',default: 1,min: 0,title: 'Intensity'
});Bloom.attributes.add('bloomThreshold', {type: 'number',default: 0.25,min: 0,max: 1,title: 'Threshold'
});Bloom.attributes.add('blurAmount', {type: 'number',default: 4,min: 1,'title': 'Blur amount'
});Bloom.prototype.initialize = function () {this.effect = new BloomEffect(this.app.graphicsDevice);this.effect.bloomThreshold = this.bloomThreshold;this.effect.blurAmount = this.blurAmount;this.effect.bloomIntensity = this.bloomIntensity;var queue = this.entity.camera.postEffects;queue.addEffect(this.effect);this.on('attr', function (name, value) {this.effect[name] = value;}, this);this.on('state', function (enabled) {if (enabled) {queue.addEffect(this.effect);} else {queue.removeEffect(this.effect);}});this.on('destroy', function () {queue.removeEffect(this.effect);this.effect._destroy();});
};

2. 将脚本挂载在相机

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows11系统如何设置锁屏壁纸

1. 在开始页面里面找到设置 2. 在设置里面找到个性化 3. 按照红色圈出部分操作 个性化锁屏界面 选择 图片 浏览照片 选择一张你觉得好看的图片作为锁屏壁纸 注&#xff1a;如果需要在锁屏后的登录页面显示壁纸 请勾选第三个红圈部分

zabbix-proxy分布式监控

Zabbix是一款开源的企业级网络监控软件&#xff0c;可以监测服务器、网络设备、应用程序等各种资源的状态和性能指标。在大型环境中&#xff0c;如果只有一个Zabbix Server来监控所有的节点&#xff0c;可能会遇到性能瓶颈和数据处理难题。 为了解决这个问题&#xff0c;Zabbi…

【操作系统】文件系统之文件共享与文件保护

文章目录 文件共享硬链接软链接 文件保护口令保护加密保护访问控制 文件共享 为了实现文件的共享&#xff0c;引入了“计数器”字段&#xff0c;当一个文件每被一个用户所共享&#xff0c;那么计数器就加一。如果一个用户删除文件&#xff0c;计数器相应的减一。如果计数器为0…

基于单片机电梯液晶显示防超重,防气体报警、防夹报警控制系统及源程序

一、系统方案 1、本设计采用51单片机作为主控器。 2、液晶显示楼层。 3、防超重&#xff0c;防气体报警、防夹报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /lcd1602初始化设置*/ void init_1602() //lcd1602初始化设置 { write_co…

每天分享五款工具,让大家工作生活更顺心

​ 快乐不是在于拥有什么,而在于我们和别人分享什么。每天分享五款工具&#xff0c;让大家工作办公更顺心就是我最大的快乐。 1.沙盒软件——Sandboxie ​ Sandboxie是一款可以在沙盒中运行程序的软件&#xff0c;它可以保护用户的系统和数据免受恶意软件、病毒和其他威胁的影…

如何利用 AI 写一本书并实现被动收入

如何每个月写一本能赚 5000 美元的书&#xff1f;不少人不知道如何在一周内写作和出版一本书 这里有个教程教你如何利用 AI 写一本书并实现被动收入 [收藏起来以备后用] 推出书友智能写作工具&#xff1a;Bookwiz 不用花几年时间独自写作一本小说&#xff0c;人工智能可以作…

PaddleClas学习2——使用PPLCNet模型对车辆朝向进行识别(python)

使用PPLCNet模型对车辆朝向进行识别 1. 配置PaddlePaddle,PaddleClas环境2. 准备数据2.1 标注数据格式2.2 标注数据3. 模型训练3.1 修改配置文件3.2 训练、评估4 模型预测1. 配置PaddlePaddle,PaddleClas环境 安装:请先参考文档 环境准备 配置 PaddleClas 运行环境。 2. 准…

【docker】Docker网络与iptables

Docker能为我们提供很强大和灵活的网络能力&#xff0c;很大程度上要归功于与iptables的结合。在使用时&#xff0c;你可能没有太关注到 iptables在其中产生的作用&#xff0c;这是因为Docker已经帮我们自动的完成了相关的配置。 iptables在Docker中的应用主要是用于网络流量控…

【STM32】CRC(循环冗余校验)

一、CRC的背景知识 1、什么是CRC (1)CRC&#xff08;Cyclic Redundancy Check&#xff09;&#xff0c;循环冗余校验 (2)什么是校验&#xff0c;为什么需要校验&#xff1a;数据传输&#xff0c;数据存储过程中需要使用到的 (3)什么是冗余&#xff1a;表示比实际上要传输的数据…

【MySQL】聚合函数、group by、update、delete

聚合函数、group by、update、delete 前言正式开始update将孙悟空同学的数学成绩变更为 80 分将曹孟德同学的数学成绩变更为 60 分&#xff0c;语文成绩变更为 70 分将总成绩倒数前三的 3 位同学的数学成绩加上 30 分将所有同学的语文成绩更新为原来的 2 倍 delete删除孙悟空同…

51单片机/STM32F103/STM32F407学习1_点亮LED灯

目录&#xff1a; 基础知识单片机从0实现单片机GPIO介绍 参考连接&#xff1a; 野火霸天虎教程 https://doc.embedfire.com/products/link/zh/latest/mcu/stm32/ebf_stm32f407_batianhu_v1_v2/download/stm32f407_batianhu_v1_v2.html x.1 基础知识 x.1.1 指针中的取地址&a…

Wireshark的数据包它来啦!

通过Wireshark工具&#xff0c;可以轻松的看到网卡的数据信息。通过Wireshark显示的数据包内容信息&#xff0c;通常分七栏&#xff0c;介绍一下&#xff1a; 1No.&#xff1a; 数据包编号。 2.Time Time显示时间&#xff0c;以1号数据包发生开始计时。 3.Source Source显示内容…

Redis篇---第十二篇

系列文章目录 文章目录 系列文章目录前言一、Memcache与Redis的区别都有哪些?二、单线程的redis为什么这么快三、redis的数据类型,以及每种数据类型的使用场景前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇…

【变换器Transformers · 巴伦 - - 平衡与非平衡Baluns and ununs】

目录 1. 介绍2. 常见巴伦应用3. 巴伦理论与平衡系统和非平衡系统简介4. 常见的双端系统&#xff1a;4.1. 射频馈线4.2. 负载4.3. 信号源 5. 巴伦模式与技术6. 铁芯线平衡-不平衡转换器&#xff08;隔离和自耦变压器平衡-不平衡转换器&#xff09;7. 传输线的平衡巴伦和变阻抗巴…

筒仓料位监测|敢不敢对“精度”下狠手!您家筒仓料位测得准吗?

您家是不是还在人工敲仓估算&#xff1f; 您能精确知道料位和库存吗&#xff1f; 您能实时看到库存盈亏吗&#xff1f; 筒仓里装了什么&#xff1f;用了多少&#xff1f; 什么时候进料最划算&#xff1f; 您家的筒仓管理方式可靠吗&#xff1f; 上海思伟筒仓料位监测方案 看…

1.rk3588的yolov5运行:pt_onnx_rknn转换及rknn在rk3588系统python运行

自己有点笨&#xff0c;查资料查了一周才完美的实现了yolov5在rk3588环境下的运行&#xff0c;在这里写具体步骤希望大家少走弯路。具体步骤如下&#xff1a; 一、yolov5的原代码下载及pt文件转换为onnx文件 1.yolov5的原代码下载及环境搭建 在这里一定要下载正确版本的源代码…

1. 基础语法

文章目录 一些基本的概念JDK 和JREJava语言的编译原理编译器和jdk的安装 编译器的基础介绍新建项目的规范src文件介绍src规范强调包 ModuleIJ的一些特殊的操作分屏写代码去掉代码提示的大小写限制注释设置文件编码设置 设置回车快捷键 Java语法基本概念关键字修饰符 与 非修饰符…

一篇文章让你彻底了解Java算法「十大经典排序算法」

✍️作者简介&#xff1a;码农小北&#xff08;专注于Android、Web、TCP/IP等技术方向&#xff09; &#x1f433;博客主页&#xff1a; 开源中国、稀土掘金、51cto博客、博客园、知乎、简书、慕课网、CSDN &#x1f514;如果文章对您有一定的帮助请&#x1f449;关注✨、点赞&…

5款免费BI数据可视化工具,2023年最新精选推荐!

BI可视化工具顾名思义是进行数据分析和可视化的软件&#xff0c;旨在将数据以表格、图表、仪表盘等形式展示出来&#xff0c;让用户能够更加直观了解其业务状况、发现问题&#xff0c;并在必要时进行决策。   市面上BI数据可视化工具很多&#xff0c;目前比较火的像国外的Tabl…

文本转语音

免费工具 音视频转译 通义听悟 | https://tingwu.aliyun.com/u/wg57n33kml5nkr3p 音色迁移 speechify | https://speechify.com/voice-cloning/ 视频生成 lalamu | http://lalamu.studio/demo/ 画质增强 topazlabs video AI | https://www.topazlabs.com 付费工具 rask | htt…