Files
LOLv333/js/cuteTexture.js
T

274 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const DEFAULT_VERTEX_SHADER = `
attribute float aVertexIndex;
varying highp vec2 vTextureCoord;
// This vertex shader produces the following, when drawn using indices 0..3:
//
// 1 | 0-----x.....2
// 0 | | s | . ´
// -1 | x_____x´
// -2 | : .´
// -3 | 1´
// +---------------
// -1 0 1 2 3
//
// The axes are clip-space x and y. The region marked s is the visible region.
// The digits in the corners of the right-angled triangle are the vertex
// indices.
//
// The top-left has UV 0,0, the bottom-left has 0,2, and the top-right has 2,0.
// This means that the UV gets interpolated to 1,1 at the bottom-right corner
// of the clip-space rectangle that is at 1,-1 in clip space.
void main() {
vec2 uv = vec2(floor(aVertexIndex / 2.0), floor(mod(aVertexIndex, 2.0))) * 2.0;
gl_Position = vec4(uv * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0);
vTextureCoord = uv;
}
`
const FRAGMENT_SHADER_UTILITIES = `
highp float noise1(highp vec2 co){
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}
// Color spaces from https://github.com/tobspr/GLSL-Color-Spaces/blob/master/ColorSpaces.inc.glsl
const highp float HCV_EPSILON = 1e-10;
const highp float HSL_EPSILON = 1e-10;
highp vec3 hue_to_rgb(highp float hue)
{
highp float R = abs(hue * 6.0 - 3.0) - 1.0;
highp float G = 2.0 - abs(hue * 6.0 - 2.0);
highp float B = 2.0 - abs(hue * 6.0 - 4.0);
return clamp(vec3(R,G,B), vec3(0), vec3(1));
}
// Converts from HSL to linear RGB
highp vec3 hsl_to_rgb(highp vec3 hsl)
{
highp vec3 rgb = hue_to_rgb(hsl.x);
highp float C = (1.0 - abs(2.0 * hsl.z - 1.0)) * hsl.y;
return (rgb - 0.5) * C + hsl.z;
}
// Converts a value from linear RGB to HCV (Hue, Chroma, Value)
highp vec3 rgb_to_hcv(highp vec3 rgb)
{
// Based on work by Sam Hocevar and Emil Persson
highp vec4 P = (rgb.g < rgb.b) ? vec4(rgb.bg, -1.0, 2.0/3.0) : vec4(rgb.gb, 0.0, -1.0/3.0);
highp vec4 Q = (rgb.r < P.x) ? vec4(P.xyw, rgb.r) : vec4(rgb.r, P.yzx);
highp float C = Q.x - min(Q.w, Q.y);
highp float H = abs((Q.w - Q.y) / (6.0 * C + HCV_EPSILON) + Q.z);
return vec3(H, C, Q.x);
}
// Converts from linear rgb to HSL
highp vec3 rgb_to_hsl(highp vec3 rgb)
{
highp vec3 HCV = rgb_to_hcv(rgb);
highp float L = HCV.z - HCV.y * 0.5;
highp float S = HCV.y / (1.0 - abs(L * 2.0 - 1.0) + HSL_EPSILON);
return vec3(HCV.x, S, L);
}
highp vec3 hue_rotate(highp vec3 rgb_input, highp float amount) {
highp vec3 hsl = rgb_to_hsl(rgb_input);
hsl.x = mod(hsl.x+amount,1.0);
return hsl_to_rgb(hsl);
}
`
const FRAGMENT_SHADER_UTILITIES_N_LINES = FRAGMENT_SHADER_UTILITIES.split(/\n/g).length
const DEFAULT_FRAGMENT_SHADER = `
// Some random values computed for each frame
uniform highp vec4 uRandom;
// Size (in logical pixel) of current CuteTexture canvas
uniform lowp vec2 uCanvasSize;
// Number of seconds since canvas was initialized
uniform lowp float uTime;
// Coordinates of current pixel being computed from [0,0] (top left) to [1,1] (bottom right)
varying highp vec2 vTextureCoord;
void main() {
gl_FragColor = vec4(vTextureCoord.x, vTextureCoord.y, 0.0, 1.0);
}
`
/**
* Initialise un canvas d'arrière plan "CuteTexture" avec les options données
*/
export function initCuteTexture(){
/** @type {HTMLCanvasElement} */
const targetCanvas = document.querySelector("canvas[data-cute-texture]") || document.querySelector("canvas.cute-texture") || document.querySelector("canvas");
if(!targetCanvas)
throw new Error(`Aucun élément <canvas> trouvé sur la page`)
{
let computedStyle = getComputedStyle(targetCanvas)
let elementWidth = parseFloat(computedStyle.width)
let elementHeight = parseFloat(computedStyle.height)
targetCanvas.width = elementWidth
targetCanvas.height = elementHeight
}
const ctx = targetCanvas.getContext("webgl");
if(ctx == null)
throw new Error("WebGL n'ext pas supporté sur votre machine")
let mainShader = initMainShader(ctx)
console.log("Shader CuteTexture initialisé", mainShader)
const initTime = Date.now()
function startRender(){
renderCuteTexture(targetCanvas, mainShader, initTime)
requestAnimationFrame(startRender)
}
startRender()
return {
target: targetCanvas,
initTime,
resize: function(width, height) {
targetCanvas.width = width
targetCanvas.height = height
}
}
}
/**
* @param {WebGLRenderingContext} ctx
*/
function initMainShader(ctx, oldShader=null){
const vertexShader = ctx.createShader(ctx.VERTEX_SHADER)
ctx.shaderSource(vertexShader, DEFAULT_VERTEX_SHADER)
ctx.compileShader(vertexShader)
if (!ctx.getShaderParameter(vertexShader, ctx.COMPILE_STATUS)) {
throw new Error(`Le vertex shader n'a pas pu être compilé: ${ctx.getShaderInfoLog(vertexShader)}`)
ctx.deleteShader(vertexShader)
return null
}
let fragmentShaderCode;
let fragmentShaderCodeElement = document.querySelector(`script[type="x-shader/glsl+fragment"]`);
if(fragmentShaderCodeElement){
fragmentShaderCode = fragmentShaderCodeElement.innerHTML
} else {
fragmentShaderCode = DEFAULT_FRAGMENT_SHADER
}
const fragmentShader = ctx.createShader(ctx.FRAGMENT_SHADER)
ctx.shaderSource(fragmentShader, FRAGMENT_SHADER_UTILITIES+fragmentShaderCode)
ctx.compileShader(fragmentShader)
if (!ctx.getShaderParameter(fragmentShader, ctx.COMPILE_STATUS)) {
let error = ctx.getShaderInfoLog(fragmentShader)
let errorLine = error.match(/\d+:(\d+):/)
if(errorLine) {
let lineNum = parseInt(errorLine[1])
let lineNumeWithoutUtils = lineNum-FRAGMENT_SHADER_UTILITIES_N_LINES;
if(lineNumeWithoutUtils >= 0){
error = `Ligne ${lineNumeWithoutUtils}: ${error}`
}
}
ctx.deleteShader(fragmentShader)
throw new Error(`Le fragment shader n'a pas pu être compilé: ${error}`)
}
const shaderProgram = ctx.createProgram()
ctx.attachShader(shaderProgram, vertexShader)
ctx.attachShader(shaderProgram, fragmentShader)
ctx.linkProgram(shaderProgram)
if (!ctx.getProgramParameter(shaderProgram, ctx.LINK_STATUS)) {
throw new Error(`Le shader n'a pas pu être linké: ${ctx.getProgramInfoLog(shaderProgram)}`)
ctx.deleteShader(vertexShader)
ctx.deleteShader(fragmentShader)
ctx.deleteProgram(shaderProgram)
return null
}
if(oldShader){
ctx.deleteProgram(oldShader.program)
ctx.deleteShader(oldShader.vertexShader)
ctx.deleteShader(oldShader.fragmentShader)
ctx.deleteBuffer(oldShader.vertexIndexBuffer)
}
const vertexIndexBuffer = ctx.createBuffer()
ctx.bindBuffer(ctx.ARRAY_BUFFER, vertexIndexBuffer)
ctx.bufferData(ctx.ARRAY_BUFFER, new Float32Array([0, 1, 2]), ctx.STATIC_DRAW)
return {
program: shaderProgram,
vertexShader,
fragmentShader,
fragmentShaderElement: fragmentShaderCodeElement,
aVertexIndex: ctx.getAttribLocation(shaderProgram, "aVertexIndex"),
uRandom: ctx.getUniformLocation(shaderProgram, "uRandom"),
uCanvasSize: ctx.getUniformLocation(shaderProgram, "uCanvasSize"),
uTime: ctx.getUniformLocation(shaderProgram, "uTime"),
vertexIndexBuffer,
}
}
/**
* @param {HTMLCanvasElement} target
* @param {*} mainShader
*/
function renderCuteTexture(target, mainShader, initTime){
let ctx = target.getContext("webgl")
ctx.clearColor(0, 0, 0, 0)
ctx.clearDepth(1.0)
ctx.enable(ctx.DEPTH_TEST)
ctx.depthFunc(ctx.LEQUAL)
ctx.clear(ctx.COLOR_BUFFER_BIT)
ctx.bindBuffer(ctx.ARRAY_BUFFER, mainShader.vertexIndexBuffer)
ctx.vertexAttribPointer(
mainShader.aVertexIndex, // location
1, // pull out 1 value per iteration
ctx.FLOAT, // the data in the buffer is 32bit floats
false, // don't normalize
0, // how many bytes to get from one set of values to the next
0 // how many bytes inside the buffer to start from
)
ctx.enableVertexAttribArray(mainShader.aVertexIndex)
ctx.useProgram(mainShader.program)
if(mainShader.uRandom){
ctx.uniform4f(
mainShader.uRandom,
Math.random(),
Math.random(),
Math.random(),
Math.random()
)
}
if(mainShader.uCanvasSize){
ctx.uniform2f(
mainShader.uCanvasSize,
target.width,
target.height
)
}
if(mainShader.uTime){
ctx.uniform1f(
mainShader.uTime,
(Date.now() - initTime)/1000
)
}
ctx.drawArrays(ctx.TRIANGLE_STRIP, 0, 3);
}