158 lines
5.1 KiB
Vue
158 lines
5.1 KiB
Vue
<script setup lang="ts">
|
|
import noise from "~/assets/shaders/noise.frag?raw"
|
|
|
|
const canvas = ref<HTMLCanvasElement | null>(null)
|
|
|
|
const settingsStore = useSettingsStore()
|
|
|
|
declare const webglUtils: {
|
|
createProgramFromSources: (gl: WebGLRenderingContext, array: [string, string]) => WebGLProgram,
|
|
resizeCanvasToDisplaySize: (e: HTMLCanvasElement | OffscreenCanvas) => any
|
|
}
|
|
declare const resolveLygiaAsync: (source: string) => Promise<string>
|
|
|
|
export interface Props {
|
|
colorStart?: [number, number, number]
|
|
colorEnd?: [number, number, number]
|
|
}
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
colorStart: () => [0, .3, 0],
|
|
colorEnd: () => [0, 1, 0]
|
|
})
|
|
|
|
const targetFramerate = 1000 / settingsStore.shaderFramerate
|
|
|
|
const interval = ref<ReturnType<typeof setInterval>>()
|
|
|
|
let fragmentShader = await resolveLygiaAsync(noise);
|
|
|
|
async function main(canvas: HTMLCanvasElement, fragmentShader2: string) {
|
|
// Get A WebGL context
|
|
/** @type {HTMLCanvasElement} */
|
|
const gl = canvas.getContext("webgl");
|
|
if (!gl) {
|
|
return;
|
|
}
|
|
|
|
const vs = `attribute vec4 a_position;void main() {gl_Position = a_position;}`;
|
|
|
|
// setup GLSL program
|
|
const program = webglUtils.createProgramFromSources(gl, [vs, fragmentShader2]);
|
|
|
|
// look up where the vertex data needs to go.
|
|
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
|
|
|
|
// look up uniform locations
|
|
const offset = gl.getUniformLocation(program, "u_offset");
|
|
const resolutionLocation = gl.getUniformLocation(program, "u_resolution");
|
|
const colorStart = gl.getUniformLocation(program, "u_color_start");
|
|
const quality = gl.getUniformLocation(program, "u_quality");
|
|
const colorEnd = gl.getUniformLocation(program, "u_color_end");
|
|
const timeLocation = gl.getUniformLocation(program, "u_time");
|
|
|
|
// Create a buffer to put three 2d clip space points in
|
|
const positionBuffer = gl.createBuffer();
|
|
|
|
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
|
|
|
// fill it with a 2 triangles that cover clipspace
|
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
|
|
-1, -1, // first triangle
|
|
1, -1,
|
|
-1, 1,
|
|
-1, 1, // second triangle
|
|
1, -1,
|
|
1, 1,
|
|
]), gl.STATIC_DRAW);
|
|
|
|
|
|
const gl2 = gl
|
|
function render(time: number) {
|
|
time *= 0.001; // convert to seconds
|
|
webglUtils.resizeCanvasToDisplaySize(gl2.canvas);
|
|
|
|
// Tell WebGL how to convert from clip space to pixels
|
|
gl2.viewport(0, 0, gl2.canvas.width, gl2.canvas.height);
|
|
|
|
// Tell it to use our program (pair of shaders)
|
|
gl2.useProgram(program);
|
|
|
|
// Turn on the attribute
|
|
gl2.enableVertexAttribArray(positionAttributeLocation);
|
|
|
|
// Bind the position buffer.
|
|
gl2.bindBuffer(gl2.ARRAY_BUFFER, positionBuffer);
|
|
|
|
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
|
|
gl2.vertexAttribPointer(
|
|
positionAttributeLocation,
|
|
2, // 2 components per iteration
|
|
gl2.FLOAT, // the data is 32bit floats
|
|
false, // don't normalize the data
|
|
0, // 0 = move forward size * sizeof(type) each iteration to get the next position
|
|
0, // start at the beginning of the buffer
|
|
);
|
|
|
|
const bounds = canvas.getBoundingClientRect()
|
|
const aspectRatio = Math.max(window.screen.width, window.screen.height) * devicePixelRatio
|
|
|
|
gl2.uniform2f(offset, bounds.left, -bounds.bottom);
|
|
// console.log(canvas.getBoundingClientRect().left * window.devicePixelRatio)
|
|
gl2.uniform1i(quality, settingsStore.shaderQuality)
|
|
gl2.uniform2f(resolutionLocation, aspectRatio, aspectRatio);
|
|
gl2.uniform3f(colorStart, props.colorStart[0], props.colorStart[1], props.colorStart[2]);
|
|
gl2.uniform3f(colorEnd, props.colorEnd[0], props.colorEnd[1], props.colorEnd[2]);
|
|
gl2.uniform1f(timeLocation, time);
|
|
gl2.drawArrays(
|
|
gl2.TRIANGLES,
|
|
0, // offset
|
|
6, // num vertices to process
|
|
);
|
|
}
|
|
clearInterval(interval.value)
|
|
interval.value = setInterval(() => requestAnimationFrame(render), targetFramerate)
|
|
}
|
|
|
|
|
|
async function reloadShader() {
|
|
nextTick(() => {
|
|
if (!canvas.value) { return }
|
|
let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + settingsStore.shaderQuality);
|
|
clearInterval(interval.value)
|
|
main(canvas.value, fragmentShader2);
|
|
})
|
|
|
|
}
|
|
watch(() => settingsStore.shaderQuality, (newValue, oldValue) => {
|
|
reloadShader()
|
|
})
|
|
|
|
watch(() => settingsStore.shaderFramerate, (newValue, oldValue) => {
|
|
reloadShader()
|
|
})
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
reloadShader()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
clearInterval(interval.value)
|
|
})
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<canvas v-if="settingsStore.shaderQuality != 0" ref="canvas" id="background">test</canvas>
|
|
</template>
|
|
|
|
<style scoped>
|
|
#background {
|
|
left: 0;
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 100%;
|
|
}
|
|
</style> |