webgl 实现正弦波

462 阅读2分钟

image.png

1 什么是正弦波简单来讲就是y = sinx. x的值域为负无穷到正无穷,y的取值范围0-1 如图

image.png

这是一个连续的函数。我们用若干个离散的点来模拟这个函数.以下函数用来生成在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

image.png

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;
    }
    
}

image.png

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()

image.png