1 什么是正弦波简单来讲就是y = sinx. x的值域为负无穷到正无穷,y的取值范围0-1 如图
这是一个连续的函数。我们用若干个离散的点来模拟这个函数.以下函数用来生成在x轴的离散点。由于裁切坐标系的范围为-1,1。所以要对其进行缩放。
// x 轴缩放
function getWebglPosition(){
return (Math.random() * 2) - 1
}
// 生成随机点
function getPositons(length){
const res = []
while(length >= 0){
res.push(getWebglPosition(),0.0)
length--
}
return res
}
2 编写顶点着色器,我们将点以point的形式传入。所以需要指定gl_pointSize(这是一个glsl内置变量,用来指定点所占的像素大小)。而后将我们随机生成的x用sinx 函数求出y值。注意我们随意生成的x坐标在-1,到1直接.所以要乘以3.14获取完整的波形。
// 顶点坐标,一个四维向量,代表x,y,z,w 其中 w 始终为1.0
attribute vec4 a_position;
// 偏移量用来生成波动画
uniform float u_time;
// size 用来指定点的像素大小
uniform float u_size;
void main(){
gl_PointSize = u_size;
vec4 po = vec4(1.0,1.0,0.0,1.0);
po.x = a_position.x;
po.y = sin(a_position.x * 3.14 + u_time);
gl_Position = po;
}
3 编写片元着色器。 注意,上文我们在顶点着色器指定了点的像素大小。众所周知像素是方的。所以如果不做任何的处理。会得到一个方形的点。所以需要在片元着色器对像素做一个筛选。即当前点位到中心点位大于某个阈值则舍去。这个阈值一般是半径。这里取0.5
precision mediump float;
// 求渐变色
float getLiner(vec2 st , vec2 po){
vec2 di = st - po;
float le = length(di);
return sin(1.785 * le / 0.5);
}
void main(){
// 计算圆形
if(distance(gl_PointCoord , vec2(0.5)) <= 0.5){
gl_FragColor.xyz = vec3(1.0 ,0.0,1.0);
float a = 0.8 - getLiner(gl_PointCoord , vec2(0.5));
if(a > 0.5) {
gl_FragColor.a = 1.0;
}else{
gl_FragColor.a = a;
}
}else{
discard;
}
}
4 链接顶点着色器和片元着色器,并往gpu发送数据
// 创建shader
export function createShader(gl, type, source) {
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
// 创建着色器程序
export function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
// 加载上文的顶点着色和片元着色器片段
export async function loadFiles(url){
const data = await fetch(url)
return await data.text()
}
const div = document.getElementById("main")
const canvas = document.createElement("canvas")
const gl = canvas.getContext("webgl")
canvas.width = window.innerWidth
canvas.height = window.innerHeight
div.appendChild(canvas)
// 获取顶点着色器数据
const vs = await loadFiles('./vs.vs')
// 获取片元着色器数据
const fs = await loadFiles('./fs.fs')
// 创建顶点着色器
const vsShader = createShader(gl , gl.VERTEX_SHADER , vs)
// 创建片元着色器
const fsShader = createShader(gl , gl.FRAGMENT_SHADER , fs)
// 创建着色器程序
const program = createProgram(gl , vsShader , fsShader)
// 获取着色器中a_positon 变量的索引
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
// 获取常量size索引
const s = gl.getUniformLocation(program , "u_size")
const time = gl.getUniformLocation(program , "u_time")
const positionBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
// 点的数目
const posintLength = 500
// 生成随机点
const positions = getPositons(posintLength)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW)
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
// 清楚canvas
gl.clearColor(0 , 0 , 0 , 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
// 使用着色器
gl.useProgram(program)
gl.enableVertexAttribArray(positionAttributeLocation)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.uniform1f(s ,20)
gl.enable(gl.BLEND)
gl.blendFunc(gl.SRC_ALPHA , gl.ONE_MINUS_SRC_ALPHA)
const size = 2
const type = gl.FLOAT
const normalize = false
const stride = 0
const offset = 0
// 传送顶点数据
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset);
// 绘图类型
const primitiveType = gl.POINTS;
let count = 0;
// 每一帧重绘制作动画
const render = ()=>{
gl.clearColor(0 , 0 , 0 , 1.0);
gl.clear(gl.COLOR_BUFFER_BIT)
gl.uniform1f(time , count)
gl.drawArrays(primitiveType, offset, posintLength);
count = count + 0.03
requestAnimationFrame(render)
}
render()