-
-
Save kobitoDevelopment/d2022be2d32b0b49aadd33bf9c16f50c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #canvas { | |
| display: block; | |
| width: 100%; | |
| height: 100vh; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <div> | |
| <span>進捗率</span> | |
| <span class="progress"></span> | |
| </div> | |
| <div> | |
| <span>マウス座標</span> | |
| <span class="mousePosition"></span> | |
| </div> | |
| <button class="trigger">スタート</button> | |
| <canvas id="canvas"></canvas> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* ====================================== | |
| 線形補完用の関数 | |
| ======================================= */ | |
| // (加減, 上限, 割合)を渡して、下限と上限の間の割合%にあたる数値がどの値にあたるかを返す | |
| function lerp(x, y, a) { | |
| return (1 - a) * x + a * y; | |
| } | |
| // (加減, 上限, 現在の値)を渡して、現在の値が下限と上限の間を0~1とした場合にどの値にあたるかを返す | |
| function norm(v, a, b) { | |
| return (v - a) / (b - a); | |
| } | |
| /* ====================================== | |
| マウス、タッチ座標をシェーダー内で使うための準備 | |
| ======================================= */ | |
| // マウスの座標 | |
| const mouse = { | |
| current: { | |
| x: 0, | |
| y: 0, | |
| }, | |
| last: { | |
| x: 0, | |
| y: 0, | |
| }, | |
| inertia: { | |
| x: 0, | |
| y: 0, | |
| }, | |
| velocity: { | |
| x: 0, | |
| y: 0, | |
| }, | |
| }; | |
| // マウス座標を監視する関数 | |
| const mousemove = function (event) { | |
| mouse.last.x = mouse.current.x; | |
| mouse.last.y = mouse.current.y; | |
| mouse.current.x = event.offsetX; | |
| mouse.current.y = event.offsetY; | |
| mouse.velocity.x = (mouse.current.x - mouse.last.x) / 16; | |
| mouse.velocity.y = (mouse.current.y - mouse.last.y) / 16; | |
| // マウス移動時にも波紋を追加 | |
| if (mouse.current.x !== 0 || mouse.current.y !== 0) { | |
| addRippleAt(mouse.current.x, mouse.current.y); | |
| } | |
| }; | |
| // タッチ座標を監視する関数 | |
| const touchmove = function (event) { | |
| const rect = event.target.getBoundingClientRect(); | |
| const offsetX = event.touches[0].clientX - window.pageXOffset - rect.left; | |
| const offsetY = event.touches[0].clientY - window.pageYOffset - rect.top; | |
| mouse.last.x = mouse.current.x; | |
| mouse.last.y = mouse.current.y; | |
| mouse.current.x = offsetX; | |
| mouse.current.y = offsetY; | |
| mouse.velocity.x = (mouse.current.x - mouse.last.x) / 16; | |
| mouse.velocity.y = (mouse.current.y - mouse.last.y) / 16; | |
| }; | |
| /* マウスとタッチの座標を監視 */ | |
| document.getElementById("canvas").addEventListener("mousemove", mousemove); | |
| document.getElementById("canvas").addEventListener("touchmove", touchmove); | |
| /* 波紋を追加する関数 */ | |
| function addRippleAt(x, y) { | |
| if (!gl || !programInfoRippleDrop) return; | |
| // 現在の読み込み用バッファの内容を書き込み用バッファにコピー | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[bufferWriteIndex].framebuffer); | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| gl.useProgram(programInfoRippleDrop.program); | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, framebuffers[bufferReadIndex].texture); | |
| const uTexturePrevFrameDrop = gl.getUniformLocation(programInfoRippleDrop.program, "uTexturePrevFrame"); | |
| const uDropCenter = gl.getUniformLocation(programInfoRippleDrop.program, "uDropCenter"); | |
| const uDropRadius = gl.getUniformLocation(programInfoRippleDrop.program, "uDropRadius"); | |
| const uDropStrength = gl.getUniformLocation(programInfoRippleDrop.program, "uDropStrength"); | |
| // 座標正規化(-1から1の範囲に変換) | |
| const elWidth = gl.canvas.width; | |
| const elHeight = gl.canvas.height; | |
| const normalizedX = (2 * x) / elWidth - 1.0; | |
| const normalizedY = 1.0 - (2 * y) / elHeight; | |
| const radius = 20; | |
| const longestSide = Math.max(elWidth, elHeight); | |
| const normalizedRadius = radius / longestSide; | |
| gl.uniform1i(uTexturePrevFrameDrop, 0); | |
| gl.uniform2fv(uDropCenter, [normalizedX, normalizedY]); | |
| gl.uniform1f(uDropRadius, normalizedRadius); | |
| gl.uniform1f(uDropStrength, 0.01); | |
| gl.drawArrays(gl.TRIANGLES, 0, programInfoRippleDrop.verticeNum); | |
| // バッファを切り替え | |
| bufferWriteIndex = 1 - bufferWriteIndex; | |
| bufferReadIndex = 1 - bufferReadIndex; | |
| } | |
| document.getElementById("canvas").addEventListener("mousedown", function (event) { | |
| addRippleAt(event.offsetX, event.offsetY); | |
| }); | |
| document.getElementById("canvas").addEventListener("touchstart", function (event) { | |
| const rect = event.target.getBoundingClientRect(); | |
| const offsetX = event.touches[0].clientX - window.pageXOffset - rect.left; | |
| const offsetY = event.touches[0].clientY - window.pageYOffset - rect.top; | |
| addRippleAt(offsetX, offsetY); | |
| }); | |
| /* ====================================== | |
| リサイズ対応 | |
| ======================================= */ | |
| function resize(resizedCanvas) { | |
| canvasInfo = canvas.getBoundingClientRect(); | |
| canvas.width = canvasInfo.width; | |
| canvas.height = canvasInfo.height; | |
| resizedCanvas.viewportWidth = canvas.width; | |
| resizedCanvas.viewportHeight = canvas.height; | |
| } | |
| /* ====================================== | |
| 再帰実行するアニメーション用の準備 | |
| ======================================= */ | |
| /* アニメーション管理位用ID */ | |
| let animationID; | |
| /* tweenさせたいデータを格納 */ | |
| const tweenData = { | |
| num: 0, | |
| }; | |
| /* tweenのイージング関数(linearにあたる関数で等速) */ | |
| function easeInOutQuart(x) { | |
| return x < 0.5 ? 8 * x * x * x * x : 1 - Math.pow(-2 * x + 2, 4) / 2; | |
| } | |
| function tween(obj, from, value, duration, easing) { | |
| let start = null; | |
| function step(timestamp) { | |
| if (!start) { | |
| start = timestamp; | |
| } | |
| let progress = timestamp - start; | |
| obj.num = from + easing(progress / duration) * (value - from); | |
| if (progress < duration) { | |
| animationID = requestAnimationFrame(step); | |
| } else { | |
| obj.num = value; | |
| } | |
| } | |
| animationID = requestAnimationFrame(step); | |
| } | |
| const trigger = document.querySelector(".trigger"); | |
| trigger.addEventListener("click", function () { | |
| tween(tweenData, 0, 1.0, 2000, easeInOutQuart); | |
| }); | |
| /* ====================================== | |
| WebGLを実行するためのデータを格納する変数を準備 | |
| ======================================= */ | |
| let gl; // webGLを実行するためのデータを格納する変数 | |
| let canvasInfo; | |
| /* フレームバッファ関連の変数 */ | |
| let framebuffers = []; // 2つのフレームバッファを格納 | |
| let bufferWriteIndex = 0; // 書き込み用バッファのインデックス | |
| let bufferReadIndex = 1; // 読み込み用バッファのインデックス | |
| let programInfoRipple; // ripple更新用プログラム情報 | |
| let programInfoRippleDrop; // ripple追加用プログラム情報 | |
| /* ====================================== | |
| webGLを実行するための全ての処理をまとめた関数 | |
| ======================================= */ | |
| function startup() { | |
| let canvas = document.querySelector("#canvas"); // webGLを実行するためのcanvas要素を指定 | |
| canvasInfo = canvas.getBoundingClientRect(); // canvas要素の位置とサイズを取得 | |
| gl = createGLContext(canvas); // 上記で取得した要素にwebGL実行環境を実装 | |
| canvas.width = canvasInfo.width; // canvasの横幅を使用できるよう格納 | |
| canvas.height = canvasInfo.height; // canvasの縦幅を使用できるよう格納 | |
| /* 2つのフレームバッファを作成 */ | |
| for (let i = 0; i < 2; i++) { | |
| framebuffers.push(createFramebuffer(gl, gl.canvas.width, gl.canvas.height)); | |
| } | |
| /* フレームバッファを初期化(透明な黒でクリア) */ | |
| for (let i = 0; i < 2; i++) { | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[i].framebuffer); | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| gl.clearColor(0, 0, 0, 0); | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| } | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
| /* メインのシェーダーを準備 */ | |
| const shaderProgram = setupShaders(vertexShaderMain, fragmentShaderMain); | |
| const programInfo = { | |
| program: shaderProgram, | |
| attribLocations: { | |
| position: gl.getAttribLocation(shaderProgram, "aVertexPosition"), | |
| color: gl.getAttribLocation(shaderProgram, "aVertexColor"), | |
| texCoordLocation: gl.getAttribLocation(shaderProgram, "aTexCoord"), | |
| }, | |
| }; | |
| /* ripple追加用のシェーダーを準備 */ | |
| const shaderProgramRippleDrop = setupShaders(vertexShaderMain, fragmentShaderRippleDrop); | |
| programInfoRippleDrop = { | |
| program: shaderProgramRippleDrop, | |
| attribLocations: { | |
| position: gl.getAttribLocation(shaderProgramRippleDrop, "aVertexPosition"), | |
| color: gl.getAttribLocation(shaderProgramRippleDrop, "aVertexColor"), | |
| texCoordLocation: gl.getAttribLocation(shaderProgramRippleDrop, "aTexCoord"), | |
| }, | |
| }; | |
| programInfoRippleDrop.verticeNum = setupBuffers(programInfoRippleDrop); | |
| /* ripple更新用のシェーダーを準備 */ | |
| const shaderProgramRipple = setupShaders(vertexShaderMain, fragmentShaderRippleUpdate); | |
| programInfoRipple = { | |
| program: shaderProgramRipple, | |
| attribLocations: { | |
| position: gl.getAttribLocation(shaderProgramRipple, "aVertexPosition"), | |
| color: gl.getAttribLocation(shaderProgramRipple, "aVertexColor"), | |
| texCoordLocation: gl.getAttribLocation(shaderProgramRipple, "aTexCoord"), | |
| }, | |
| }; | |
| programInfoRipple.verticeNum = setupBuffers(programInfoRipple); | |
| programInfo.verticeNum = setupBuffers(programInfo); // 頂点バッファーのセットアップと頂点数の取得 | |
| // 使用するテクスチャをそれぞれロードしてバインドする | |
| texture1 = loadTexture(loadedImageArray[0]); | |
| gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); // WebGLのコンテキストとキャンパスのサイズを同じにする。 | |
| gl.useProgram(programInfo.program); // WebGLのコンテキストが使用するプログラムを指定 | |
| draw(programInfo); | |
| } | |
| /* ====================================== | |
| 画像の読み込みを待機、ロード完了後にPromise を返却する関数 | |
| ======================================= */ | |
| function loadImage(imagePath) { | |
| return new Promise((resolve) => { | |
| const img = new Image(); | |
| img.src = imagePath; | |
| img.addEventListener("load", function () { | |
| return resolve(img); | |
| }); | |
| }); | |
| } | |
| /* ====================================== | |
| テクスチャとして使用する全ての画像のロードが完了したら実行 | |
| ======================================= */ | |
| const imagesPath = ["assets/images/1.jpg"]; | |
| let loadedImageArray = []; | |
| /* 使用するテクスチャの数だけ変数を準備 */ | |
| let texture1; | |
| const requestsImages = imagesPath.map(function (imagePath) { | |
| return loadImage(imagePath); | |
| }); | |
| async function startWebGL() { | |
| await Promise.all(requestsImages).then(function (images) { | |
| loadedImageArray = images; | |
| }); | |
| startup(); | |
| } | |
| startWebGL(); | |
| /* ====================================== | |
| WebGLを実行する場所(コンテキスト)を作成する関数 | |
| ======================================= */ | |
| function createGLContext(canvas) { | |
| let ctx = canvas.getContext("webgl"); // webGLの実行コンテキストを取得 | |
| if (ctx) { | |
| ctx.viewportWidth = canvas.width; // コンテキストの横幅をcanvasの横幅と一致させる | |
| ctx.viewportHeight = canvas.height; // コンテキストの縦幅をcanvasの横幅と一致させる | |
| } else { | |
| console.log("webGLを実行できません。"); | |
| } | |
| return ctx; | |
| } | |
| /* ====================================== | |
| シェーダーコードを読み込む関数 開始 | |
| ====================================== */ | |
| // fragment、またはvertexのシェーダータイプを指定して呼び出す | |
| function loadShader(type, shaderSource) { | |
| const shader = gl.createShader(type); | |
| gl.shaderSource(shader, shaderSource); // シェーダーコードをバイナリコードにコンパイル(解析の際、シェーダーコードにindexが付与される) | |
| gl.compileShader(shader); | |
| if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
| alert("シェーダーの作成に失敗しました。\n" + gl.getShaderInfoLog(shader)); | |
| gl.deleteShader(shader); | |
| return null; | |
| } | |
| return shader; | |
| } | |
| /* ===================================== | |
| オフスクリーンレンダリング用にフレームバッファを作成する関数 | |
| ====================================== */ | |
| function createFramebuffer(gl, width, height) { | |
| const framebuffer = gl.createFramebuffer(); // フレームバッファ | |
| const depthRenderBuffer = gl.createRenderbuffer(); // レンダーバッファ | |
| const texture = gl.createTexture(); // テクスチャ | |
| // フレームバッファをバインド | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); | |
| // レンダーバッファをバインド | |
| gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer); | |
| // レンダーバッファを深度バッファとして設定する | |
| gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); | |
| // フレームバッファにレンダーバッファを関連付けする | |
| gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderBuffer); | |
| // テクスチャをユニット0にバインド | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, texture); | |
| // テクスチャにサイズなどを設定する(ただし中身は null) | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); | |
| // テクスチャパラメータを設定 | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| // フレームバッファにテクスチャを関連付けする | |
| gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); | |
| // すべてのオブジェクトは念の為バインドを解除しておく | |
| gl.bindTexture(gl.TEXTURE_2D, null); | |
| gl.bindRenderbuffer(gl.RENDERBUFFER, null); | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
| // 各オブジェクトを、JavaScript のオブジェクトに格納して返す | |
| return { | |
| framebuffer: framebuffer, | |
| depthRenderbuffer: depthRenderBuffer, | |
| texture: texture, | |
| }; | |
| } | |
| /* ====================================== | |
| vertexShaderとfragmentShaderを記述 | |
| ====================================== */ | |
| /* メインのvertexShader */ | |
| const vertexShaderMain = ` | |
| precision mediump float; // precision...精度修飾子。どの程度の精度でデータを扱うか(law mideun high があり、highpにすると負荷が高くなる) | |
| /* | |
| attribute... 頂点ごとに異なるデータを受け取る変数(バッファから座標などの情報を取得) | |
| uniform...全頂点で共通の値を持つデータを宣言(取得) | |
| */ | |
| attribute vec2 aVertexPosition; // aVertexPosition...頂点情報の座標を表すattribute | |
| attribute vec3 aVertexColor; // aVertextColor...頂点情報の色を表すattribute | |
| attribute vec2 aTexCoord; // 画像を描画するためのテクスチャ座標 | |
| /* | |
| varying... vertexShaderからfragmentShaderへ変数を渡す | |
| */ | |
| varying vec2 vTexCoord; // テクスチャ座標をfragmentShaderへ渡す | |
| varying vec2 vVertexPosition; // 頂点座標をfragmentShaderへ渡す | |
| varying vec3 vVertexColor; // 頂点に設定した色をfragmentShaderへ渡す | |
| /* | |
| void main(){}に書いた処理が実行される(これ以前に、main内で使用する関数・変数の定義は可能) | |
| */ | |
| void main() { | |
| vTexCoord = aTexCoord; | |
| vVertexPosition = aVertexPosition; | |
| vVertexColor = aVertexColor; | |
| gl_Position = vec4(aVertexPosition, 0.0, 1.0); | |
| } | |
| `; | |
| /* メインのfragmentShader. */ | |
| const fragmentShaderMain = ` | |
| precision highp float; | |
| uniform sampler2D uTexture1; // テクスチャ画像1枚目 | |
| uniform sampler2D uTexturePrevFrame; // 前フレームの描画結果を保持するテクスチャ | |
| uniform vec2 uResolution; // 画面幅(w,h) | |
| varying vec2 vTexCoord; // テクスチャ座標 | |
| void main() { | |
| /* 1枚目のテクスチャ(1.jpg)にripple効果を適用 */ | |
| vec4 rippleInfo = texture2D(uTexturePrevFrame, vTexCoord); | |
| // rippleの高さ情報から歪み計算 | |
| float height = rippleInfo.r; | |
| vec2 delta = vec2(1.0 / uResolution.x, 1.0 / uResolution.y); | |
| float heightX = texture2D(uTexturePrevFrame, vTexCoord + vec2(delta.x, 0.0)).r; | |
| float heightY = texture2D(uTexturePrevFrame, vTexCoord + vec2(0.0, delta.y)).r; | |
| // 法線ベクトルを計算 | |
| vec3 dx = vec3(delta.x, heightX - height, 0.0); | |
| vec3 dy = vec3(0.0, heightY - height, delta.y); | |
| vec2 offset = -normalize(cross(dy, dx)).xz; | |
| // 歪み強度 | |
| float perturbance = 0.03; | |
| vec2 distortedCoord = vTexCoord + offset * perturbance; | |
| // 1.jpgに歪み効果を適用(Y軸反転) | |
| vec2 flippedDistortedCoord = vec2(distortedCoord.x, 1.0 - distortedCoord.y); | |
| gl_FragColor = texture2D(uTexture1, flippedDistortedCoord); | |
| // スペキュラーハイライトを追加 | |
| float specular = pow(max(0.0, dot(offset, normalize(vec2(-0.6, 1.0)))), 4.0); | |
| gl_FragColor.rgb += specular; | |
| } | |
| `; | |
| /* 波紋追加用のfragmentShader */ | |
| const fragmentShaderRippleDrop = ` | |
| precision highp float; | |
| const float PI = 3.141592653589793; | |
| uniform sampler2D uTexturePrevFrame; | |
| uniform vec2 uDropCenter; | |
| uniform float uDropRadius; | |
| uniform float uDropStrength; | |
| varying vec2 vTexCoord; | |
| void main() { | |
| vec4 info = texture2D(uTexturePrevFrame, vTexCoord); | |
| float drop = max(0.0, 1.0 - length(uDropCenter * 0.5 + 0.5 - vTexCoord) / uDropRadius); | |
| drop = 0.5 - cos(drop * PI) * 0.5; | |
| info.r += drop * uDropStrength; | |
| gl_FragColor = info; | |
| } | |
| `; | |
| /* 波紋更新用のfragmentShader */ | |
| const fragmentShaderRippleUpdate = ` | |
| precision highp float; | |
| uniform sampler2D uTexturePrevFrame; | |
| uniform vec2 uResolution; | |
| varying vec2 vTexCoord; | |
| void main() { | |
| vec4 info = texture2D(uTexturePrevFrame, vTexCoord); | |
| vec2 delta = vec2(1.0 / uResolution.x, 1.0 / uResolution.y); | |
| // 隣接ピクセルの高さを取得 | |
| float average = ( | |
| texture2D(uTexturePrevFrame, vTexCoord - vec2(delta.x, 0.0)).r + | |
| texture2D(uTexturePrevFrame, vTexCoord - vec2(0.0, delta.y)).r + | |
| texture2D(uTexturePrevFrame, vTexCoord + vec2(delta.x, 0.0)).r + | |
| texture2D(uTexturePrevFrame, vTexCoord + vec2(0.0, delta.y)).r | |
| ) * 0.25; | |
| // 波動方程式 | |
| info.g += (average - info.r) * 2.0; | |
| info.g *= 0.995; // 減衰 | |
| info.r += info.g; | |
| gl_FragColor = info; | |
| } | |
| `; | |
| /* ====================================== | |
| シェーダーコードをWebGLコンテキストにバインド | |
| ====================================== */ | |
| function setupShaders(vertexShaderSource, fragmentShaderSource) { | |
| /* ===================================== | |
| シェーダーを読み込む 開始 | |
| ====================================== */ | |
| const vertexShader = loadShader(gl.VERTEX_SHADER, vertexShaderSource); // vertexShaderの読み込み(第一引数にシェーダータイプ、第二引数にシェーダーコード) | |
| const fragmentShader = loadShader(gl.FRAGMENT_SHADER, fragmentShaderSource); // fragmentShaderの読み込み(第一引数にシェーダータイプ、第二引数にシェーダーコード) | |
| /* ===================================== | |
| webGLプログラムオブジェクトを作成 開始 | |
| ====================================== */ | |
| const shaderProgram = gl.createProgram(); // シェーダープログラムを作成 | |
| gl.attachShader(shaderProgram, vertexShader); // プログラムにシェーダーをバインド(追加) | |
| gl.attachShader(shaderProgram, fragmentShader); // vertexシェーダーとfragmentシェーダーをリンク(varying等でデータを受け渡せるように) | |
| gl.linkProgram(shaderProgram); | |
| if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { | |
| alert("シェーダーセットアップに失敗しました。\n環境による問題が発生しています。"); | |
| } | |
| return shaderProgram; | |
| } | |
| /* ===================================== | |
| テクスチャを読み込む関数 | |
| ====================================== */ | |
| function loadTexture(info) { | |
| const texture = gl.createTexture(); // テクスチャオブジェクトを作成する | |
| gl.bindTexture(gl.TEXTURE_2D, texture); // テクスチャオブジェクトをバインドする | |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, info); // 画像データをテクスチャに設定する | |
| // テクスチャのラップモードを設定する | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
| // テクスチャのフィルタリングモードを設定する | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); | |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); | |
| // ピクセルの格納方法を設定する(画像のY軸の反転を無効化する) | |
| gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); | |
| return texture; | |
| } | |
| /* ===================================== | |
| バッファーのセットアップとテクスチャのロード | |
| ====================================== */ | |
| function setupBuffers(pInfo) { | |
| /* 頂点を設定 */ | |
| const vertexPositionBuffer = gl.createBuffer(); // 頂点座標のバッファーを作成する | |
| const verticeNum = 6; // 頂点の数 | |
| const triangleVertices = [-1, -1, 1, -1, 1, 1, 1, 1, -1, 1, -1, -1]; // 頂点座標(-1 から 1の間、原点は中心(0,0)) | |
| gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer); // ARRAY_BUFFERに頂点データを格納するバッファを紐づける | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(triangleVertices), gl.STATIC_DRAW); // ARRAY_BUFFERバッファに頂点データをロードする | |
| gl.vertexAttribPointer(pInfo.attribLocations.position, 2, gl.FLOAT, false, 0, 0); // シェーダーコード内から抽出された属性(attribute)と上記でアップした頂点データを紐づける | |
| gl.enableVertexAttribArray(pInfo.attribLocations.position); // 属性を有効化する | |
| // テクスチャ座標のバッファーを作成する(フレームバッファ用にY軸を統一) | |
| const imageVertices = [0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0]; // テクスチャ座標(フレームバッファ用) | |
| const texCoordBuffer = gl.createBuffer(); | |
| // テクスチャ座標バッファーをバインドし、データを設定する | |
| gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer); // ARRAY_BUFFERにテクスチャ座標データを格納するバッファを紐づける | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(imageVertices), gl.STATIC_DRAW); // 画像を表示させる領域を頂点で囲む | |
| gl.enableVertexAttribArray(pInfo.attribLocations.texCoordLocation); // 属性を有効化する | |
| gl.vertexAttribPointer(pInfo.attribLocations.texCoordLocation, 2, gl.FLOAT, false, 0, 0); //シェーダーコード内から抽出された属性(attribute)と上記でアップしたテクスチャ座秒データを紐づける | |
| /* 頂点の色を設定する場合 */ | |
| // 頂点の色 | |
| const colorVertices = [1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1]; | |
| const vertexColorBuffer = gl.createBuffer(); // 位置を管理する頂点の入れ物(バッファ)を作成 | |
| gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer); // ARRAY_BUFFERに頂点データを格納するバッファを紐づける | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colorVertices), gl.STATIC_DRAW); // ARRAY_BUFFERバッファに頂点データをロードする | |
| gl.vertexAttribPointer(pInfo.attribLocations.color, colorVertices.length / verticeNum, gl.FLOAT, false, 0, 0); // シェーダーコード内から抽出された属性(attribute)と上記でアップした頂点データを紐づける | |
| gl.enableVertexAttribArray(pInfo.attribLocations.color); // 属性を有効化する | |
| return verticeNum; // 頂点数を返す | |
| } | |
| function draw(pInfo) { | |
| // シェーダー内で使うuniformを設定 | |
| const uResolutionLoc = gl.getUniformLocation(pInfo.program, "uResolution"); | |
| const uTextureLoc1 = gl.getUniformLocation(pInfo.program, "uTexture1"); | |
| const uTexturePrevFrame = gl.getUniformLocation(pInfo.program, "uTexturePrevFrame"); | |
| /* ====================================== | |
| requestAnimationFrameを再起実行 | |
| ======================================= */ | |
| function animate(timestamp) { | |
| /* リサイズ対応 */ | |
| resize(gl); | |
| /* ステップ2: 波紋更新シェーダーで波紋を拡散(毎フレーム実行) */ | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[bufferWriteIndex].framebuffer); | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| gl.useProgram(programInfoRipple.program); | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, framebuffers[bufferReadIndex].texture); | |
| const uTexturePrevFrameRipple = gl.getUniformLocation(programInfoRipple.program, "uTexturePrevFrame"); | |
| const uResolutionRipple = gl.getUniformLocation(programInfoRipple.program, "uResolution"); | |
| gl.uniform1i(uTexturePrevFrameRipple, 0); | |
| gl.uniform2fv(uResolutionRipple, [gl.canvas.width, gl.canvas.height]); | |
| gl.drawArrays(gl.TRIANGLES, 0, programInfoRipple.verticeNum); | |
| // 波紋更新後にバッファを入れ替え(sample2と同じタイミング) | |
| bufferWriteIndex = 1 - bufferWriteIndex; | |
| bufferReadIndex = 1 - bufferReadIndex; | |
| /* ステップ3: メインシェーダーで画面に最終描画 */ | |
| gl.bindFramebuffer(gl.FRAMEBUFFER, null); | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| gl.clearColor(0, 0, 0, 1); | |
| gl.clear(gl.COLOR_BUFFER_BIT); | |
| gl.useProgram(pInfo.program); | |
| gl.activeTexture(gl.TEXTURE0); | |
| gl.bindTexture(gl.TEXTURE_2D, texture1); | |
| gl.activeTexture(gl.TEXTURE1); | |
| gl.bindTexture(gl.TEXTURE_2D, framebuffers[bufferReadIndex].texture); | |
| gl.uniform1i(uTextureLoc1, 0); | |
| gl.uniform1i(uTexturePrevFrame, 1); | |
| gl.uniform2fv(uResolutionLoc, [gl.canvas.width, gl.canvas.height]); | |
| gl.drawArrays(gl.TRIANGLES, 0, pInfo.verticeNum); | |
| window.requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment