# threejs

# canvas 和 webgl

canvas是H5新增的DOM元素,用途:显示二维和三维的图像

绘制:二维图形可以使用(canvas API 或 webgl API);三维图形使用webgl api

绘制2D图形主要有CanvasRenderingContext2D接口完成

// 获取实例
canvas.getContext('2d')

绘制3D WebGLRenderingContext接口完成(webgl2.0)

canvas.getContext('webgl')

还有 WebGL2RenderingContext(webgl3.0)

canvas.getContext('webgl2')

# webgl

一种3D绘图协议,衍生于OpenGL ES2.0,可以结合HTML5和js在网页上绘制和渲染二维和三维图形

应用场景:数据可视化 游戏引擎 交互演示 VR 地图 室内设计 城市规划

gl.clearColor(r,g,b,a) 指定清空 <canvas>的颜⾊,接收四个参数(取值区间为 0.0~1.0)

gl.clear 需要和 gl.clearColor 提到的函数搭配使用

# threejs之着色器

着色器就是让开发者自己去编写一段程序,用来代替固定渲染管线,来处理图像的渲染。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')


  // 着色器
  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 必须要存在 main 函数
    void main() {
      // 要绘制的点的坐标
      gl_Position = vec4(0.0,0.0,0.0,1.0);
      // 点的大小
      gl_PointSize = 30.0;
    }
  `; // 顶点着色器

  //  gl_Position vec4(0.0,0.0,0.0,1.0)  x, y, z, w齐次坐标 (x/w, y/w, z/w)

  // gl_FragColor vec4(1.0,0.0,0.0,1.0) r, g, b, a
  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
    const vertexShader = gl.createShader(gl.VERTEX_SHADER);
    const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

    gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
    gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码

    // 编译着色器
    gl.compileShader(vertexShader)
    gl.compileShader(fragmentShader)

    // 创建一个程序对象
    const program = gl.createProgram();

    gl.attachShader(program, vertexShader)
    gl.attachShader(program, fragmentShader)

    gl.linkProgram(program)

    gl.useProgram(program)

    return program;
}

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  // 执行绘制
    // 绘制几何图元的函数
  // 要绘制的图形是什么, 从哪个开始,   使用几个顶点
  gl.drawArrays(gl.POINTS, 0, 1);
  gl.drawArrays(gl.LINES, 0, 1); // 最少需要有两个点,
  gl.drawArrays(gl.TRIANGLES, 0, 1); // 3个点

  // 3个顶点
  // 0.0, 0.0, 0.0
  // 0.2, 0.0, 0.0
  // 0.4, 0.0, 0.0
  gl.drawArrays(gl.POINTS, 0, 1);
  gl.drawArrays(gl.LINES, 1, 2);
</script>
  • gl_Position 是一个内置的 vec4 变量,用于存储顶点着色器计算出的顶点的齐次坐标
  • gl_PointSize 是一个内置的 float 变量,用于指定点精灵(Point Sprites)的大小
  • gl_FragColor 是旧版 GLSL(在 GLSL 1.20 和更早版本中)中用于设置片元(fragment,在 OpenGL 中通常指的是像素的未处理颜色)颜色的内置变量。在顶点着色器(vertex shader)中并没有 gl_FragColor,它只在片元着色器(fragment shader)中使用。然而,从 GLSL 1.30 版本开始,推荐使用 out 关键字来声明片元着色器的输出变量,而不是使用 gl_FragColor。这是因为使用 out 关键字可以使着色器更加模块化,并允许更灵活的着色器间通信。

# lib代码

function initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE) {
  const vertexShader = gl.createShader(gl.VERTEX_SHADER);// 用于创建一个新的顶点着色器对象
  const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); //用于创建一个新的片元着色器对象

  gl.shaderSource(vertexShader, VERTEX_SHADER_SOURCE) // 指定顶点着色器的源码
  gl.shaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE) // 指定片元着色器的源码

  // 编译着色器
  gl.compileShader(vertexShader)
  gl.compileShader(fragmentShader)

  // 创建一个程序对象
  const program = gl.createProgram();

  gl.attachShader(program, vertexShader)
  gl.attachShader(program, fragmentShader)

  gl.linkProgram(program)

  gl.useProgram(program)

  return program;
}

// 平移矩阵
function getTranslateMatrix(x = 0,y = 0,z = 0) {
  return new Float32Array([
    1.0,0.0,0.0,0.0,
    0.0,1.0,0.0,0.0,
    0.0,0.0,1.0,0.0,
    x  ,y  ,z  , 1,
  ])
}
// 缩放矩阵
function getScaleMatrix(x = 1,y = 1,z = 1) {
  return new Float32Array([
    x  ,0.0,0.0,0.0,
    0.0,y  ,0.0,0.0,
    0.0,0.0,z  ,0.0,
    0.0,0.0,0.0, 1,
  ])
}
// 绕z轴旋转的旋转矩阵
function getRotateMatrix(deg) {
  return new Float32Array([
    Math.cos(deg)  ,Math.sin(deg) ,0.0,0.0,
    -Math.sin(deg)  ,Math.cos(deg) ,0.0,0.0,
    0.0,            0.0,            1.0,0.0,
    0.0,            0.0,            0.0, 1,
  ])
}

// 矩阵复合函数
function mixMatrix(A, B) {
  const result = new Float32Array(16);

  for (let i = 0; i < 4; i++) {
    result[i] = A[i] * B[0] + A[i + 4] * B[1] + A[i + 8] * B[2] + A[i + 12] * B[3]
    result[i + 4] = A[i] * B[4] + A[i + 4] * B[5] + A[i + 8] * B[6] + A[i + 12] * B[7]
    result[i + 8] = A[i] * B[8] + A[i + 4] * B[9] + A[i + 8] * B[10] + A[i + 12] * B[11]
    result[i + 12] = A[i] * B[12] + A[i + 4] * B[13] + A[i + 8] * B[14] + A[i + 12] * B[15]
  }

  return result;
}

// 归一化函数
function normalized(arr) {
  let sum = 0;

  for (let i = 0; i < arr.length; i++) {
    sum += arr[i] * arr[i]
  }

  const middle = Math.sqrt(sum);

  for (let i = 0; i < arr.length; i++) {
    arr[i] = arr[i] / middle;
  }
}

// 叉积函数 获取法向量
function cross(a,b) {
  return new Float32Array([
    a[1] * b[2] - a[2] * b[1],
    a[2] * b[0] - a[0] * b[2],
    a[0] * b[1] - a[1] * b[0],
  ])
}

// 点积函数 获取投影长度
function dot(a, b) {
  return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}

// 向量差
function minus(a, b) {
  return new Float32Array([
    a[0] - b[0],
    a[1] - b[1],
    a[2] - b[2],
  ])
}

// 视图矩阵获取
function getViewMatrix(eyex, eyey, eyez, lookAtx, lookAty, lookAtz, upx, upy, upz) {

  // 视点
  const eye = new Float32Array([eyex, eyey, eyez])
  // 目标点
  const lookAt = new Float32Array([lookAtx, lookAty, lookAtz])
  // 上方向
  const up = new Float32Array([upx, upy, upz])

  // 确定z轴
  const z = minus(eye, lookAt);

  normalized(z);
  normalized(up);

  // 确定x轴
  const x = cross(z, up);

  normalized(x);
  // 确定y轴
  const y = cross(x, z);

  return new Float32Array([
    x[0],       y[0],       z[0],       0,
    x[1],       y[1],       z[1],       0,
    x[2],       y[2],       z[2],       0,
    -dot(x,eye),-dot(y,eye),-dot(z,eye),1
  ])
}

// 获取正射投影矩阵
function getOrtho(l, r, t, b, n, f) {
  return new Float32Array([
    2 / (r - l), 0,           0,           0,
    0,           2/(t-b),     0,           0,
    0,           0,           -2/(f-n),    0,
    -(r+l)/(r-l),-(t+b)/(t-b),-(f+n)/(f-n),1
  ])
}

// 获取透视投影矩阵
function getPerspective(fov, aspect, far, near) {
  fov = fov * Math.PI / 180;
  return new Float32Array([
    1/(aspect*Math.tan(fov / 2)), 0, 0, 0,
    0, 1/(Math.tan(fov/2)),0,0,
    0,0,-(far+near)/(far-near),-(2*far*near)/(far-near),
    0,0,-1,0,
  ])
}

function distanceSelf(a, b) {
  const x = a[0] - b[0]
  const y = a[1] - b[1]
  const z = a[2] - b[2]

  const v = x * x + y * y + z * z;

  return Math.sqrt(v)
}

# gl.drawArrays绘制图形

gl.drawArrays(gl.POINTS, 0, 1);
+ gl.POINTS:这是一个枚举值,表示你想绘制的图元是点。在WebGL中,gl.POINTS 的值是 0,表示你想把每个顶点都作为一个点来绘制。
+ 0:这是第一个参数,表示你想从顶点缓冲区中的哪个位置开始绘制。在这里,你从索引 0 开始,这意味着你会从顶点缓冲区的开始处取第一个顶点来绘制。
+ 1:这是第三个参数,表示你想绘制多少个顶点。在这里,你只想绘制一个顶点。
作用 说明
gl.POINTS ⼀系列点
gl.LINES 线段 ⼀系列单独的线段,如果顶点是奇数,最后⼀个会被忽略
gl.LINE_LOOP 闭合线 ⼀系列连接的线段,结束时,会闭合终点和起点
gl.LINE_STRIP 线条 ⼀系列连接的线段,不会闭合终点和起点
gl.TRIANGLES 三角形 ⼀系列单独的三角形
gl.TRIANGLE_STRIP 三角带 ⼀系列条带状的三角形
gl.TRIANGLE_FAN 三角形 飘带状三角形

# threejs坐标系

# webgl顶点着色器使用attribute变量

attribute 变量只能在顶点着色器中使用,不能在片元着色器中使用

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;// 变量定义
    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 30.0;
    }
  `; // 顶点着色器

 const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
 // 获取变量
  const aPosition = gl.getAttribLocation(program, 'aPosition');
 //设置变量
  gl.vertexAttrib4f(aPosition, 0.5,0.5,0.0,1.0)
  // gl.vertexAttrib3f(aPosition, 0.5,0.5,0.0)// 自动补齐缺少的内容
  // gl.vertexAttrib2f(aPosition, 0.5,0.5)

获取 attribute 变量需要在 initShader 函数之后,因为会用到 program 这个程序对象。

# webgl着色器实现图形转换

  1. 平移

核心修改代码,增加一个变量aTranslate

 gl_Position = vec4(aPosition.x + aTranslate, aPosition.y, aPosition.z, 1.0);
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    attribute float aTranslate;
    void main() {
      gl_Position = vec4(aPosition.x + aTranslate, aPosition.y, aPosition.z, 1.0);
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const aTranslate = gl.getAttribLocation(program, 'aTranslate');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)

  let x = -1;
  setInterval(() => {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }
    gl.vertexAttrib1f(aTranslate, x);
    gl.drawArrays(gl.TRIANGLES, 0, 3);
  }, 60)
</script>
  1. 缩放
 gl_Position = vec4(aPosition.x * aScale, aPosition.y * aScale, aPosition.z * aScale, 1.0);
  1. 选转
const VERTEX_SHADER_SOURCE = `
  attribute vec4 aPosition;
  attribute float deg;
  void main() {
    gl_Position.x = aPosition.x * cos(deg) - aPosition.y * sin(deg);
    gl_Position.y = aPosition.x * sin(deg) + aPosition.y * cos(deg);
    gl_Position.z = aPosition.z;
    gl_Position.w = aPosition.w;
  }
`; 
const deg = gl.getAttribLocation(program, 'deg');
function animation() {
  x += -0.01;
  gl.vertexAttrib1f(deg, x);
  gl.drawArrays(gl.TRIANGLES, 0, 3);

  requestAnimationFrame(animation)
}
animation();

# webgl使用矩阵

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 mat;
    void main() {
      gl_Position = mat * aPosition;
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const mat = gl.getUniformLocation(program, 'mat');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)

  let x = -1;
  function animation() {
    x += 0.01;
    if (x > 1) {
      x = -1;
    }

    const matrix = getTranslateMatrix(x, x);
    // gl.vertexAttrib1f(aTranslate, x);
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()
</script>
// 平移矩阵
function getTranslateMatrix(x = 0,y = 0,z = 0) {
  return new Float32Array([
    1.0,0.0,0.0,0.0,
    0.0,1.0,0.0,0.0,
    0.0,0.0,1.0,0.0,
    x  ,y  ,z  , 1,
  ])
}
// 缩放矩阵
function getScaleMatrix(x = 1,y = 1,z = 1) {
  return new Float32Array([
    x  ,0.0,0.0,0.0,
    0.0,y  ,0.0,0.0,
    0.0,0.0,z  ,0.0,
    0.0,0.0,0.0, 1,
  ])
}
// 绕z轴旋转的旋转矩阵
function getRotateMatrix(deg) {
  return new Float32Array([
    Math.cos(deg)  ,Math.sin(deg) ,0.0,0.0,
    -Math.sin(deg)  ,Math.cos(deg) ,0.0,0.0,
    0.0,            0.0,            1.0,0.0,
    0.0,            0.0,            0.0, 1,
  ])
}

// 矩阵复合函数
function mixMatrix(A, B) {
  const result = new Float32Array(16);
  for (let i = 0; i < 4; i++) {
    result[i] = A[i] * B[0] + A[i + 4] * B[1] + A[i + 8] * B[2] + A[i + 12] * B[3]
    result[i + 4] = A[i] * B[4] + A[i + 4] * B[5] + A[i + 8] * B[6] + A[i + 12] * B[7]
    result[i + 8] = A[i] * B[8] + A[i + 4] * B[9] + A[i + 8] * B[10] + A[i + 12] * B[11]
    result[i + 12] = A[i] * B[12] + A[i + 4] * B[13] + A[i + 8] * B[14] + A[i + 12] * B[15]
  }
  return result;
}

# 矩阵组合多个线性变换

为什么矩阵相乘可以得到多个操作方法结合的结果呢?这主要是基于矩阵乘法的定义和线性变换的性质。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    uniform mat4 mat;
    void main() {
      gl_Position = mat * aPosition;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const mat = gl.getUniformLocation(program, 'mat');

  const points = new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)

  let deg = 0;
  let translateX = -1;
  let scaleX = 0.1;
  function animation() {
    deg += 0.01;
    translateX += 0.01;
    scaleX += 0.01;

    if (translateX > 1) {
      translateX = -1;
    }

    if (scaleX > 1.5) {
      scaleX = 0.1;
    }

    const translate = getTranslateMatrix(translateX);
    const scale = getScaleMatrix(scaleX);
    const rotate = getRotateMatrix(deg);

    const matrix = mixMatrix(mixMatrix(translate, scale), rotate)
    // gl.vertexAttrib1f(aTranslate, x);
    gl.uniformMatrix4fv(mat, false, matrix);
    gl.drawArrays(gl.TRIANGLES, 0, 3);

    requestAnimationFrame(animation);
  }

  animation()
</script>

# webgl片元着色器uniform变量设置

  • uniform需要给变量设置精度
  • uniform也可以用在顶点着色器
  • uniform vec2 uColor;//vec2和使用的gl.vertexAttrib2f对应(vec3/gl.vertexAttrib3f|vec4/gl.vertexAttrib4f);
  • **uniform float uColor《==》gl.vertexAttrib1f<==>gl_FragColor = vec4(uColor, 0.0, 0.0,1.0); **,特殊的地方
precision mediump float;
// ⾼精度:highp, 低精度:lowp,
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    uniform vec4 uPosition;
    // 只传递顶点数据
    attribute vec4 aPosition;
    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    precision mediump float;
    uniform vec2 uColor;
    void main() {
      gl_FragColor = vec4(uColor.r, uColor.g, 0.0,1.0); // vec4
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');

  const uColor = gl.getUniformLocation(program, 'uColor')

  const points = []
  ctx.onclick = function(ev) {
    // 坐标
    const x = ev.clientX
    const y = ev.clientY

    const domPosition = ev.target.getBoundingClientRect();

    const domx = x - domPosition.left
    const domy = y - domPosition.top;


    const halfWidth = ctx.offsetWidth / 2
    const halfHeight = ctx.offsetHeight / 2

    const clickX = (domx - halfWidth) / halfWidth
    const clickY = (halfHeight - domy) / halfHeight

    points.push({
      clickX, clickY
    })

    for (let i = 0; i < points.length; i++) {
      gl.vertexAttrib2f(aPosition, points[i].clickX, points[i].clickY)

      gl.uniform2f(uColor, points[i].clickX, points[i].clickY)
      gl.drawArrays(gl.POINTS, 0, 1);
    }
  }
</script>

# gl.uniform*具体区别

如gl.uniform2f gl.uniform1i uniform1fv 。

  1. gl.uniform2f(location, v0, v1)
  • 作用:设置一个包含两个浮点数的统一变量。
  • 参数:
    • location:一个 WebGLUniformLocation 对象,表示着色器中统一变量的位置。
    • v0:第一个浮点数值。
    • v1:第二个浮点数值。
  1. gl.uniform1i(location, x)
  • 作用:设置一个整数类型的统一变量。
  • 参数:
    • location:一个 WebGLUniformLocation 对象,表示着色器中统一变量的位置。
    • x:要设置的整数值。

在纹理映射的上下文中,这个函数经常用于设置纹理采样器的索引。

  1. gl.uniform1fv(location, v)
  • 作用:设置一个包含多个浮点数的统一变量数组。
  • 参数:
    • location:一个 WebGLUniformLocation 对象,表示着色器中统一变量的位置。
    • v:一个 Float32Array 或包含浮点数的类似数组对象,表示要设置的浮点数值数组。

这个函数允许你一次性设置多个浮点数值到一个统一变量数组中。

# varying变量

varying是WebGL中的一个关键字,用于声明一种特殊类型的变量,这种变量在顶点着色器和片元着色器之间传递数据。以下是关于varying变量的详细解释:

  1. 定义与作用:
  • varying变量是在顶点着色器和片元着色器之间传递信息的桥梁。
  • 它们在顶点着色器中被计算并赋值,然后这些值被传递到片元着色器中。
  • 在片元着色器中,这些值被用来进行插值计算,以确定要绘制的像素的颜色或其他属性。
  1. 使用场景:
  • varying变量通常用于传递如光照信息、纹理坐标等从顶点着色器到片元着色器的数据。
  • 例如,如果想在图形表面应用纹理,就需要通过varying变量将纹理坐标从顶点着色器传递到片元着色器。
  1. 执行流程:
  • 在顶点着色器中,varying变量被声明并赋值。
  • 这些值随后被传递到片元着色器,在片元着色器中,每个像素都会对这些值进行插值。
  • 插值后的结果用于计算最终像素的颜色。
  1. 特点:
  • varying变量的值在顶点着色器和片元着色器之间是线性插值的,这使得渲染出来的图像更加平滑和真实。
  • 必须在顶点着色器和片元着色器中同时声明同名的varying变量。

# GLSL变量类型

  1. 标量(Scalar Types)
  • 浮点型(Float):用于表示单精度浮点数,如 float klimt;
  • 整型(Integer):
  • 有符号整型:int utrillo;
  • 无符号整型:uint(在OpenGL ES 3.0及更高版本中引入)
  • 布尔型(Boolean):bool doga;
  1. 向量(Vector Types)
  • GLSL支持多种长度的向量,其基本类型也分为bool、int、uint及float四种。常见的向量类型有:
  • vec2:包含2个浮点数的向量,如位置、纹理坐标等。
  • vec3:包含3个浮点数的向量,常用于表示三维空间中的点或方向。
  • vec4:包含4个浮点数的向量,常用于表示颜色(RGBA)或四维空间中的点。

类似地,GLSL也支持bvec2、bvec3、bvec4(布尔向量),ivec2、ivec3、ivec4(整数向量),以及uvec2、uvec3、uvec4(无符号整数向量)。

  1. 矩阵(Matrix Types) GLSL支持多种大小的矩阵类型,如mat2(2x2矩阵)、mat3(3x3矩阵)、mat4(4x4矩阵)等。这些矩阵类型常用于图形变换,如旋转、缩放和平移。

  2. 采样器(Sampler Types) 采样器类型用于在着色器中访问纹理数据。最常见的采样器类型是sampler2D,用于访问二维纹理。其他采样器类型还包括samplerCube(用于立方体纹理)和sampler3D(用于三维纹理)等。

  3. 结构体(Structure Types) GLSL允许定义结构体(structure)来组合多个不同类型的变量。结构体可以包含标量、向量、矩阵和其他结构体。

  4. 数组(Array Types) GLSL支持数组类型,允许声明和使用固定大小的数组。数组可以包含任何GLSL支持的数据类型,包括标量、向量、矩阵和结构体。

# GLSL部分函数

  1. texture2D是一个函数,用于在纹理缓冲区(simple2D声明的变量)中提取纹理数据,渲染到需要绘制的对象中
  2. diatance是一个获取两个向量(两个点)之间距离的函数。返回一个浮点数据。这个用途比较明确,只要是获取距离,都可以考虑使用它

# 贪吃蛇案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  // 不清除缓冲区
  // const gl = ctx.getContext('webgl', { preserveDrawingBuffer: true })
  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;
    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 15.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');

  // 蛇身的长度
  let points = [
    {x: 0, y: 0},
  ]

  // 食物的坐标
  const random = {
    isConnect: true
  }

  // 移动的速度
  let originSpeed = 0.02;

  // 行动的速度
  let speed = originSpeed;

  // 移动的方向
  let direction = 'x';

  // 允许吃掉食物的误差范围
  let base = 1.5;

  document.onkeydown = (event) => {
    switch (event.keyCode) {
      case 37:
        direction = 'x';
        speed = -originSpeed
        break;
      case 38:
        direction = 'y';
        speed = originSpeed;
        break;
      case 39:
        direction = 'x';
        speed = originSpeed;
        break;
      case 40:
        direction = 'y';
        speed = -originSpeed;
        break;
    }
  }

  function createRandom() {
    if (random.isConnect) {
      random.x = Math.random() * 2 - 1;
      random.y = Math.random() * 2 - 1;

      random.isConnect = false;
    }
  }

  function draw() {
    gl.vertexAttrib3f(aPosition, random.x, random.y, 0.0);
    gl.drawArrays(gl.POINTS, 0, 1);

    let prex = 0;
    let prey = 0;
    for (let i = 0; i < points.length; i++) {
      if (i === 0) {
        prex = points[0].x
        prey = points[0].y
        points[0][direction] += speed;
      } else {
        let {x, y} = points[i]
        points[i].x = prex
        points[i].y = prey

        prex = x;
        prey = y;
      }

      gl.vertexAttrib3f(aPosition, points[i].x, points[i].y, 0.0);

      gl.drawArrays(gl.POINTS, 0, 1);
    }
  }

  let timer = null
  function start() {
    timer = setInterval(() => {
      // 边界判断
      if (
        points[0].x > 1.0 ||
        points[0].x < -1.0 ||
        points[0].y < -1.0 ||
        points[0].y > 1.0
      ) {
        alert('游戏结束');
        restart();
      }

      if (
        points[0].x > random.x - base * originSpeed &&
        points[0].x < random.x + base * originSpeed &&
        points[0].y < random.y + base * originSpeed &&
        points[0].y > random.y - base * originSpeed
      ) {
        points.push({ x: random.x, y: random.y })
        random.isConnect = true;
      }

      createRandom();
      draw();
    }, 100)
  }

  start();

  function restart() {
    clearInterval(timer)
    points = [
      {x: 0, y: 0}
    ]
    direction = 'x'
    speed = originSpeed
    start();
  }
</script>

# 百叶窗效果

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;
    varying vec4 vPosition;

    void main() {
      vPosition = aPosition;

      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    precision lowp float;
    uniform float uHeight;
    varying vec4 vPosition;
    uniform float list[5]; // 声明一个数组变量

    void main() {
      for(int i = 0; i < 4; i++) {
        if (vPosition.y > list[i + 1] && vPosition.y < list[i]) {
          if (vPosition.y > uHeight - float(i) * 0.5) {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
          }
        }
      }
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const uHeight = gl.getUniformLocation(program, 'uHeight');
  const uList = gl.getUniformLocation(program,'list');

  gl.uniform1fv(uList, [1.0, 0.5, 0.0, -0.5, -1.0]);
  const points = new Float32Array([
    -1.0, -1.0,
    1.0, -1.0,
    -1.0,  1.0,
    1.0,  1.0,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)
  // gl.vertexAttrib2f(aPosition, 0.0, 0.0)

  let value = 1;
  function run() {
    value -= 0.01;

    gl.uniform1f(uHeight, value);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    requestAnimationFrame(run)
  }
  run();
</script>

# webgl缓冲对象

# 绘制多个点

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="400" height="400">
    此浏览器不支持canvas
  </canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;
    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      gl_PointSize = 10.0;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)
  // 获取顶点着色器中位置属性的位置(location)
  const aPosition = gl.getAttribLocation(program, 'aPosition');

  const points = new Float32Array([
    -0.5, -0.5,
     0.5, -0.5,
     0.0,  0.5,
  ])
  
  // 创建一个缓冲区对象并上传顶点位置数据 
  const buffer = gl.createBuffer();
  /*
  gl.bindBuffer 和 gl.bufferData 这两个函数是用于管理和上传顶点数据到 GPU 的关键步骤
  */
  //gl.bindBuffer 函数用于将 WebGLBuffer 对象与指定的缓冲区目标(如 gl.ARRAY_BUFFER)绑定
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  //gl.bufferData 函数用于上传数据到 GPU 并存储在绑定到指定目标的缓冲区中。
  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
  // 指定顶点位置数据的格式,并绑定到顶点着色器中的位置属性  
  // 因为数据是-1到1不需要规划改变,而且不需要偏移
  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);
  // 启用一个顶点属性数组,WebGL 就会知道在渲染时要使用与该索引对应的顶点属性数组。
  gl.enableVertexAttribArray(aPosition)
  // gl.vertexAttrib2f(aPosition, 0.0, 0.0)

  gl.drawArrays(gl.POINTS, 0, 3);
</script>

# 多缓冲区和偏移设置

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;
    attribute float aPointSize;
    void main() {
      gl_Position = aPosition;
      gl_PointSize = aPointSize;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const aPointSize = gl.getAttribLocation(program, 'aPointSize');

  const points = new Float32Array([
    -0.5, -0.5, 10.0, // 10.0
    0.5, -0.5, 20.0, // 20.0
    0.0,  0.5, 30.0, // 30.0
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  const BYTES = points.BYTES_PER_ELEMENT;
  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 3, 0);

  gl.enableVertexAttribArray(aPosition)

  gl.vertexAttribPointer(aPointSize, 1, gl.FLOAT, false, BYTES * 3, BYTES * 2);

  gl.enableVertexAttribArray(aPointSize)

  gl.drawArrays(gl.POINTS, 0, 3);
</script>
  • points.BYTES_PER_ELEMENT:它返回数组的每个元素所占用的字节数
  • 当顶点数据包含多个属性(如位置、颜色、法线等),并且这些属性在缓冲区中是交错排列的(即它们一起组成了一个大数组,每个顶点的所有属性紧接着上一个顶点的所有属性),需要使用 stride 来指定从一个顶点的开始到下一个顶点的开始的字节数。
  • 如果顶点数据只包含一种属性(如仅位置坐标),并且这些属性在缓冲区中是紧密排列的(即没有间隙),可以将 stride 设置为 0,让 WebGL 知道如何读取数据。
  • gl.vertexAttribPointer:offset偏移量根据当前使用的数据来计算,因为案例中前两个数据是提供给位置的,第三个数据是给大小使用的,所以偏移量为BYTES * 2

# 渐变三角形

在顶点着色器和片元着色器之间,你可以使用varying类型的变量来传递数据。当在顶点着色器中为varying变量设置不同的值时,GPU会在顶点之间插值这些值,并在片元着色器中使用插值后的结果。

这使得你可以通过调整顶点着色器中varying变量的值来实现渐变效果。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition;

    varying vec4 vColor;

    void main() {
      vColor = aPosition;

      gl_Position = aPosition;
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    precision lowp float;
    varying vec4 vColor;

    void main() {
      gl_FragColor = vColor;
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');

  const points = new Float32Array([
    -0.5, -0.5,
    0.5, -0.5,
    0.0,  0.5,
  ])

  const buffer = gl.createBuffer();

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, 0, 0);

  gl.enableVertexAttribArray(aPosition)

  gl.drawArrays(gl.TRIANGLES, 0, 3);
</script>

# webgl 渲染流程

# webgl 绘制纹理

  1. gl.texParameteri方法:设置纹理参数。该方法通常接受三个参数,第一个参数指定纹理类型(如GL_TEXTURE_2D),第二个参数指定参数名称(如纹理的放大、缩小过滤方式或纹理的环绕方式),第三个参数指定参数值。
  2. gl.bindTexture方法:将纹理对象绑定到指定的纹理目标上。通常接受两个参数,第一个参数指定纹理目标(如GL_TEXTURE_2D),第二个参数是纹理对象的名称(通常是一个非零的整数)。
  3. gl.texImage2D方法:指定一个二维纹理图像的参数和数据。该方法有多个参数,包括纹理目标、细节级别、内部格式、宽度、高度、边框、格式、类型以及指向图像数据的指针。
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
      background: yellow;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;

    attribute vec4 aTex;

    varying vec2 vTex;

    void main() {
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      vTex = vec2(aTex.x, aTex.y);
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    precision lowp float;
    uniform sampler2D uSampler;
    varying vec2 vTex;

    void main() {
      gl_FragColor = texture2D(uSampler, vTex);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const aTex = gl.getAttribLocation(program, 'aTex');
  // 获得的采样器统一变量的位置
  const uSampler = gl.getUniformLocation(program, 'uSampler');

  const points = new Float32Array([
    -0.5,  0.5, 0.0, 1.0,
    -0.5, -0.5, 0.0, 0.0,
     0.5,  0.5, 1.0, 1.0,
     0.5, -0.5, 1.0, 0.0,
  ])

  const buffer = gl.createBuffer();
  const BYTES = points.BYTES_PER_ELEMENT;

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0);

  gl.enableVertexAttribArray(aPosition)

  gl.vertexAttribPointer(aTex, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2);

  gl.enableVertexAttribArray(aTex)

  const img = new Image();
  img.onload = function() {
    // 创建纹理对象
    const texture = gl.createTexture();

    // 翻转 图片 Y轴
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)

    // 开启一个纹理单元
    gl.activeTexture(gl.TEXTURE0);//激活纹理单元0  

    // 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 处理放大缩小的逻辑
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)

    // 横向 纵向 平铺的方式
    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.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);
    // 设置采样器统一变量到纹理单元索引(在这里是0)
    gl.uniform1i(uSampler, 0);

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  }

  img.src = '../assets/content.png'

</script>

gl.uniform1i(uSampler, 0); 是一个用于设置着色器(shader)中整数(integer)统一变量(uniform variable)值的函数调用。

# 放大镜案例

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="../lib/index.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    canvas{
      margin: 50px auto 0;
      display: block;
    }
  </style>
</head>
<body>
<canvas id="canvas" width="400" height="400">
  此浏览器不支持canvas
</canvas>
</body>
</html>
<script>

  const ctx = document.getElementById('canvas')

  const gl = ctx.getContext('webgl')

  // 创建着色器源码
  const VERTEX_SHADER_SOURCE = `
    // 只传递顶点数据
    attribute vec4 aPosition;

    attribute vec4 aTex;

    varying vec2 vTex;
    varying vec4 vPosition;

    void main() {
      vPosition = aPosition;
      gl_Position = aPosition; // vec4(0.0,0.0,0.0,1.0)
      vTex = vec2(aTex.x, aTex.y);
    }
  `; // 顶点着色器

  const FRAGMENT_SHADER_SOURCE = `
    precision lowp float;
    uniform sampler2D uSampler;
    varying vec2 vTex;
    uniform vec2 lookAt; // 观察点的坐标
    varying vec4 vPosition;

    void main() {
      vec2 uv = vTex;

      float fOpacity = 0.0;
      // 1. 判断当前点和 lookAt 的距离
      float dis = distance(lookAt, vec2(vPosition));
      if (dis > 0.2) {
        fOpacity = 0.05;
      } else {
        fOpacity = 1.0;
        // vPosition.xy 创建了一个新的 vec2 向量,它包含了 vPosition 的前两个分量(即 .x 和 .y)。
        //计算了 vPosition 的前两个分量与 lookAt之间的差值,并将结果存储在 diff 这个 vec2 变量中。
        vec2 diff = vPosition.xy - lookAt;
        uv.x -= diff.x * 0.2;
        uv.y += diff.y * 0.2;
      }
      vec4 color = texture2D(uSampler, uv);
      gl_FragColor = vec4(color.xyz * fOpacity, fOpacity);
    }
  `; // 片元着色器

  const program = initShader(gl, VERTEX_SHADER_SOURCE, FRAGMENT_SHADER_SOURCE)

  const aPosition = gl.getAttribLocation(program, 'aPosition');
  const aTex = gl.getAttribLocation(program, 'aTex');
  const uSampler = gl.getUniformLocation(program, 'uSampler');
  const lookAt = gl.getUniformLocation(program, 'lookAt');

  const points = new Float32Array([
    -0.9,  0.9, 0.0, 1.0,
    -0.9, -0.9, 0.0, 0.0,
     0.9,  0.9, 1.0, 1.0,
     0.9, -0.9, 1.0, 0.0,
  ])

  const buffer = gl.createBuffer();
  const BYTES = points.BYTES_PER_ELEMENT;

  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

  gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);

  gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0);

  gl.enableVertexAttribArray(aPosition)

  gl.vertexAttribPointer(aTex, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2);

  gl.enableVertexAttribArray(aTex)

  const img = new Image();
  img.onload = function() {
    // 创建纹理对象
    const texture = gl.createTexture();

    // 翻转 图片 Y轴
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)

    // 开启一个纹理单元
    gl.activeTexture(gl.TEXTURE0);

    // 绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D, texture);

    // 处理放大缩小的逻辑
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)

    // 横向 纵向 平铺的方式
    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.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, img);

    gl.uniform1i(uSampler, 0);
  }

  img.src = '../assets/content.png'

  function draw() {

    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    requestAnimationFrame(draw)
  }

  draw()

  document.onmousemove = (ev) => {
    // 坐标
    const x = ev.clientX
    const y = ev.clientY

    const domPosition = ev.target.getBoundingClientRect();

    const domx = x - domPosition.left
    const domy = y - domPosition.top;
    const halfWidth = ctx.offsetWidth / 2
    const halfHeight = ctx.offsetHeight / 2

    const clickX = (domx - halfWidth) / halfWidth
    const clickY = (halfHeight - domy) / halfHeight

    gl.uniform2fv(lookAt, [clickX, clickY]);
  }

</script>
最后更新: 6/14/2024, 8:13:18 AM