xxxxxxxxxx
// RenderNode. ver2.2
// bloomの実験
// bindFBO新しくなってなかった...
// めんどうだからあっちのRenderNode移したらバグ消えたが
// 差分が分からん...
// RenderNode相当更新しました(お騒がせしました)
// resizeも持っていこう
// 何とかできたぞ...!!
// applyBloom(デフォルト:効果適用後)
// brightness(輝度抽出部分のみ)
// blured(ブラーかけた部分のみ)
// 後者の場合はbloomだけ使う
// configの列挙体って===で判定するとバグるの???
// なぜ...
// RenderNodeだいぶ更新したのでそのうちライブラリ化して分離します(やる気があれば)
// fps表示にbloomが適用されないように修正かけたよ
// setTextureで指定する番号は0でいいみたいです
// dither導入したけど正直わからん...まあいいや。
// どちらかというとp5.Textureの内容をtextureをbindすることで自由にいじれるっていうね、
// それが分かったことの方が収穫だわね。REPEATとか、いろいろいじれるわけだ。
// 色構成を白→単色にした
// 白に対する効果も見れるしこの方がいいでしょう
// 20220901
// ディザリングと3Dの共存できたよ
// ditherをdyeより前に読み込ませる必要があるみたい
// というか
// bloom_0もdyeより先じゃないとちらつきが発生しました
// 原因は分からないが応急処置としてこれでいこう
// 20220906
// invert実装
// 20230227
// p5.js 1.6.0にバージョンアップしました
// 20230727
// 1.7.0がwebgl2でfloatTextureの使い方がアレで使えないので
// webgl1にしたうえでバージョンアップしました。
// 2023-10-06
// p5wgexで書き直したいです。
// とりあえず成功。🎉
// 環境(OpenProcessingなど)によってはGUIの無名関数による生成を許していないので、
// 注意してください。これで1時間くらい消えたことあるので。
// linearToGamma使うと若干明るくなるのがわかる
// よくわかんないけど仕事してるのはわかった。外すべきではないね。
// luminasTransparentを使うと輝度で透明度が設定されます
// この処理だとBloomの最終結果が透明度1なんですよね
// それで
// 適切に透明度を設定しないと背景にかぶせたりできないのです
// 2023-10-18
// save実装しました
// lilはsave導入しやすいのでいいですね
// millis()はタイマーに直しておこう...
// ------------------------------------------------------- //
// config.
const MODE_APPLY = 0;
const MODE_BRIGHTNESS = 1;
const MODE_BLURRED = 2;
const BLOOM_RESOLUTION = 256;
const BLOOM_ITERATION = 4;
let config = {
BALL_HUE: 0.55,
BLOOM: true,
BLOOM_INTENSITY: 4.0,
BLOOM_THRESHOLD: 0.6,
BLOOM_SOFT_KNEE: 0.7,
BLOOM_COLOR: [1,1,1],
MODE:MODE_APPLY,
luminasTransparent:true,
backgroundColor:[0.5, 0.35, 0.05],
save: () => { saveFlag = true; } // これでいいらしい
};
function createGUI(){
var gui = new lil.GUI({ width: 280 });
gui.add(config, 'BALL_HUE', 0, 1, 0.01).name('ball_hue');
gui.add(config, 'BLOOM').name('bloom');
gui.add(config, 'BLOOM_INTENSITY', 0, 8, 0.1).name('intensity');
gui.add(config, 'BLOOM_THRESHOLD', 0, 1, 0.1).name('threshold');
gui.add(config, 'BLOOM_SOFT_KNEE', 0, 1, 0.1).name('soft_knee');
gui.addColor(config, 'BLOOM_COLOR').name('bloom_color');
gui.add(config, 'MODE', {'APPLY':MODE_APPLY, 'BRIGHTNESS':MODE_BRIGHTNESS, 'BLURRED':MODE_BLURRED}).name('mode');
gui.add(config, 'luminasTransparent');
gui.addColor(config, 'backgroundColor');
gui.add(config, 'save');
}
// -------------------------------------------------------- //
// global.
const ex = p5wgex;
let _node; // RenderSystemSetにアクセスするためのglobal.
let pfc; // 一応ね
let bullets = [];
let saveFlag = false;
const timer = new ex.Timer();
// ------------------------------------------------------- //
// shaders.
// 基本バーテックスシェーダ
// foxBoard使うのです。
const baseVertexShader =
`#version 300 es
in vec2 aPosition;
out vec2 vUv;
out vec2 vL; // left
out vec2 vR; // right
out vec2 vT; // top
out vec2 vB; // bottom
uniform vec2 uTexelSize;
void main () {
// 0~1の0~1で上下逆なのでTがプラス
vUv = aPosition * 0.5 + 0.5;
vL = vUv - vec2(uTexelSize.x, 0.0);
vR = vUv + vec2(uTexelSize.x, 0.0);
vT = vUv + vec2(0.0, uTexelSize.y);
vB = vUv - vec2(0.0, uTexelSize.y);
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
// simple vertex shader.
// vLやら何やらを使ってない場合はこっちを使いましょう。copyとか使ってないはず。
const simpleVertexShader =
`#version 300 es
in vec2 aPosition;
out vec2 vUv;
void main () {
vUv = aPosition * 0.5 + 0.5;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
// ディスプレイ用
const displayShaderSource =
`#version 300 es
precision highp float;
precision highp sampler2D;
const vec3 luminationVector = vec3(77.0, 151.0, 28.0) / 255.0;
in vec2 vUv;
in vec2 vL;
in vec2 vR;
in vec2 vT;
in vec2 vB;
uniform sampler2D uTexture;
uniform sampler2D uBloom;
uniform int uMode; // 0,1,2
out vec4 fragColor;
// 各種フラ
uniform bool uBloomFlag;
uniform bool uLuminasTransparent;
// リニア→ガンマ
vec3 linearToGamma (vec3 color) { // linearをGammaに変換・・
color = max(color, vec3(0));
return max(1.055 * pow(color, vec3(0.416666667)) - 0.055, vec3(0));
}
// メインコード
void main () {
vec4 tex = texture(uTexture, vUv);
vec3 c = tex.rgb;
vec3 bloom;
bloom = texture(uBloom, vUv).rgb;
bloom = linearToGamma(bloom);
if(uBloomFlag){
c += bloom;
}
vec3 result = c;
if(uMode != 0){ result = bloom; } // 1,2の場合
// 輝度でalphaを決める(そのままだと1なので)
if (uLuminasTransparent) {
tex.a = dot(luminationVector, tex.rgb);
}
fragColor = vec4(result, tex.a);
}
`;
// どうも輝度抽出とかいうのをここでやってるっぽい
// brがbrightness?というかまあc.r,c.g,c.bの最大値。
// これがcurve.xより小さいと0になるのでそれで切ってる?
// わからん.....
const bloomPrefilterShader =
`#version 300 es
precision mediump float;
precision mediump sampler2D;
in vec2 vUv;
uniform sampler2D uTexture;
uniform vec3 uCurve;
uniform float uThreshold;
out vec4 fragColor;
void main () {
vec3 c = texture(uTexture, vUv).rgb;
float br = max(c.r, max(c.g, c.b));
float rq = clamp(br - uCurve.x, 0.0, uCurve.y);
rq = uCurve.z * rq * rq;
c *= max(rq, br - uThreshold) / max(br, 0.0001);
fragColor = vec4(c, 0.0);
}
`;
// bloomのメインシェーダ
const bloomBlurShader =
`#version 300 es
precision mediump float;
precision mediump sampler2D;
in vec2 vL;
in vec2 vR;
in vec2 vT;
in vec2 vB;
uniform sampler2D uTexture;
out vec4 fragColor;
void main () {
vec4 sum = vec4(0.0);
sum += texture(uTexture, vL);
sum += texture(uTexture, vR);
sum += texture(uTexture, vT);
sum += texture(uTexture, vB);
sum *= 0.25;
fragColor = sum;
}
`;
// bloomの仕上げシェーダ
// 最終的にブラーがかかったものができる!ようです!!
const bloomFinalShader =
`#version 300 es
precision mediump float;
precision mediump sampler2D;
in vec2 vL;
in vec2 vR;
in vec2 vT;
in vec2 vB;
uniform sampler2D uTexture;
uniform float uIntensity;
uniform vec3 uBloomColor;
out vec4 fragColor;
void main () {
vec4 sum = vec4(0.0);
sum += texture(uTexture, vL);
sum += texture(uTexture, vR);
sum += texture(uTexture, vT);
sum += texture(uTexture, vB);
sum *= 0.25;
fragColor = sum * uIntensity * vec4(uBloomColor, 1.0);
}
`;
// copyは不要ですね...ただのベタ塗りならもうこれは要らないので。不要です。
// --------------------------------------------------------------- //
// setup.
function setup(){
timer.initialize("slot0");
createCanvas(windowWidth, windowWidth*.75, WEBGL);
pixelDensity(1);
_node = new ex.RenderNode(this._renderer.GL);
createGUI();
// ここでフレームバッファ作るようだよ
initFramebuffers();
// foxBoardでいいよね
_node.registPainter("display", baseVertexShader, displayShaderSource);
_node.registPainter("bloomPrefilter", simpleVertexShader, bloomPrefilterShader);
_node.registPainter("bloomBlur", baseVertexShader, bloomBlurShader);
_node.registPainter("bloomFinal", baseVertexShader, bloomFinalShader);
// textureの用意
_node.registTexture("bg", {src:(function(){
return createGraphics(width, height);
})()});
for(let k=0;k<30;k++){
bullets.push({x:width/2,y:height/2,d:TAU*k/32,seed:k});
}
pfc = new ex.PerformanceChecker(this.canvas);
}
// --------------------------------------- //
// initFramebuffers.
function initFramebuffers(){
// これでいいはず。
_node.registFBO("dye", {
w:width, h:height,
color:{info:{type:"half_float", magFilter:"linear"}}
});
// 番号を使うやり方は柔軟性に乏しいので、
// RenderNodeを新しくする際に破棄しました。
initBloomFramebuffers();
}
// 面倒なのでイテレーション回数を4で固定。
function initBloomFramebuffers(){
let res = getResolution(width, height, BLOOM_RESOLUTION);
_node.registFBO("bloom_0", {
w:res.frameWidth, h:res.frameHeight,
color:{info:{type:"half_float", magFilter:"linear"}}
});
// とりあえず4でいいです。
for(let i = 1; i <= BLOOM_ITERATION; i++){
let fw = (res.frameWidth >> i);
let fh = (res.frameHeight >> i);
_node.registFBO("bloom_" + i, {
w:fw, h:fh,
color:{info:{type:"half_float", magFilter:"linear"}}
});
}
}
// ---------------------------------------------------------------
// main loop.
function draw(){
pfc.update();
_node.clear(config.backgroundColor);
draw2DGraphics();
step();
render();
if (saveFlag) { _node.save({fileName:"bloom_bullets" + Math.floor(timer.getElapsedMillis("slot0"))}); saveFlag = false; }
}
// -----------------------------------------------------------------
// step.
function draw2DGraphics(){
// textureのupdateは関数を渡して楽に実行できます
_node.updateTexture("bg", function(bg){
bg.noStroke();
bg.blendMode(MULTIPLY);
bg.background(250, 240, 230);
bg.blendMode(BLEND);
bg.fill(255, 128, 32);
const t = timer.getElapsed("slot0");
let r = 10;
while(r--){
for(let p of bullets){
p.d += (noise(t, p.seed)-0.5) * TAU/240;
p.x += cos(p.d)*0.5;
p.y += sin(p.d)*0.5;
bg.circle(p.x,p.y,3);
if (p.x < 0 || p.x > width || p.y < 0 || p.y > height) {
p.seed = Math.floor(Math.random()*99);
}
if (p.x < 0) {
p.x = -p.x; p.d = PI-p.d;
}
if (p.x > width) {
p.x = width-p.x; p.d = PI-p.d;
}
if (p.y < 0) {
p.y = -p.y; p.d = -p.d;
}
if (p.y > height) {
p.y = height-p.y; p.d = -p.d;
}
}
}
});
}
function step(){
// ノーブレンドがデフォルトならこれは不要
//gl.disable(gl.BLEND);
// setAttributeをいちいち呼び出さないといけない
// これで何度原因不明のバグに見舞われたか数えきれない
// よくこれでブルームと流体やったわ(すげぇ)
// dyeにbgを貼り付ける処理。renderTextureがあるのでこれでいいですね。
_node.bindFBO("dye");
_node.renderTexture('tex', "bg"); // renderTexture便利ですね...
}
// ---------------------------------------------------------------
// render.
function render(){
applyBloom();
drawDisplay(); // 仕上げ!
}
function applyBloom(){
// dyeを元にしてbloomになんか焼き付ける
// デフォがノンブレンドなら(以下略)
//gl.disable(gl.BLEND);
let res = getResolution(width, height, BLOOM_RESOLUTION);
let knee = config.BLOOM_THRESHOLD * config.BLOOM_SOFT_KNEE + 0.0001;
let curve0 = config.BLOOM_THRESHOLD - knee;
let curve1 = knee * 2;
let curve2 = 0.25 / knee;
// ここの処理texelSize使ってない
// dyeの内容を初期値として埋め込んで
// 輝度抽出なるものを行なっているようです
// clear必須って書いてあるけど多分p5.Textureの仕様との競合で
// 良からぬことが起きていて
// それを防ぐための処理だと思うんですけど
// とりあえず無視して
// あとで不具合が起きたら対処しましょ
// どうせいくらでもやり直せるんだ
// bloom_0はclearで毎フレーム初期化しないとまずいですね...
// ノンブレンドで描画している限り不要のはずだけど、あったほうが行儀はいいかもしれない。
_node.bindFBO("bloom_0");
_node.clear();
_node.use('bloomPrefilter', 'foxBoard')
.setFBOtexture2D('uTexture', 'dye')
.setUniform('uCurve', [curve0, curve1, curve2])
.setUniform('uThreshold', config.BLOOM_THRESHOLD)
.drawArrays("triangle_strip")
.unbind();
// ここで切ってどんな処理なのか確認したいわね
if(config.MODE == MODE_BRIGHTNESS){ return; }
_node.use('bloomBlur', 'foxBoard');
// まず0→1, 1→2, ..., N-1→Nとする
for(let i = 1; i <= BLOOM_ITERATION; i++){
// i-1→iって感じ
const w = (res.frameWidth >> (i-1));
const h = (res.frameHeight >> (i-1));
_node.bindFBO('bloom_' + i);
_node.setUniform('uTexelSize', [1/w, 1/h])
.setFBOtexture2D('uTexture', 'bloom_' + (i-1))
.drawArrays("triangle_strip")
.unbind();
}
// 一時的にone,oneのblendにする
// 次にN→N-1,...,2→1とする。
for(let i = BLOOM_ITERATION; i >= 2; i--){
const w = (res.frameWidth >> i);
const h = (res.frameHeight >> i);
_node.bindFBO('bloom_' + (i-1));
_node.setUniform('uTexelSize', [1/w, 1/h])
.setFBOtexture2D('uTexture', 'bloom_' + i)
.drawArrays("triangle_strip", {blend:["one", "one"]})
.unbind();
}
// 最後に1→0でおしまい
const w1 = (res.frameWidth >> 1);
const h1 = (res.frameHeight >> 1);
// coulourは柔軟なのでだいたい何でもありです...
// この場合おそらく0~1で3つのRGB値が入ってるのでそれをあれしてる感じですね
const col = config.BLOOM_COLOR;
_node.bindFBO('bloom_0');
_node.use('bloomFinal', 'foxBoard')
.setFBOtexture2D('uTexture', 'bloom_1')
.setUniform('uTexelSize', [1/w1, 1/h1])
.setUniform('uIntensity', config.BLOOM_INTENSITY * (0.5 + 0.5 * Math.cos(Math.PI*0.4*timer.getElapsed("slot0"))))
.setUniform('uBloomColor', ex.coulour(col))
.drawArrays("triangle_strip")
.unbind();
// bloomについては以上。
}
function drawDisplay(){
// スクリーンへの描画
_node.bindFBO(null);
_node.use('display', 'foxBoard')
.setFBOtexture2D('uTexture', 'dye')
.setUniform('uBloomFlag', config.BLOOM)
.setUniform('uLuminasTransparent', config.luminasTransparent)
.setUniform('uMode', config.MODE)
.setFBOtexture2D('uBloom', 'bloom_0')
.drawArrays("triangle_strip", {blend:"blend"})
.unbind()
.flush();
}
// ----------------------------------------------------------//
// utility for bloom.
// ちょっとした工夫
// 2べきに合わせるということらしい
// 短い方がresolutionで長い方がそれに長/短を掛ける形
// たとえば800x640の場合、
// aspectRatio=1.25ですよね。で、resolutionが256だとすると、
// _minは256で、_maxは256*1.25で320となるわけ。320, 256が正しいわけ。
// 逆に160x480とかだとどうなるかというと、aspectRatioが1/3なので、3になって、
// 256と256x3で、つまり256, 768になるってわけ。小さい方が256になるように
// 大きさを決めているのですね。
// ざっくり言うと???
// w>h... 256*(w/h), 256です!
// w<=h... 256, 256*(h/w)です!
// ...それでいいじゃん.....
function getResolution(w, h, res){
if (w > h) {
return {frameWidth:res*(w/h), frameHeight:res};
}
return {frameWidth:res, frameHeight:res*(h/w)};
}