WebGL渲染与创建2D内容

目录


WebGL是一个强大的工具,可以用来在Web浏览器中创建复杂的3D图形。虽然它的设计初衷是为了3D渲染,但也可以用于创建2D内容。通过巧妙地利用几何、投影和纹理,我们可以构建出各种2D图形。

创建画布

首先,我们需要在HTML中设置一个<canvas>元素,用于承载WebGL渲染的内容:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebGL 2D Example</title>
</head>
<body>
  <canvas id="webgl-canvas" width="600" height="400"></canvas>
  <script src="main.js"></script>
</body>
</html>

2D渲染

// 获取canvas元素和WebGL上下文
const canvas = document.getElementById('webgl-canvas');
const gl = canvas.getContext('webgl', { antialias: false });

// 设置视口大小和清除颜色
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(1, 1, 1, 1); // 白色背景
gl.clear(gl.COLOR_BUFFER_BIT);

// 定义2D坐标变换矩阵
const projectionMatrix = mat3.create();
mat3.identity(projectionMatrix);
mat3.translate(projectionMatrix, projectionMatrix, [-1, -1, 0]);
mat3.scale(projectionMatrix, projectionMatrix, [2 / canvas.width, 2 / canvas.height, 1]);

// 创建顶点着色器和片段着色器
const vertexShaderSource = `
attribute vec2 position;
uniform mat3 projectionMatrix;
void main() {
  gl_Position = vec4((projectionMatrix * vec3(position, 1)).xy, 0, 1);
}
`;

const fragmentShaderSource = `
precision mediump float;
uniform vec4 color;
void main() {
  gl_FragColor = color;
}
`;

// 编译和链接着色器
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);

// 获取顶点位置和颜色的属性位置
const positionAttributeLocation = gl.getAttribLocation(program, 'position');
const colorUniformLocation = gl.getUniformLocation(program, 'color');

// 创建顶点缓冲区并设置顶点数据
const vertices = [
  -1, -1, // 左下角
  1, -1,  // 右下角
  -1, 1,  // 左上角
  1, 1,   // 右上角
];
const positionBuffer = createBuffer(gl, vertices);

// 绘制矩形
function drawRectangle(color) {
  gl.useProgram(program);
  gl.uniform4fv(colorUniformLocation, color);
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length / 2);
}

// 初始化绘图
drawRectangle([0, 0, 255, 255]); // 蓝色矩形

// 动画循环
function animate() {
  requestAnimationFrame(animate);
  
  // 更新颜色
  let t = performance.now() / 1000;
  let r = Math.sin(t) * 128 + 128;
  let g = Math.cos(t) * 128 + 128;
  let b = 255;
  drawRectangle([r / 255, g / 255, b / 255, 1]);

  // 渲染到屏幕
  gl.flush();
}

animate();

上述代码中,设置了一个2D投影矩阵,将[-1, 1]的坐标范围映射到canvas的[0, width]和[0, height]。接着,我们创建了顶点着色器和片段着色器,用于处理顶点位置和颜色。然后,我们设置了顶点数据并创建了一个缓冲区,用于存储矩形的四个顶点。最后,我们定义了一个drawRectangle函数,用于绘制矩形,并在动画循环中不断改变颜色。

通过对顶点进行2D坐标变换实现的。在顶点着色器中,我们使用了projectionMatrix来转换顶点位置,使其适应canvas的2D坐标系。在片段着色器中,我们简单地将颜色传入并输出。

修改顶点着色器

添加一个rotationscale uniforms

const vertexShaderSource = `
attribute vec2 position;
uniform mat3 projectionMatrix;
uniform float rotation;
uniform vec2 scale;
void main() {
  // 应用旋转和平移
  vec2 rotatedPosition = vec2(
    position.x * cos(rotation) - position.y * sin(rotation),
    position.x * sin(rotation) + position.y * cos(rotation)
  );

  // 应用缩放
  vec2 scaledPosition = vec2(
    rotatedPosition.x * scale.x,
    rotatedPosition.y * scale.y
  );

  gl_Position = vec4((projectionMatrix * vec3(scaledPosition, 1)).xy, 0, 1);
}
`;

创建和更新这些uniforms:

// 新增旋转和缩放uniforms
const rotationUniformLocation = gl.getUniformLocation(program, 'rotation');
const scaleUniformLocation = gl.getUniformLocation(program, 'scale');

// 修改drawRectangle函数,添加旋转和缩放
function drawRectangle(color, rotation, scale) {
  gl.useProgram(program);
  gl.uniform4fv(colorUniformLocation, color);
  gl.uniform1f(rotationUniformLocation, rotation);
  gl.uniform2fv(scaleUniformLocation, scale);

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length / 2);
}

// 动画循环
function animate() {
  requestAnimationFrame(animate);

  // 更新旋转和缩放
  let t = performance.now() / 1000;
  let rotationValue = t * Math.PI * 2; // 旋转角度
  let scaleX = 1 + Math.sin(t) * 0.2; // 缩放比例X
  let scaleY = 1 + Math.cos(t) * 0.2; // 缩放比例Y

  // 绘制旋转和缩放的矩形
  drawRectangle([0, 0, 255, 255], rotationValue, [scaleX, scaleY]);

  // 渲染到屏幕
  gl.flush();
}

animate();

现在,矩形会随着时间的推移而旋转和缩放。rotation uniform用于控制旋转角度,scale uniform是一个二维向量,用于控制X轴和Y轴的缩放比例。

平移

修改drawRectangle函数以接受平移参数,并在动画循环中更新平移值:

// 新增平移uniform
const translationUniformLocation = gl.getUniformLocation(program, 'translation');

// 修改drawRectangle函数,添加平移
function drawRectangle(color, rotation, scale, translation) {
  gl.useProgram(program);
  gl.uniform4fv(colorUniformLocation, color);
  gl.uniform1f(rotationUniformLocation, rotation);
  gl.uniform2fv(scaleUniformLocation, scale);
  gl.uniform2fv(translationUniformLocation, translation);

  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.enableVertexAttribArray(positionAttributeLocation);
  gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertices.length / 2);
}

// 动画循环
function animate() {
  requestAnimationFrame(animate);

  // 更新旋转、缩放和平移
  let t = performance.now() / 1000;
  let rotationValue = t * Math.PI * 2;
  let scaleX = 1 + Math.sin(t) * 0.2;
  let scaleY = 1 + Math.cos(t) * 0.2;
  let translationX = Math.sin(t) * canvas.width * 0.2;
  let translationY = Math.cos(t) * canvas.height * 0.2;

  // 绘制旋转、缩放和平移的矩形
  drawRectangle([0, 0, 255, 255], rotationValue, [scaleX, scaleY], [translationX, translationY]);

  // 渲染到屏幕
  gl.flush();
}

animate();

上面添加了一个新的uniform translation,用于控制矩形的平移。在动画循环中,我们计算平移值,使其随时间变化。这样,矩形不仅会旋转和缩放,还会在canvas内移动。

WebGL还支持纹理贴图,可以用于创建复杂的2D图像。假设我们有一个名为texture.png的图像,我们可以将其加载并应用于矩形:

// 加载纹理
const texture = loadTexture(gl, 'texture.png');

// 在drawRectangle中应用纹理
function drawRectangle(color, rotation, scale, translation, texture) {
  // ...
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.uniform1i(gl.getUniformLocation(program, 'texture'), 0);

  // ...
}

// 创建纹理
function loadTexture(gl, url) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  const level = 0;
  const internalFormat = gl.RGBA;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED_BYTE;
  const pixelData = null;

  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    border,
    srcFormat,
    srcType,
    pixelData
  );

  const image = new Image();
  image.onload = () => {
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, gl.RGBA, gl.UNSIGNED_BYTE, image);
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);
  };
  image.src = url;

  return texture;
}

// 修改drawRectangle调用,传递纹理
drawRectangle([1, 1, 1, 1], rotationValue, [scaleX, scaleY], [translationX, translationY], texture);

现在,随着旋转、缩放和平移,图像也会相应地改变。

光照

在WebGL中,光照通常在片段着色器中计算。你需要定义光源的位置、颜色以及物体表面的属性(如法线)。

   const fragmentShaderSource = `
   precision mediump float;
   uniform vec4 color;
   uniform vec3 lightPosition;
   varying vec3 vNormal;
   varying vec3 vWorldPosition;

   void main() {
     vec3 lightDirection = normalize(lightPosition - vWorldPosition);
     vec3 diffuse = max(dot(vNormal, lightDirection), 0.0) * color.rgb;

     gl_FragColor = vec4(diffuse, color.a);
   }
   `;
   
   // 在顶点着色器中传递法线和世界位置
   const vertexShaderSource = `
   attribute vec3 position;
   attribute vec3 normal;
   uniform mat4 modelViewMatrix;
   uniform mat4 projectionMatrix;
   varying vec3 vNormal;
   varying vec3 vWorldPosition;

   void main() {
     vNormal = normalize(mat3(modelViewMatrix) * normal);
     vWorldPosition = vec3(modelViewMatrix * vec4(position, 1.0));
     gl_Position = projectionMatrix * vec4(vWorldPosition, 1.0);
   }
   `;

在上述代码中,我们计算了光线与物体表面的点的法线向量的点积,用于确定漫反射光的强度。请注意,实际的光照模型可能更复杂,包括镜面高光、环境光等。

深度测试

WebGL默认启用深度测试,但你可以通过gl.enable(gl.DEPTH_TEST)来确认。深度测试用于确定哪些像素应该在前景,哪些应该在背景。确保在绘制3D形状时,先绘制远处的形状,再绘制近处的形状。

   gl.enable(gl.DEPTH_TEST);
   gl.depthFunc(gl.LESS); // 使用小于比较函数

混合模式

在WebGL中,混合模式通过gl.blendFuncgl.blendEquation设置。

   gl.enable(gl.BLEND);
   gl.blendEquation(gl.FUNC_ADD);
   gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天涯学馆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值