• 原文地址:Day 10. Multiple textures
  • 原文作者:Andrei Lesnitsky

这是 WebGL 系列的第 10 天教程,每天都有新文章公布。

订阅或者退出邮件列表以便及时获取更新内容。

源代码在这里

咱们曾经晓得如何应用单个图像作为纹理,然而如果要渲染多个图像该怎么办?

明天咱们将学习如何做到这一点。

首先,咱们须要在片段着色器中定义 sampler2D

???? src/shaders/texture.f.glsl

  precision mediump float;    uniform sampler2D texture;+ uniform sampler2D otherTexture;  uniform vec2 resolution;    vec4 inverse(vec4 color) {

同时渲染两个矩形,而不是单个矩形。右边的矩形将应用曾经存在的纹理,左边的矩形将应用新的纹理。

???? src/texture.js

  gl.linkProgram(program);  gl.useProgram(program);  - const vertexPosition = new Float32Array(createRect(-1, -1, 2, 2));+ const vertexPosition = new Float32Array([+     ...createRect(-1, -1, 1, 2), // left rect+     ...createRect(-1, 0, 1, 2), // right rect+ ]);  const vertexPositionBuffer = gl.createBuffer();    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);  gl.enableVertexAttribArray(attributeLocations.position);  gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);  - const vertexIndices = new Uint8Array([0, 1, 2, 1, 2, 3]);+ const vertexIndices = new Uint8Array([+     // left rect+     0, 1, 2, +     1, 2, 3, +     +     // right rect+     4, 5, 6, +     5, 6, 7,+ ]);  const indexBuffer = gl.createBuffer();    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);

咱们还须要一种为每个矩形指定纹理坐标的办法,同时咱们不再应用 gl_FragCoord ,因而咱们须要定义另一个属性(texCoord)。

???? src/shaders/texture.v.glsl

  attribute vec2 position;+ attribute vec2 texCoord;    void main() {      gl_Position = vec4(position, 0, 1);

这个属性的内容应蕴含两个矩形的坐标,左上角是 0,0,宽度和高度别离是 1.0

???? src/texture.js

  gl.linkProgram(program);  gl.useProgram(program);  + const texCoords = new Float32Array([+     ...createRect(0, 0, 1, 1), // left rect+     ...createRect(0, 0, 1, 1), // right rect+ ]);+ const texCoordsBuffer = gl.createBuffer();+ + gl.bindBuffer(gl.ARRAY_BUFFER, texCoordsBuffer);+ gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);+   const vertexPosition = new Float32Array([      ...createRect(-1, -1, 1, 2), // left rect      ...createRect(-1, 0, 1, 2), // right rect

咱们还须要在 JS 中设置 texCoord 属性:

???? src/texture.js

    const attributeLocations = {      position: gl.getAttribLocation(program, 'position'),+     texCoord: gl.getAttribLocation(program, 'texCoord'),  };    const uniformLocations = {  gl.enableVertexAttribArray(attributeLocations.position);  gl.vertexAttribPointer(attributeLocations.position, 2, gl.FLOAT, false, 0, 0);  + gl.bindBuffer(gl.ARRAY_BUFFER, texCoordsBuffer);+ + gl.enableVertexAttribArray(attributeLocations.texCoord);+ gl.vertexAttribPointer(attributeLocations.texCoord, 2, gl.FLOAT, false, 0, 0);+   const vertexIndices = new Uint8Array([      // left rect      0, 1, 2, 

将这些变动的数据传递给片段着色器:

???? src/shaders/texture.f.glsl

      );  }  + varying vec2 vTexCoord;+   void main() {-     vec2 texCoord = gl_FragCoord.xy / resolution;+     vec2 texCoord = vTexCoord;      gl_FragColor = texture2D(texture, texCoord);        gl_FragColor = sepia(gl_FragColor);

???? src/shaders/texture.v.glsl

  attribute vec2 position;  attribute vec2 texCoord;  + varying vec2 vTexCoord;+   void main() {      gl_Position = vec4(position, 0, 1);+ +     vTexCoord = texCoord;  }

好的,咱们渲染了两个矩形,然而它们应用雷同的纹理。咱们再增加一个属性,该属性将指定要应用的纹理并将此数据通过另一种变动传递给片段着色器。

???? src/shaders/texture.v.glsl

  attribute vec2 position;  attribute vec2 texCoord;+ attribute float texIndex;    varying vec2 vTexCoord;+ varying float vTexIndex;    void main() {      gl_Position = vec4(position, 0, 1);        vTexCoord = texCoord;+     vTexIndex = texIndex;  }

所以当初片段着色器将晓得要应用哪种纹理。

留神:这不是在片段着色器中应用多个纹理的最好办法,只是如何实现此目标的示例。

???? src/shaders/texture.f.glsl

  }    varying vec2 vTexCoord;+ varying float vTexIndex;    void main() {      vec2 texCoord = vTexCoord;-     gl_FragColor = texture2D(texture, texCoord);  -     gl_FragColor = sepia(gl_FragColor);+     if (vTexIndex == 0.0) {+         gl_FragColor = texture2D(texture, texCoord);+     } else {+         gl_FragColor = texture2D(otherTexture, texCoord);+     }  }

对于左矩形的 tex 索引是 0,对于右矩形的 tex 索引是 1

???? src/texture.js

  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordsBuffer);  gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);  + const texIndicies = new Float32Array([+     ...Array.from({ length: 4 }).fill(0), // left rect+     ...Array.from({ length: 4 }).fill(1), // right rect+ ]);+ const texIndiciesBuffer = gl.createBuffer();+ + gl.bindBuffer(gl.ARRAY_BUFFER, texIndiciesBuffer);+ gl.bufferData(gl.ARRAY_BUFFER, texIndicies, gl.STATIC_DRAW);+   const vertexPosition = new Float32Array([      ...createRect(-1, -1, 1, 2), // left rect      ...createRect(-1, 0, 1, 2), // right rect

再次,咱们须要设置顶点属性:

???? src/texture.js

  const attributeLocations = {      position: gl.getAttribLocation(program, 'position'),      texCoord: gl.getAttribLocation(program, 'texCoord'),+     texIndex: gl.getAttribLocation(program, 'texIndex'),  };    const uniformLocations = {  gl.enableVertexAttribArray(attributeLocations.texCoord);  gl.vertexAttribPointer(attributeLocations.texCoord, 2, gl.FLOAT, false, 0, 0);  + gl.bindBuffer(gl.ARRAY_BUFFER, texIndiciesBuffer);+ + gl.enableVertexAttribArray(attributeLocations.texIndex);+ gl.vertexAttribPointer(attributeLocations.texIndex, 1, gl.FLOAT, false, 0, 0);+   const vertexIndices = new Uint8Array([      // left rect      0, 1, 2, 

当初让咱们加载第二个纹理图像:

???? src/texture.js

  import { createRect } from './shape-helpers';    import textureImageSrc from '../assets/images/texture.jpg';+ import textureGreenImageSrc from '../assets/images/texture-green.jpg';    const canvas = document.querySelector('canvas');  const gl = canvas.getContext('webgl');  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW);  - loadImage(textureImageSrc).then((textureImg) => {+ Promise.all([+     loadImage(textureImageSrc),+     loadImage(textureGreenImageSrc),+ ]).then(([textureImg, textureGreenImg]) => {      const texture = gl.createTexture();        gl.bindTexture(gl.TEXTURE_2D, texture);

因为咱们必须创立另一个纹理 – 咱们须要提取一些通用代码来拆散辅助函数:

???? src/gl-helpers.js

        return p;  }+ + export function createTexture(gl) {+     const texture = gl.createTexture();+     +     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);+ +     return texture;+ }+ + export function setImage(gl, texture, img) {+     gl.bindTexture(gl.TEXTURE_2D, texture);+ +     gl.texImage2D(+         gl.TEXTURE_2D,+         0,+         gl.RGBA,+         gl.RGBA,+         gl.UNSIGNED_BYTE,+         img,+     );+ }

???? src/texture.js

      loadImage(textureImageSrc),      loadImage(textureGreenImageSrc),  ]).then(([textureImg, textureGreenImg]) => {-     const texture = gl.createTexture();- -     gl.bindTexture(gl.TEXTURE_2D, texture);- -     gl.texImage2D(-         gl.TEXTURE_2D,-         0,-         gl.RGBA,-         gl.RGBA,-         gl.UNSIGNED_BYTE,-         textureImg,-     );- -     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);+         gl.activeTexture(gl.TEXTURE0);      gl.uniform1i(uniformLocations.texture, 0);

当初,让咱们应用咱们新创建的助手:

???? src/texture.js

  import vShaderSource from './shaders/texture.v.glsl';  import fShaderSource from './shaders/texture.f.glsl';- import { compileShader, loadImage } from './gl-helpers';+ import { compileShader, loadImage, createTexture, setImage } from './gl-helpers';  import { createRect } from './shape-helpers';    import textureImageSrc from '../assets/images/texture.jpg';      loadImage(textureImageSrc),      loadImage(textureGreenImageSrc),  ]).then(([textureImg, textureGreenImg]) => {+     const texture = createTexture(gl);+     setImage(gl, texture, textureImg);  +     const otherTexture = createTexture(gl);+     setImage(gl, otherTexture, textureGreenImg);        gl.activeTexture(gl.TEXTURE0);      gl.uniform1i(uniformLocations.texture, 0);

取得对立的地位:

???? src/texture.js

    const uniformLocations = {      texture: gl.getUniformLocation(program, 'texture'),+     otherTexture: gl.getUniformLocation(program, 'otherTexture'),      resolution: gl.getUniformLocation(program, 'resolution'),  };  

并为必要的 uniforms 设置必要的纹理。

uniform 设置纹理,您应该指定。

  • 纹理单元在范畴 [gl.TEXTURE0..gl.TEXTURE31] 内流动(纹理单元的数量取决于GPU,能够应用 gl.getParameter 进行复原)
  • 将纹理绑定到纹理单元
  • 将纹理单元“索引”设置为 sampler2D uniform

???? src/texture.js

      setImage(gl, otherTexture, textureGreenImg);        gl.activeTexture(gl.TEXTURE0);+     gl.bindTexture(gl.TEXTURE_2D, texture);      gl.uniform1i(uniformLocations.texture, 0);  +     gl.activeTexture(gl.TEXTURE1);+     gl.bindTexture(gl.TEXTURE_2D, otherTexture);+     gl.uniform1i(uniformLocations.otherTexture, 1);+       gl.uniform2fv(uniformLocations.resolution, [canvas.width, canvas.height]);        gl.drawElements(gl.TRIANGLES, vertexIndices.length, gl.UNSIGNED_BYTE, 0);

就是这样,咱们当初能够渲染多个纹理。

今天见????