概念

  • gpu程序与cpu程序有些差异,cpu是串行执行,gpu是并行执行
  • 与worker相似,编写一段shader着色器程序在gpu运行,并且js和shader间可以相互传递数据
  • webgl的功能方法很复杂繁琐,先封装几个函数备用
    // 创建一个着色器shader程序
    export function createProgram(gl, vertexSrc, fragmentSrc){
      const vertexShader = gl.createShader(gl.VERTEX_SHADER)    // 创建顶点着色器
      gl.shaderSource(vertexShader, vertexSrc)          // 绑定顶点着色器源码
      gl.compileShader(vertexShader)                    // 编译定点着色器
      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER)  // 创建片段着色器
      gl.shaderSource(fragmentShader, fragmentSrc)      // 绑定片段着色器源码
      gl.compileShader(fragmentShader)                  // 编译片段着色器
      const program = gl.createProgram()                // 创建着色器程序
      gl.attachShader(program, vertexShader)            // 指定顶点着色器
      gl.attachShader(program, fragmentShader)          // 指定片段着色色器
      gl.linkProgram(program)                           // 链接程序
      gl.useProgram(program)                            // 使用着色器
      gl.program = program
      return program      // 返回程序对象,后面设置参数的时候会用到
    }
    
    // 设置shader程序的attribute变量
    export function setAttribute(gl, program, name, data, options) {
      const attr = gl.getAttribLocation(program, name)  // 获取程序变量
      const buffer = gl.createBuffer()
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
      gl.bufferData(gl.ARRAY_BUFFER, data, options.usage||gl.STATIC_DRAW) // 设置变量值
      gl.enableVertexAttribArray(attr)      // 启用变量
      gl.vertexAttribPointer(  // 设置变量属性
        attr,
        options.size,               // attribute的分量数 1,2,3,4
        options.type||gl.FLOAT,     // attribute数据类型
        options.normalize||false,   // 是否归一化
        options.stride||0,          // 每个顶点数据占用的字节数
        options.offset||0)          // 顶点数据在缓冲区中的偏移量
    }
    
    // 设置shader程序的uniform变量
    export function setUniform(gl, program, typeSuffix, name, ...values) {
      const func = gl['uniform'+typeSuffix]
      func && func.call(gl, gl.getUniformLocation(program, name), ...values)
    }
    
    // 将一张图片设置为纹理,并绑定到纹理单元0
    export function setTexture(gl, image) {
      var texture = gl.createTexture()
      gl.activeTexture(gl.TEXTURE0)
      gl.bindTexture(gl.TEXTURE_2D, texture)
      // 水平重复方式
      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.LINEAR)
      // 放大过滤方式
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
      // 纹理数据上传到GPU
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
    }
    

HelloWorld

创建一个三原色叠加的图形

// hello.vert.glsl
attribute vec2 a_position;  // 图片区域的4个顶点

void main() {  // 返回顶点,顶点所围成的区域就是整个图像
  gl_Position = vec4(a_position.xy, 0.0, 1.0);
}
// hello.frag.glsl
#ifdef GL_ES
precision mediump float;    // 默认中精度
#endif

uniform vec2 u_resolution;    // canvas的宽高vec2(width, height)

void main(){
  // 坐标系扩大2倍,并将原点移动到canvas中心,width与height的最小项长度定为2
  vec2 p = (gl_FragCoord.xy * 2.0 - u_resolution)/min(u_resolution.x, u_resolution.y);
  vec3 color1=vec3(0.0, 0.0, 0.0);
  vec3 color2=vec3(0.0, 0.0, 0.0);
  vec3 color3=vec3(0.0, 0.0, 0.0);
  if(distance(vec2(-0.2, -0.2),vec2(p.xy))<=0.4){ // 红色的圆形区域
    color1=vec3(1.0, 0.0, 0.0);
  }
  if(distance(vec2(0.2, -0.2),vec2(p.xy))<=0.4){ // 绿色的圆形区域
    color2=vec3(0.0, 1.0, 0.0);
  }
  if(distance(vec2(0.0, 0.2),vec2(p.xy))<=0.4){ // 蓝色的圆形区域
    color3=vec3(0.0, 0.0, 1.0);
  }
  vec3 color=vec3(0.0, 0.0, 0.0) + color1 + color2 + color3; // 颜色叠加
  gl_FragColor=vec4(color, 1.0); // 返回颜色值,设置区域内所有点的颜色,构成图像
}
import helloVertexSrc from '../shader/hello.vert.glsl?raw'
import helloFragmentSrc from '../shader/hello.frag.glsl?raw'
function helloWorld(el) {
  const gl = el.getContext('webgl') || el.getContext('experimental-webgl')
  resetWebgl(gl)
  // 创建shader程序
  const shaderProgram = createProgram(gl, helloVertexSrc, helloFragmentSrc)
  // 设置顶点属性
  setAttribute(gl, shaderProgram, 'a_position',
    new Float32Array([
      -1, -1,
      1, -1,
      -1, 1,
      1, 1
    ]), {
      usage: gl.STATIC_DRAW,
      size: 2,
      type: gl.FLOAT,
      normalize: false,
      stride: 0,
      offset: 0,
  })
  setUniform(gl, shaderProgram, '2f', 'u_resolution', el.width, el.height)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
</script>

使用图片纹理

将图片渲染到webgl中

// image.vert.glsl
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
  gl_Position = vec4(a_position.xy, 0.0, 1.0);
  // v_texCoord = a_texCoord;
  v_texCoord = vec2(a_texCoord.x, 1.0 - a_texCoord.y);    // 变换坐标,不然图像是倒的
}
// image.frag.glsl
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
  gl_FragColor = texture2D(u_texture, v_texCoord);
}
import imageVertexSrc from '../shader/image.vert.glsl?raw'
import imageFragmentSrc from '../shader/image.frag.glsl?raw'
function imageTexture(el) {
  const gl = el.getContext('webgl') || el.getContext('experimental-webgl')
  resetWebgl(gl)
  const shaderProgram = createProgram(gl, imageVertexSrc, imageFragmentSrc)
  // 设置顶点属性
  setAttribute(gl, shaderProgram, 'a_position',
    new Float32Array([
      -1, -1,
      1, -1,
      -1, 1,
      1, 1
    ]), {
      usage: gl.STATIC_DRAW,
      size: 2,
      type: gl.FLOAT,
      normalize: false,
      stride: 0,
      offset: 0,
  })
  setAttribute(gl, shaderProgram, 'a_texCoord', new Float32Array([
    0, 0,
    1, 0,
    0, 1,
    1, 1
  ]), {
    usage: gl.STATIC_DRAW,
    size: 2,
    type: gl.FLOAT,
    normalize: false,
    stride: 0,
    offset: 0,
  })
  // 设置纹理
  let img = new Image()
  img.onload = ()=>{
    setTexture(gl, img)
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
    img = null
  }
  img.src = riverImage
  setUniform(gl, shaderProgram, '2f', 'u_resolution', el.width, el.height)
  setUniform(gl, shaderProgram, '1i', 'u_texture', 0)   // gl.TEXTURE0
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
}

算法

未完待续

实例效果

Last Updated:
Contributors: qigang