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.mzph.cn/news/154095.shtml

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

相关文章

Altium Designer学习笔记2

原理图的绘制 需要掌握的是系统自带原理图库元件的添加。

[WUSTCTF 2020]level2 脱壳

这里我们发现ida打开后是有报错的 所以猜测是否有壳 所以我们去看看是否有壳 发现去壳了 然后再放到IDA中看即可

Atlassian发布最新补贴政策,Jira/Confluence迁移上云最低可至零成本

到2024年2月15日&#xff0c;Atlassian将不再提供对Jira、Confluence、Jira Service Management等Server版产品的支持。 近期&#xff0c;Atlassian推出了一项针对云产品的特殊优惠。现在从Server版迁移到云版&#xff0c;您能享受到高额补贴&#xff0c;甚至成本低至零元。立…

Odoo:行业领先的免费开源财务管理解决方案

面向现代企业的财务和会计软件 可靠关账&#xff0c;更快速、更准确地报告财务数据 Odoo ERP财务和会计软件可帮助财务主管设计、革新和理顺财务流程和运营。Odoo ERP无缝整合各种核心财务和会计功能&#xff0c;提供强大的合规管理特性&#xff0c;有助于企业改善业务绩效、提…

Selenium4+python被单独定义<div>的动态输入框和二级下拉框要怎么定位?

今天在做练习题的时候,发现几个问题捣鼓了好久,写下这篇来记录 问题一: 有层级的复选框无法定位到二级目录 对于这种拥有二级框的选项无法定位,也不是<select>属性. 我们查看下HTML,发现它是被单独封装在body内拥有动态属性的独立<div>,当窗口点击的时候才会触发…

十六、RabbitMQ快速入门

目录 一、在centos上下载MQ镜像 二、安装运行容器 三、登录进入MQ 1、添加一个新的用户 2、新建虚拟机 3、 为用户分配权限 四、RabbitMQ的基本概念 RabbitMQ中的几个概念: 五、常见消息模型 六、简单的消息生产与消费 1、消费者类 2、生产者类 3、基本消息队列的消…

数据库中的undo与redo的作用

undo与redo的作用 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定期…

Kubernetes Gateway API 攻略:解锁集群流量服务新维度!

Kubernetes Gateway API 刚刚 GA&#xff0c;旨在改进将集群服务暴露给外部的过程。这其中包括一套更标准、更强大的 API资源&#xff0c;用于管理已暴露的服务。在这篇文章中&#xff0c;我将介绍 Gateway API 资源&#xff0c;并以 Istio 为例来展示这些资源是如何关联的。通…

C语言之sizeof 和 strlen 详细介绍

C语言之sizeof 和 strlen 文章目录 C语言之sizeof 和 strlen1. sizeof 和 strlen 的比较1.1 sizeof1.2 strlen1.3 sizeof 和 strlen 的对比 2. 练习2.1.1 一维数组2.1.2 字符数组 1. sizeof 和 strlen 的比较 1.1 sizeof sizeof是C语言中的一个关键字&#xff0c;计算的是变量…

设计模式常见面试题

简单梳理下二十三种设计模式&#xff0c;在使用设计模式的时候&#xff0c;不仅要对其分类了然于胸&#xff0c;还要了解每个设计模式的应用场景、设计与实现&#xff0c;以及其优缺点。同时&#xff0c;还要能区分功能相近的设计模式&#xff0c;避免出现误用的情况。 什么是…

python -opencv形态学操作

python -opencv形态学操作 1.服饰和膨胀 1.服饰和膨胀 opencv 腐蚀通过cv2.erode实现&#xff0c;膨胀通过cv2.dilate实现&#xff0c;看一下下面代码&#xff1a; from ctypes.wintypes import SIZE from multiprocessing.pool import IMapUnorderedIterator import cv2 i…

76基于matlab的免疫算法求解配送中心选址问题,根据配送地址确定最佳配送中心地址位置。

基于matlab的免疫算法求解配送中心选址问题&#xff0c;根据配送地址确定最佳配送中心地址位置。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 76matlab免疫算法配送中心选址 (xiaohongshu.com)

泛型进阶:通配符

基本概念 对泛型不了解的可以看这篇博客&#xff1a;数据结构前瞻-CSDN博客 一般来说&#xff0c;&#xff1f;在泛型里的使用就是通配符 看看下面的代码 class Message<T> {private T message ;public T getMessage() {return message;}public void setMessage(T m…

网络协议头分析及抓包三次挥手四次握手

数据的封装与传递过程 思考&#xff1a; 应用层调用send后&#xff0c;是如何把数据发送到另一台机器的某个进程的。接收的设备收到数据包后&#xff0c;如何处理给应用层&#xff1f; MTU &#xff1a; Maximum Transmit Unit 最大传输单元 物理接口&#xff08;数据链路层&am…

Lightsail VPS 实例在哪些方面胜过 EC2 实例?

文章作者&#xff1a;Libai 引言 Lightsail VPS 实例和 EC2 实例是云计算领域中两种受欢迎的技术。虽然两者都提供虚拟服务器解决方案&#xff0c;但了解 Lightsail VPS 实例在哪些方面胜过 EC2 实例非常重要。在本文中&#xff0c;我们将探讨这两种技术之间的关键区别&#x…

CI/CD - jenkins

目录 一、部署 1、简介 2、部署 二、配置 三、实时触发 四、自动化构建docker镜像 五、通过ssh插件交付任务 六、添加jenkins节点 七、RBAC 八、pipeline 九、jenkins结合ansible参数化构建 1、安装ansible 2、新建gitlab项目 3、jenkins新建项目playbook 一、部…

choices参数的使用、MVC和MTV的模式、创建表对表关系的三种创建方式

【1】choices参数的使用 应用场景&#xff1a;针对表中可能列表完全的字段&#xff0c;采用choices参数 例如&#xff1a;性别&#xff0c;代码如下 # 1.创建一张表class gender_info(models.Model):name models.CharField(max_length32)password models.CharField(max_lengt…

大数据题目的解题技巧

目录 大数据题目的技巧总括 实例精析 实例一 实例二 实例三 大数据题目的技巧总括 &#xff08;1&#xff09;哈希函数可以把数据按照种类均匀分流&#xff1b; &#xff08;2&#xff09;布隆过滤器用于集合的建立与查询&#xff0c;并可以节省大量空间&#xff1b; &…

ubuntu 安装 gparted

前提环境&#xff1a; 阿里云的源。 sudo apt update sudo apt upgrade sudo apt install gparted 搜索&#xff1a;

Android AIDL中使用Surface问题

1.构建ITest.aidl文件 package com.xxx.xxxx;import android.view.Surface;interface IMonitorService {boolean addSurface(in Surface surface);boolean removeSurface(in Surface surface); } 2.构建时报错 3.Surface源码分析 android.view.Surface中包含两个Surface类&am…