Three 之 three.js (webgl)PostProcessing/shader/EffectComposer 屏幕渲染 之 饱和度、对比度、亮度动态调整效果简单实现
目录
Three 之 three.js (webgl)PostProcessing/shader/EffectComposer 屏幕渲染 之 饱和度、对比度、亮度动态调整效果简单实现
一、简单介绍
Three js 开发的一些知识整理,方便后期遇到类似的问题,能够及时查阅使用。
本节介绍, three.js (webgl)中有各种材质,不同材质又有不同的效果,这里做简单的介绍,如果有不足之处,欢迎指出,或者你有更好的方法,欢迎留言。
后期处理
1、threejs 的后期处理通道包提供了各种强大的效果,有了这些效果会大大降低代码难度,并且可以直接使用内置的着色器包,避免了复杂的着色器代码编写。后期处理通道一般都按顺序执行,后加入的会覆盖前面的通道。
2、EffectComposer 用于在three.js中实现后期处理效果。该类管理了产生最终视觉效果的后期处理过程链。 后期处理过程根据它们添加/插入的顺序来执行,最后一个过程会被自动渲染到屏幕上。
3、后置处理通常是指应用到2d图像上的某种特效或者是滤镜。在ThreeJs的场景中,我们有的是由很多网格(mesh)构成的场景(scene)。我们将其渲染成2d图像。一般来说,图像被直接渲染成canvas然后在浏览器中被展示,然而在结果被输出到canvas之前,我们也可以通过另外的一个render target并应用一些后置效果。这被称为Post Processing,因为它发生在主场景渲染过程之后。
后置处理的示例 比如 Instagram 的滤镜,photoshop的滤镜。
工作方式:
1)你需要创建EffectComposer然后增加一些Pass对象。
2)每一个Pass阶段都可以增加一些后置处理特效,添加小插图,模糊,添加光晕,添加噪点,调整色相,饱和度,对比度等等。最终把效果渲染到canvas。
理解EffectComposer是如何工作的是有一点重要的
1)它创建两个render targets。让我们称他们为rtA和rtB
2)然后你调用EffectComposer.addPass按照你想要应用它们的顺序增加pass。然后它们就被向下图所示的被应用。举例说明,如下图:
首先 你传入RenderPass的场景被渲染到rtA,不管rta的内容是啥,它继续向下一个pass传递。下一个pass将它作为输入做一些操作然后将其写入到rtB。然后rtB传到下一个pass,将rtB作为输入作一些操作然后在写回rtA。这个过程在整个pass过程中持续发生。
更多信息查看 Threejs 官网:Three.js – JavaScript 3D Library
本节介绍,Threejs 中的屏幕渲染(后期渲染)添加对整个屏幕的亮度、饱和度、对比度进行动态调节的效果。说明:
(1)brigtness亮度直接乘以一个系数,也就是RGB整体缩放,调整亮度;
(2)saturation饱和度:首先根据公式计算同等亮度情况下饱和度最低的值,根据Saturation在饱和度最低的图像和原图之间差值
(3)contrast对比度:首先计算对比度最低的值,根据Contrast在对比度最低的图像和原图之间差值,
(4)结果:保持原图的透明度a,把最后 对比度得到的结果,进行显示rgb
二、实现原理
1、创建 EffectComposer ,并添加 RenderPass 通道,基础场景
2、添加 ShaderPass,主要添加 BSCShader,实现对屏幕整体 亮度、饱和度、对比度的调整
3、BSCShader 把传入场景 2d图像 ,通过算法屏幕的2d 图像,进行亮度、饱和度、对比度,如下的关键代码
// 获取原图的颜色rgba
vec4 color = texture2D(tDiffuse, vUv);
//brigtness亮度直接乘以一个系数,也就是RGB整体缩放,调整亮度
vec3 finalColor = color.rgb * _Brightness;
//saturation饱和度:首先根据公式计算同等亮度情况下饱和度最低的值:
float gray = 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
vec3 grayColor = vec3(gray, gray, gray);
//根据Saturation在饱和度最低的图像和原图之间差值
finalColor = lerpColor(grayColor, finalColor, _Saturation);
//contrast对比度:首先计算对比度最低的值
vec3 avgColor = vec3(0.5, 0.5, 0.5);
//根据Contrast在对比度最低的图像和原图之间差值
finalColor = lerpColor(avgColor, finalColor, _Contrast);
// 结果rgb,透明度保持原值即可
gl_FragColor = vec4(vec3(finalColor), color.a);
三、注意事项
1、场景不断变化,要持续的效果,需要办 EffectComposer.render() 渲染刷新
2、可以改变 BSCShader的 参数,进行对应的效果变化
四、效果预览
五、实现步骤
1、为了方便学习,这里是基于 Github 代码,进行开发的,大家可以下载官网代码,很多值得学习的案例
GitHub - mrdoob/three.js: JavaScript 3D Library.
gitcode:mirrors / mrdoob / three.js · GitCode
2、在上面的基础上,添加一个 html ,用来实现案例效果,引入相关包
3、创建基础场景
4、添加后期渲染,并且添加点屏幕效果通道
5、添加 UI 调节亮度、饱和度 和 对比度
6、完成其他的基础代码编写,运行脚本,效果如下
六、关键代码
1、BSCPostProcessingEffect.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>003BSCPostProcessingEffect</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "../../../build/three.module.js"
}
}
</script>
<script type="module">
// 引入 three 基础库
import * as THREE from 'three';
// GUI
import { GUI } from '../../jsm/libs/lil-gui.module.min.js';
// 后期渲染关键库
import { EffectComposer } from '../../jsm/postprocessing/EffectComposer.js';
import { RenderPass } from '../../jsm/postprocessing/RenderPass.js';
import { ShaderPass } from '../../jsm/postprocessing/ShaderPass.js';
// 颜色分离脚本
import { BSCShader } from './shaders/003BSCShader.js';
let scene, camera, renderer, composer;
let object;
let effect;
let effectControls;
const params = {
enable: true
};
init();
animate();
function init() {
// 渲染器
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setClearColor('#cccccc');
document.body.appendChild( renderer.domElement );
// camera
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.z = 8;
// 场景
scene = new THREE.Scene();
// 添加物体到场景中
object = new THREE.Object3D();
scene.add( object );
const geometry = new THREE.SphereGeometry( 1, 4, 4 );
const material = new THREE.MeshPhongMaterial( { color: '#ff3399', flatShading: true } );
const material2 = new THREE.MeshPhongMaterial( { color: '#880088' } );
const mesh = new THREE.Mesh( geometry, material );
mesh.position.set(-2,0,0)
object.add(mesh)
const mesh2 = new THREE.Mesh( geometry, material2 );
mesh2.position.set(2,0,0)
object.add(mesh2)
// 添加环境光
scene.add( new THREE.AmbientLight( 0x222222 ) );
// 添加方向光
const light = new THREE.DirectionalLight( 0xffffff );
light.position.set( 1, 1, 1 );
scene.add( light );
// 创建操作 UI
createGUI();
// 创建后期渲染通道
composer = new EffectComposer( renderer );
composer.addPass( new RenderPass( scene, camera ) );
// 添加 RGB 颜色分离效果通道效果
effect = new ShaderPass( BSCShader );
// effect.uniforms[ '_Brightness' ].value = 0.015;
effect.uniforms[ '_Brightness' ].value = effectControls._Brightness;
effect.uniforms[ '_Saturation' ].value = effectControls._Saturation;
effect.uniforms[ '_Contrast' ].value = effectControls._Contrast;
composer.addPass( effect );
// 窗口尺寸变化监听
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
// camera 更新 aspect
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
// 渲染器更新大小
renderer.setSize( window.innerWidth, window.innerHeight );
// 后期渲染更新大小
composer.setSize( window.innerWidth, window.innerHeight );
}
function createGUI() {
//存放有所有需要改变的属性的对象
effectControls = new function () {
this._Saturation = 1;
this._Brightness = 1;
this._Contrast = 1;
}();
const gui = new GUI( { name: 'HSL setting' } );
gui.add( effectControls, '_Brightness', 0, 2 ).step( 0.01 ).onChange(()=>{effect.uniforms[ '_Brightness' ].value = effectControls._Brightness;});
gui.add(effectControls,'_Saturation' , 0, 2 ).step( 0.01 ).onChange(()=>{effect.uniforms[ '_Saturation' ].value = effectControls._Saturation;});
gui.add( effectControls, '_Contrast', 0, 2 ).step( 0.01 ).onChange(()=>{effect.uniforms[ '_Contrast' ].value = effectControls._Contrast;});
gui.add( params, 'enable' );
}
function animate() {
requestAnimationFrame( animate );
// 旋转效果
// object.rotation.x += 0.005;
// object.rotation.y += 0.01;
if ( params.enable ) {
// 后期效果渲染
composer.render();
} else {
// 正常渲染
renderer.render( scene, camera );
}
}
</script>
</body>
</html>
2、BSCShader.js
/**
* RGB Shift Shader
* Shifts red and blue channels from center in opposite directions
* Ported from http://kriss.cx/tom/2009/05/rgb-shift/
* by Tom Butterworth / http://kriss.cx/tom/
*
* amount: shift distance (1 is width of input)
* angle: shift angle in radians
*/
const BSCShader = {
uniforms: {
'tDiffuse': { value: null },
'_Brightness': { value: 1 },
'_Saturation': { value: 1 },
'_Contrast': { value: 1 }
},
vertexShader: /* glsl */`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader: /* glsl */`
uniform sampler2D tDiffuse;
uniform float _Brightness;
uniform float _Saturation;
uniform float _Contrast;
varying vec2 vUv;
vec3 lerpColor(vec3 col1,vec3 col2, float value){
vec3 newCol = vec3 ((col1.r * (1.0 - value) + col2.r * value), (col1.g * (1.0 - value) + col2.g * value), (col1.b * (1.0 - value) + col2.b * value));
return newCol;
}
float mylerp(float a,float b, float value){
return (a * (1.0 - value) + b * value);
}
void main() {
// 获取原图的颜色rgba
vec4 color = texture2D(tDiffuse, vUv);
//brigtness亮度直接乘以一个系数,也就是RGB整体缩放,调整亮度
vec3 finalColor = color.rgb * _Brightness;
//saturation饱和度:首先根据公式计算同等亮度情况下饱和度最低的值:
float gray = 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
vec3 grayColor = vec3(gray, gray, gray);
//根据Saturation在饱和度最低的图像和原图之间差值
finalColor = lerpColor(grayColor, finalColor, _Saturation);
//contrast对比度:首先计算对比度最低的值
vec3 avgColor = vec3(0.5, 0.5, 0.5);
//根据Contrast在对比度最低的图像和原图之间差值
finalColor = lerpColor(avgColor, finalColor, _Contrast);
// 结果rgb,透明度保持原值即可
gl_FragColor = vec4(vec3(finalColor), color.a);
}`
};
export { BSCShader };