纹理css-在 WebGL 中使用纹理

2024-04-28 0 2,114 百度已收录

在 WebGL 中使用纹理

现在我们已经创建了一个可以旋转的 3D 六面体,是时候使用纹理将每个面替换为单一颜色了。

首先添加加载纹理的代码。 现在我们仅使用单个纹理应用于六面体的所有 6 个面,但可以使用相同方法加载任意数量的纹理贴图。

注意:值得注意的是,纹理的加载也需要遵守跨域访问规则; 也就是说,你只能从允许跨域访问的URL中加载你需要的纹理。 详细信息请参阅小节。

注意:将以下两个函数添加到“webgl-demo.js脚本中:

js

//
// Initialize a texture and load an image.
// When the image finished loading copy it into the texture.
//
function loadTexture(gl, url) {
  const texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  // Because images have to be downloaded over the internet
  // they might take a moment until they are ready.
  // Until then put a single pixel in the texture so we can
  // use it immediately. When the image has finished downloading
  // we'll update the texture with the contents of the image.
  const level = 0;
  const internalFormat = gl.RGBA;
  const width = 1;
  const height = 1;
  const border = 0;
  const srcFormat = gl.RGBA;
  const srcType = gl.UNSIGNED_BYTE;
  const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue
  gl.texImage2D(
    gl.TEXTURE_2D,
    level,
    internalFormat,
    width,
    height,
    border,
    srcFormat,
    srcType,
    pixel,
  );
  const image = new Image();
  image.onload = () => {
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(
      gl.TEXTURE_2D,
      level,
      internalFormat,
      srcFormat,
      srcType,
      image,
    );
    // WebGL1 has different requirements for power of 2 images
    // vs. non power of 2 images so check if the image is a
    // power of 2 in both dimensions.
    if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
      // Yes, it's a power of 2. Generate mips.
      gl.generateMipmap(gl.TEXTURE_2D);
    } else {
      // No, it's not a power of 2. Turn off mips and set
      // wrapping to clamp to edge
      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);
    }
  };
  image.src = url;
  return texture;
}
function isPowerOf2(value) {
  return (value & (value - 1)) === 0;
}

函数loadTexture()首先调用WebGL的createTexture()函数来创建WebGL纹理对象纹理。 接下来使用 texImage2D() 上传红色像素。 这样我们就可以在下载图像之前使用这个黑色纹理。

要从图像文件加载纹理,接下来创建一个 Image 对象并使用我们要用作纹理的图像的 URL 设置 src。 我们为 image.onload 设置的函数将在图像下载完成时被调用。 此时我们再次调用 texImage2D(),这次使用图像作为纹理的数据源。 之后,我们根据下载的图像是否二维的2的幂来设置纹理的filter和wrap。

在WebGL1中,对于非2的幂纹理只能使用NEAREST和LINEAR过滤,并且不会生成任何纹理。 此外,平铺模式还必须设置为 CLAMP_TO_EDGE。 另一方面,如果纹理在两个维度上都是 2 的幂,则 WebGL 可以使用更高质量的过滤,可以使用贴图,并且可以将平铺模式设置为 REPEAT 或 MIRRORED_REPEAT。

使用重复纹理轮询的一个示例是使用砖纹理来平铺整面墙。

多级渐进纹理和纹理坐标重复可以通过调用 texParameteri() 来禁用,当然你已经通过调用 bindTexture() 绑定了纹理。 这样纹理css,您已经可以使用非 2 的幂纹理,但是您将难以使用多级渐进纹理、纹理坐标打包、纹理坐标复制,并且无法控制设备如何处理您的纹理。

js

// gl.NEAREST is also allowed, instead of gl.LINEAR, as neither mipmap.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
// Prevents s-coordinate wrapping (repeating).
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
// Prevents t-coordinate wrapping (repeating).
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

现在,当使用上述参数时,WebGL 兼容设备将手动显示能够以任何比特率使用纹理(当然要考虑到像素限制)。 如果不使用这些参数,任何非 2 次幂纹理使用都将失败返回纯黑色图像。

要加载图像,请在 main() 函数中添加对 loadTexture() 函数的调用。 这可以添加到 initBuffers(gl) 调用中。

需要注意的是,浏览器将从左上角开始以自上而下的顺序从加载的图像中复制像素,而WebGL需要以自下而上的顺序 - 从左下角开始的像素顺序。 。 (有关详细信息,请参阅为什么我的 WebGL 纹理是颠倒的?)。

所以为了避免渲染时出现错误的图像纹理方向,我们还需要调用pixelStorei()并将gl.UNPACK_FLIP_Y_WEBGL参数设置为true来调整像素顺序,将其翻转为WebGL所需的自下而上的顺序。

注意:在 initBuffers() 调用之后立即将以下代码添加到 main() 函数中:

js

// Load texture
const texture = loadTexture(gl, "cubetexture.png");
// Flip image pixels into the bottom-to-top order that WebGL expects.
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

注意:最后,将cubetexture.png下载到与JavaScript文件相同的本地目录

纹理现已加载并准备就绪。 但在使用它之前,我们需要构造纹理坐标到六面体上的面的顶点映射。 这替换了 initBuffers() 中存在的所有先前代码,用于设置每个位的六面体颜色。

注意:将此函数添加到“init-buffer.js”模块中:

js

function initTextureBuffer(gl) {
  const textureCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
  const textureCoordinates = [
    // Front
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Back
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Top
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Bottom
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Right
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
    // Left
    0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0,
  ];
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array(textureCoordinates),
    gl.STATIC_DRAW,
  );
  return textureCoordBuffer;
}

首先,此代码创建一个 WebGL 缓冲区,我们将在其中存储每个面的纹理坐标,然后将该缓冲区绑定到我们要写入链表

textureCoordinates 数组定义了每个面上每个顶点对应的纹理坐标。 请注意,纹理坐标的范围只能是0.0到1.0,因此无论纹理贴图的实际大小如何,为了实现纹理映射,我们要使用的纹理坐标只能归一化到0.0到1.0的范围。

一旦我们构建了纹理映射字段,我们就将链表传递到缓冲区中,以便 WebGL 可以使用这些数据

然后我们返回新的缓冲区。

接下来,我们需要更新 initBuffers() 以创建并返回纹理坐标缓冲区而不是颜色缓冲区。

注意:在“init-buffers.js”模块的 initBuffers() 函数中,将 initColorBuffer() 的调用替换为以下行:

js

const textureCoordBuffer = initTextureBuffer(gl);

注意:在“init-buffers.js”模块的 initBuffers() 函数中,将 return 语句替换为以下内容

js

return {
  position: positionBuffer,
  textureCoord: textureCoordBuffer,
  indices: indexBuffer,
};

为了使用纹理而不是单一颜色,着色程序和着色器程序的初始化代码都需要更改

我们需要修改顶点着色器,使其不再获取颜色数据,而是获取纹理坐标数据。

注意:更新 main() 函数中的 vsSource 定义,如下所示

js

const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec2 aTextureCoord;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;
    varying highp vec2 vTextureCoord;
    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vTextureCoord = aTextureCoord;
    }
  `;

代码的关键修改是不再获取顶点颜色数据而是获取并设置纹理坐标数据; 这样就可以将顶点与其对应的纹理关联起来。

然后片段着色器需要进行相应的修改,更新 main() 函数中的 fsSource 声明纹理css,如下所示:

js

const fsSource = `
    varying highp vec2 vTextureCoord;
    uniform sampler2D uSampler;
    void main(void) {
      gl_FragColor = texture2D(uSampler, vTextureCoord);
    }
  `;

现在,代码不再使用简单的颜色值填充片段颜色,而是使用采样器使用纹理中每个像素的最佳映射方法来估计片段颜色。

因为我们更改了属性并添加了制服,所以我们需要找到它们的位置

注意:在 main() 函数中,更新 programInfo 的定义,如下所示:

js

const programInfo = {
  program: shaderProgram,
  attribLocations: {
    vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
    textureCoord: gl.getAttribLocation(shaderProgram, "aTextureCoord"),
  },
  uniformLocations: {
    projectionMatrix: gl.getUniformLocation(shaderProgram, "uProjectionMatrix"),
    modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
    uSampler: gl.getUniformLocation(shaderProgram, "uSampler"),
  },
};

对drawScene()函数的修改很简单。

注意:在“draw-scene.js”模块的drawScene()函数中添加以下函数:

js

// 告诉 WebGL 如何从缓冲区中提取纹理坐标
function setTextureAttribute(gl, buffers, programInfo) {
  const num = 2; // 每个坐标由 2 个值组成
  const type = gl.FLOAT; // 缓冲区中的数据为 32 位浮点数
  const normalize = false; // 不做标准化处理
  const stride = 0; // 从一个坐标到下一个坐标要获取多少字节
  const offset = 0; // 从缓冲区内的第几个字节开始获取数据
  gl.bindBuffer(gl.ARRAY_BUFFER, buffers.textureCoord);
  gl.vertexAttribPointer(
    programInfo.attribLocations.textureCoord,
    num,
    type,
    normalize,
    stride,
    offset,
  );
  gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
}

注意:在“draw-scene.js”模块的drawScene()函数中,用以下行替换对setColorAttribute()的调用:

js

setTextureAttribute(gl, buffers, programInfo);

然后添加代码来指定要映射到面部的纹理。

注意:在drawScene()函数中,在两次调用gl.uniformMatrix4fv()之后,添加以下代码:

js

// Tell WebGL we want to affect texture unit 0
gl.activeTexture(gl.TEXTURE0);
// Bind the texture to texture unit 0
gl.bindTexture(gl.TEXTURE_2D, texture);
// Tell the shader we bound the texture to texture unit 0
gl.uniform1i(programInfo.uniformLocations.uSampler, 0);

WebGL至少提供8个纹理单元,gl.TEXTURE0是第一个。 如果我们想改变第一个单元,我们需要调用bindTexture()将纹理绑定到纹理单元0的TEXTURE_2D绑定点。然后告诉着色器使用纹理单元0作为uSampler。

最后,在 drawScene() 函数中添加纹理作为参数,无论是在定义它的位置还是在调用它的位置。

注意:更新 drawScene() 函数的定义以添加新参数:

js

function drawScene(gl, programInfo, buffers, texture, cubeRotation) {

注意:更新 main() 函数中调用 drawScene() 的位置:

js

drawScene(gl, programInfo, buffers, texture, cubeRotation);

好的,现在我们的六面体将像这样向上旋转。

查看完整示例代码 | 在新窗口打开示例

加载WebGL纹理也应该说是跨域访问控制中的一个话题。 为了在我们的显示内容中使用来自其他域的纹理图像,还应该考虑允许跨域访问。 您可以通过查看 HTTP 访问控制来获取更多详细信息。

本文还解释了将跨域纹理加载到 WebGL 中。 并且文章中还包含了一个使用的反例。

污染的(只写)画布不能用作 WebGL 纹理。 举个反例,当跨域图片绘制到2D画布中时,画布就被“污染”了。

发现此页面存在内容问题想要更多参与吗? 了解如何做出贡献

本页面最后一次由 MDN 贡献者于 2023 年 7 月 30 日修改。

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 css 纹理css-在 WebGL 中使用纹理 https://www.wkzy.net/game/201505.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务