webgl纹理贴图机制

news2024/9/22 13:38:10

文章目录

  • 前言
  • 纹理图片大小规范
  • 纹理坐标系统
  • 贴图流程
    • JavaScript部分
      • 齐次坐标—uv坐标数据准备
      • 加载外部纹理图像
      • 纹理配置加载
    • 着色器部分
      • 顶点着色器
      • 片元着色器
    • 完整示例
  • 使用多张纹理
    • 着色器接受两个纹理单元
    • 封装纹理配置赋值函数
    • 完整示例
  • 总结


前言

在计算机图形学中,为了模拟更加真实的效果,需要给每个像素赋予不同的颜色值,这种情况下如果手动指定每个像素点的rgb值,将会是一件难以完成的任务。这就需要有一种机制,能够让我们把图片素材渲染到模型的一个或者多个表面上,这种机制叫做纹理贴图,本文将详细介绍在webgl中如何把一张真实图像贴到计算器图形上的。


纹理图片大小规范

WebGL 对纹理图片大小是有要求的,图片的宽度和高度必须是2的N次幂,比如 16 x 16,32 x 32,32 x 64 等。实际上,不是这个尺寸的图片也能进行贴图,但是这样不仅会增加更多的处理,还会影响性能。

纹理坐标系统

纹理也有一套自己的坐标系统,纹理坐标一般被称为 uv 坐标系(或者st),u 代表横轴坐标,v 代表纵轴坐标,他们的范围都是0到1。不管纹理图像本身的长宽是多少,都处于这个坐标系下。
在这里插入图片描述

贴图流程

JavaScript部分

在javascript中将纹理信息传递给着色器的步骤是固定的,类似缓冲区,流程虽然比较繁琐,但是熟能生巧,下面来一步步解析:

齐次坐标—uv坐标数据准备

    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const aTextCoord = gl.getAttribLocation(program, 'aTextCoord');
    const uSample = gl.getUniformLocation(program, 'uSample');
    // 创建缓冲区对象
    const buffer = gl.createBuffer();
    // 绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    // 传入的数据
    const vertices = new Float32Array([
      -0.5, 0.5,    0.0, 1.0,  // 齐次坐标x, 齐次坐标y, uv坐标u, uv坐标v
      -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 BYTES = vertices.BYTES_PER_ELEMENT;
 
    // 开辟空间并写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

    // 缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0)
    // 开启attribue缓冲区变量
    gl.enableVertexAttribArray(aPosition)

    gl.vertexAttribPointer(aTextCoord, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2)
    gl.enableVertexAttribArray(aTextCoord)

这里主要关注向缓冲区传递的数据vertices ,第一个顶点齐次坐标为(-0.5,0.5),其对应的纹理图像的uv坐标是(0.0,1.0);第二个顶点齐次坐标为(-0.5,-0.5),其对应的纹理图像的uv坐标是(0.0,0.0),依次类推。对应关系如下图:
在这里插入图片描述
然后通过 gl.vertexAttribPointer()方法从缓冲区向着色器中传递attribute变量aPosition和aTextCoord,分别表示齐次坐标和对应的uv映射,uSample 的用处将在稍后介绍。

加载外部纹理图像

    const img = new Image()
    img.onload = function(){
      // ...
    }
    img.src = "./img/sky.jpg"

我们使用 new Image 新建对象,由于图像的加载是异步的,因此需要对图像进行监听,当图像加载完成(onload事件触发)时,进入后续的步骤。

纹理配置加载

  • 创建纹理对象

使用gl.createTexture() 方法创建纹理对象,该方法不需要参数,返回新创建的纹理对象,webgl中的纹理由纹理对象统一管理

    img.onload = function(){
      const texture = gl.createTexture() // 创建纹理对象
    }
  • 图像Y轴反转

webgl中图像纹理坐标系的v和图片的坐标Y是相反的,因此,要先把图像Y轴进行反转,才能正确的映射纹理坐标:
在这里插入图片描述

    img.onload = function(){
      const texture = gl.createTexture() // 创建纹理对象
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 图像反转Y轴
    }

gl.pixelStorei规范如下:

gl.pixelStorei (pname,param): 使用pname和param的指定方式得到处理后的图像

  • pname的枚举如下
    – gl.UNPACK_FLIP_Y_WEBGL:对图像进行Y轴反转
    – gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL:将图像RGB的每个分量乘以A
  • param: 非0值(true)或 0(false)
  • 激活纹理单元并绑定至纹理对象

webgl通过名为纹理单元的机制来使用一个或多个纹理,即使使用的纹理图像只有一张,也必须为其指定一个纹理单元。在激活纹理单元后,类似于缓冲区机制,还需要将其绑定纹理对象。

    img.onload = function(){
      const texture = gl.createTexture() // 创建纹理对象
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 图像反转Y轴
      gl.activeTexture(gl.TEXTURE0) // 激活纹理单元
      gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象
    }

纹理单元的激活通过gl.activeTexture()函数

gl.activeTexture(textureUnit): 激活纹理单元,被激活的单元编号是textureUnit

  • textureUnit :gl.TEXTURE0、gl.TEXTURE1、gl.TEXTURE2····

gl.bindTexture(target, texture) 将纹理单元开启并绑定至纹理对象

gl.bindTexture (target,texture): 开启texture指定的纹理对象,将其绑定至target上

  • target的枚举如下
    – gl.TEXTURE_2D:二维纹理
    – gl.TEXTURE_CUBE_MAP:立方体纹理
  • texture: 要绑定的纹理对象

完成这两步后,纹理单元的状态将会发生改变:
在这里插入图片描述

  • 配置纹理对象参数

接下来,需要配置纹理对象的参数,设置纹理映射到图形的具体方式:如何根据纹理坐标获取纹素颜色,使用哪种方式填充纹理。纹理对象参数配置靠gl.texParameterf()函数实现。

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

      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 反转Y轴
      gl.activeTexture(gl.TEXTURE0) // 激活纹理单元
      gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象

      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // 放大处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 缩小处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // 水平平铺方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) // 竖直平铺方式

    }

gl.texParameterf() 规范如下:

gl.texParameterf(target,pname,param): 将参数param的值,将其绑定至目标纹理对象的panme属性上

  • target的枚举如下,与绑定纹理对象函数gl.bindTexture的第一个参数相同。
    – gl.TEXTURE_2D:二维纹理
    – gl.TEXTURE_CUBE_MAP:立方体纹理
  • pname: 纹理属性
  • param: 纹理属性对应的参数

纹理属性pname可以指定以下几种

纹理参数描述默认值
gl.TEXTURE_MAG_FILTER放大方法,当纹理的绘制范围比纹理本身更大时,如何获取纹素颜色。如将16 * 16的纹理图像映射到32 * 32的图形上,需要填充不足的纹理图像的像素gl.LINEAR
gl.TEXTURE_MIN_FILTER缩小方法,当纹理的绘制范围比纹理本身更小时,如何获取纹素颜色。如将32 * 32的纹理图像映射到16 * 16的图形上,需要剔除多余的纹理图像的像素gl.LINEAR
gl.TEXTURE_WRAP_S水平填充方法,如何在水平方向上对纹理图像左右侧进行填充gl.REPEAT
gl.TEXTURE_WRAP_T竖直填充方法,如何在水平方向上对纹理图像上下侧进行填充gl.REPEAT

在这里插入图片描述

当pname为gl.TEXTURE_MAG_FILTER或者gl.TEXTURE_MIN_FILTER时,param可选如下:

描述
gl.NEAREST使用原纹理上距离映射后的像素(新像素)中心最近的那个像素的颜色值作为新像素的值
gl.LINEAR使用距离新像素最近的四个像素的颜色值的加权平均作为新像素的值

当pname为gl.TEXTURE_WRAP_S或者gl.TEXTURE_WRAP_T时,param可选如下:

描述
gl.REPEAT平铺重复
gl.MIRRORED_REPEAT镜像对称重复
gl.CLAMP_TO_EDGE使用纹理边缘拉伸值
  • 纹理图像分配给纹理对象
    img.onload = function(){
      const texture = gl.createTexture() // 创建纹理对象

      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 反转Y轴
      gl.activeTexture(gl.TEXTURE0) // 激活纹理单元
      gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象

      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // 放大处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 缩小处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // 水平平铺方式
      gl.texParameterf(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.texImage2D(target,level,internalformat,format,type,image): 将imgae图像分配给绑定至target的纹理对象

  • target的枚举如下
    – gl.TEXTURE_2D:二维纹理
    – gl.TEXTURE_CUBE_MAP:立方体纹理
  • level: 0
  • internalformat: 图像内部格式
  • format: 纹理内部格式,与internalformat相同
  • type: 纹理数据类型,一般使用UNSIGNED_BYTE
  • image: 纹理图像存储的对象

internalformat和format的枚举如下表所示,其中流明表示物体表面的亮度。JPG、BMP通常使用RGB,PNG一般使用RGBA,gl.LUMINANCE和LUMINANCE_ALPHA多用于灰度图。

描述
gl.RGB红、绿、蓝
gl.RGBA红、绿、蓝、透明度
gl.ALPHA(0,0,0,透明度)
gl.LUMINANCEL、L、L、1L:流明
gl.LUMINANCE_ALPHAL、L、L、透明度

分配完成后,图像就从javascript程序中存储到了webgl系统中的纹理对象中:
在这里插入图片描述

  • 纹理单元传递给着色器

在第一步里,gl.getUniformLocation(program, ‘uSample’) 向着色器声明了一个名为uSample的uniform变量,现在,我们要把纹理单元传递给它。这一步使用 gl.uniform1i()函数,他接受两个参数:变量地址以及序号,因为之前调用绑定函数绑定的是编号 0 的gl.TEXTURE0,因此这里的参数是0:

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

      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 反转Y轴
      gl.activeTexture(gl.TEXTURE0) // 激活纹理单元
      gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象

      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // 放大处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 缩小处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // 水平平铺方式
      gl.texParameterf(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(uSample, 0) // 纹理单元传递给着色器

    }

完成传递之后webgl内部状态如下:
在这里插入图片描述

着色器部分

顶点着色器

在顶点着色器中接受缓冲区中传递的attribute变量 aPosition 和 aTextCoord;并且使用varying变量把uv坐标传递给片元着色器。

 const vertex = `
	  attribute vec4 aPosition;
      attribute vec2 aTextCoord;
      varying  vec2 vTextCoord;
	  void main() {
		gl_Position =  aPosition;
        vTextCoord = aTextCoord;
 	}

片元着色器

由顶点着色器传递来的片元纹理坐标会在光栅化过程中被内插,片元着色器接受到的是内插后的纹理坐标。sampler2D 是一种专门用于接受纹理对象的类型。接下来,需要根据纹理坐标,将纹理图像上每个纹素/像素的颜色,赋值到对应片元上,如下所示:

    const fragment = `
	  precision highp float;
      uniform sampler2D uSample;
      varying  vec2 vTextCoord;
		void main(){
			gl_FragColor =texture2D(uSample, vTextCoord);
	  }
		`

这里用到的函数是texture2D(),它是glsl的内置函数:

gl.texture2D(sampler2D sampler,vec2 coord): 将sampler的纹理上,获取坐标为coord的纹理像素颜色

  • sampler: 指定的纹理单元编号
  • coord: 指定的纹理坐标

完整示例

至此,以及完成了纹理贴图的所有步骤,效果的完整代码如下。由于canvas读取本地图片数据会受到浏览器跨域限制,鉴于我使用的是vscode编辑器,可以从左侧扩展搜索并下载一个Liver Server插件,将所有资源发布在本地服务下,避免了跨域问题。
在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>webgl</title>
  <script src="./lib.js"></script>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    canvas {
      margin: 50px 30px;
      width: 500px;
      height: 500px;
      background-color: antiquewhite;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script>
    /** @type {HTMLCanvasElement} */
    //------------------------------------------------------创建画布
    // 获取canvas元素对象
    let canvas = document.getElementById('canvas');
    // 获取webgl绘图上下文
    const gl = canvas.getContext('webgl');
    if (!gl) {
      throw new Error('WebGL not supported');
    }
    canvas.width = 500;
    canvas.height = 500;
    gl.viewport(0, 0, canvas.width, canvas.height)

    const vertex = `
			attribute vec4 aPosition;
      attribute vec2 aTextCoord;
      varying  vec2 vTextCoord;
			void main() {
				gl_Position =  aPosition;
        vTextCoord = aTextCoord;
			}
		`
    const fragment = `
			precision highp float;

      uniform sampler2D uSample;
      varying  vec2 vTextCoord;
			void main(){
				gl_FragColor =texture2D(uSample, vTextCoord);
			}
		`
    // 创建program
    const program = initShader(gl, vertex, fragment)
    // 获取attribute变量的数据存储位置
    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const aTextCoord = gl.getAttribLocation(program, 'aTextCoord');
    const uSample = gl.getUniformLocation(program, 'uSample');
    // 创建缓冲区对象
    const buffer = gl.createBuffer();
    // 绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    // 传入的数据
    const vertices = new Float32Array([
      -0.5, 0.5,    0.0, 1.0,  // 齐次坐标x, 齐次坐标y, uv坐标u, uv坐标v
      -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 BYTES = vertices.BYTES_PER_ELEMENT;
    // 开辟空间并写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

    // 缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0)
    // 开启attribue缓冲区变量
    gl.enableVertexAttribArray(aPosition)

    gl.vertexAttribPointer(aTextCoord, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2)
    gl.enableVertexAttribArray(aTextCoord)

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

      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 反转Y轴
      gl.activeTexture(gl.TEXTURE0) // 激活纹理单元
      gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象

      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // 放大处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 缩小处理方式
      gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // 水平平铺方式
      gl.texParameterf(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(uSample, 0) // 纹理单元传递给着色器

      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
    }
    img.src = "./img/sky.jpg"
  </script>
</body>
</html>

使用多张纹理

着色器接受两个纹理单元

在着色器中接受两张纹理,将它们相乘后赋给片元即可。矢量的加减乘除运算就是对应位置上的运算

    const fragment = `
	  precision highp float;
      uniform sampler2D uSample1;
      uniform sampler2D uSample2;
      varying  vec2 vTextCoord;
	  void main(){
        vec4 imgColor1 = texture2D(uSample1, vTextCoord);
        vec4 imgColor2 = texture2D(uSample2, vTextCoord);
				gl_FragColor = imgColor1 * imgColor2;
			}
		`

封装纹理配置赋值函数

由于创建两个纹理单元的流程都是一样的,只有一些参数不同,因此可以把他们封装为公共的方法:

function addImage(url, textureIndex, location) {
      return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = () => {     //onload()页面加载(文本和图片)完毕的时候
          const texture = gl.createTexture() // 创建纹理对象

          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 反转Y轴
          gl.activeTexture(gl[`TEXTURE${textureIndex}`]) // 激活纹理单元
          gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象

          gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // 放大处理方式
          gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 缩小处理方式
          gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // 水平平铺方式
          gl.texParameterf(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(location, textureIndex) // 纹理单元传递给着色器

          resolve(img)
        }
        img.onerror = () => {
          const err = new Error(`图片加载失败${url}`)
          reject(err)
        }
        img.src = url
      })
    }

完整示例

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>webgl</title>
  <script src="./lib.js"></script>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    canvas {
      margin: 50px 30px;
      width: 500px;
      height: 500px;
      background-color: antiquewhite;
    }
  </style>
</head>

<body>
  <canvas id="canvas"></canvas>
  <script>

    /** @type {HTMLCanvasElement} */
    //------------------------------------------------------创建画布
    // 获取canvas元素对象
    let canvas = document.getElementById('canvas');

    // 获取webgl绘图上下文
    const gl = canvas.getContext('webgl');
    if (!gl) {
      throw new Error('WebGL not supported');
    }

    canvas.width = 500;
    canvas.height = 500;
    gl.viewport(0, 0, canvas.width, canvas.height)

    const vertex = `
			attribute vec4 aPosition;
      attribute vec2 aTextCoord;
      varying  vec2 vTextCoord;
			void main() {
				gl_Position =  aPosition;
        vTextCoord = aTextCoord;
			}
		`
    const fragment = `
			precision highp float;

      uniform sampler2D uSample1;
      uniform sampler2D uSample2;
      varying  vec2 vTextCoord;
			void main(){
        vec4 imgColor1 = texture2D(uSample1, vTextCoord);
        vec4 imgColor2 = texture2D(uSample2, vTextCoord);
				gl_FragColor = imgColor1 * imgColor2;
			}
		`

    // 创建program
    const program = initShader(gl, vertex, fragment)
    // 获取attribute变量的数据存储位置
    const aPosition = gl.getAttribLocation(program, 'aPosition');
    const aTextCoord = gl.getAttribLocation(program, 'aTextCoord');
    const uSample1 = gl.getUniformLocation(program, 'uSample1');
    const uSample2 = gl.getUniformLocation(program, 'uSample2');
    // 创建缓冲区对象
    const buffer = gl.createBuffer();
    // 绑定缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    // 传入的数据
    const vertices = new Float32Array([
      -0.5, 0.5, 0.0, 1.0,  // 齐次坐标x, 齐次坐标y, uv坐标u, uv坐标v
      -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 BYTES = vertices.BYTES_PER_ELEMENT;

    // 开辟空间并写入数据
    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)

    // 缓冲区对象分配给attribute变量
    gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false, BYTES * 4, 0)
    // 开启attribue缓冲区变量
    gl.enableVertexAttribArray(aPosition)

    gl.vertexAttribPointer(aTextCoord, 2, gl.FLOAT, false, BYTES * 4, BYTES * 2)
    gl.enableVertexAttribArray(aTextCoord)

    function addImage(url, textureIndex, location) {
      return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = () => {     //onload()页面加载(文本和图片)完毕的时候
          const texture = gl.createTexture() // 创建纹理对象

          gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) // 反转Y轴
          gl.activeTexture(gl[`TEXTURE${textureIndex}`]) // 激活纹理单元
          gl.bindTexture(gl.TEXTURE_2D, texture) // 绑定纹理对象

          gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) // 放大处理方式
          gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) // 缩小处理方式
          gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) // 水平平铺方式
          gl.texParameterf(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(location, textureIndex) // 纹理单元传递给着色器

          resolve(img)
        }
        img.onerror = () => {
          const err = new Error(`图片加载失败${url}`)
          reject(err)
        }
        img.src = url
      })
    }

    Promise.all([addImage('./img/sky.jpg', 0, uSample1), addImage('./img/orange.jpg', 1, uSample2)])
    .then(() => {
      gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4)
    })

  </script>
</body>
</html>

总结

以上为webgl中贴图机制详细介绍,为了加深对贴图机制的理解,应该在多实验相关方法的不同参数,本文就不一一列举。

  • 纹理图片大小规范
  • 纹理坐标系统
  • 贴图流程
  • 使用多张纹理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/177101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML+CSS+JS制作炫酷【烟花特效】

文章目录制作炫酷烟花特效一、普通烟花(分散形)HTML代码CSS代码JS代码二、圆形烟花HTML代码CSS代码JS代码三、爱心形烟花HTML代码CSS代码JS代码四、源码获取在线下载制作炫酷烟花特效 &#x1f4a1;本篇内容使用htmlcssjs制作鼠标点击出现烟花效果&#xff0c;分别介绍了分散型…

python-测试代码

1. 测试函数get_name.pydef combination(first, last):将姓名组合在一起name first lastreturn name.title()hello_world.pyfrom get_name import combinationprint("Enter q to quit!") while True:first input(Please input your first name: )if first q:b…

理光Aficio MP C2500扫描到文件夹设置方法

首先在需要接收扫描文件的电脑上设置共享文件夹。 注&#xff1a; &#xff08;1&#xff09;文件夹的名字最好简单一点&#xff0c;比如&#xff1a;scan、123等等&#xff1b; &#xff08;2&#xff09;文件夹的共享权限最好能设置为最大&#xff08;WindowsXP、Windows200…

Future、CompletableFuture概述

1.同步和异步 &#xff08;1&#xff09;同步&#xff1a;需要等待结果返回&#xff0c;才能继续运行 &#xff08;2&#xff09;异步&#xff1a;不需要等待结果返回&#xff0c;就能继续运行 &#xff08;3&#xff09;异步设计&#xff1a;多线程可以让方法执行变为异步(比…

第四章必备前端基础知识-第二节3:CSS盒模型和浮动

文章目录一&#xff1a;盒模型&#xff08;1&#xff09;border&#xff08;2&#xff09;padding&#xff08;3&#xff09;margin二&#xff1a;flex布局一&#xff1a;盒模型 盒模型&#xff1a;在HTML中&#xff0c;每个标签&#xff08;或元素&#xff09;相当于是一个盒…

Mybatis和Jpa

这里写目录标题1.Mybatis1.1 JDBC的缺点1.2 Mybatis的整体架构1.3 入门案例1.3.1 问题:无法连接到数据库服务器1.4 动态代理实现Mapper1.5 mybatis-config.xml配置1.5.1 properties属性读取外部资源1.5.2 settings设置1.5.3 typeAliases1.5.4 typeHandlers&#xff08;类型处理…

【Substance Designer】基础操作和节点学习记录

写在前面 这个记录稍微有点杂&#xff0c;大概是庄懂的技术美术入门课(美术向)-直播录屏-第20课和一些基础操作的记录合集吧&#xff01; 补充 学习发现&#xff0c;基础的节点是需要学习和记录的&#xff0c;但是真正用起来还是要多用多练&#xff01;所以这种简单的记录节点…

YOLOv5/v7 引入 RepVGG 重参数化模块

本篇博文代码出自YOLOv5-lite &#xff0c;YOLOv5-lite的作者在CSDN的账号是 pogg_ &#xff0c;大家可以关注一下&#xff0c;这也是一位在开源项目上做了很多工作的博主。 RepVGG的原理和融合推导过程可以看我的这篇博文&#xff1a;RepVGG&#xff1a;让VGG风格的ConvNets再…

机制设计原理与应用(三)Screening

文章目录3 Screening3.1 为单个不可分割的项目定价3.1.1 对θ\thetaθ的假设3.1.2 问题描述3.1.3 特性3.2 为无限可分的项目定价3.2.1 对θ\thetaθ的假设3.2.3 特性3.2.4 收益最大化3.2.5 最优解决方案3 Screening Screening theory&#xff1a;机制设计理论可以被看作是其多…

Cadence PCB仿真使用Allegro PCB SI生成振铃ringing仿真报告及报告导读图文教程

🏡《Cadence 开发合集目录》   🏡《Cadence PCB 仿真宝典目录》 目录 1,概述2,生成报告3,报告导读4,总结1,概述 本文简单介绍使用Allegro PCB SI生成网络的振铃性能评估的报告的方法,及振铃ringing报告要点导读。 2,生成报告 第1步,选择需要生成报告的网络,然后…

第二章 ArcGIS数据和地理数据库

文章目录第一节 ArcGIS和4D数据基本知识1 4D数据介绍1.1 DLG1.2 DEM1.3 DOM1.4 DRG1.5 4D表现2 ArcGIS的数据和4D数据对应3 栅格数据3.1 查看帮助3.2 空间分辨率3.3 分辨率与比例尺换算3.4 栅格数据介绍——cellsize3.5 栅格数据波段3.6 栅格格式4 栅格数据改变分辨率5 转换栅格…

【 uniapp - 黑马优购 | 登录与支付(2)】如何实现三秒后跳转和微信支付

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大二在校生&#xff0c;讨厌编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;小新爱学习. &#x1f43c;个人WeChat&#xff1a;见文末 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;…

Ubuntu20.04+MAVROS+PX4+Gazebo安装教程

Ubuntu20.04MAVROSPX4Gazebo安装PX4步骤安装MAVROS安装QGCPX4仿真安装PX4步骤 从github上clone源码 git clone https://github.com/PX4/PX4-Autopilot.git --recursive进入PX4-Autopilot文件夹&#xff0c;继续下载未下载完的组件 cd PX4-Autopilot/ git submodule update -…

flowable使用 act_hi_xxx

HistoryService 流程历史信息 act_hi_procinst : 历史流程信息&#xff0c;&#xff0c;如果流程执行完了&#xff0c;end_time_ 和 duration不为null // 没有执行完的List<HistoricProcessInstance> list historyService.createHistoricProcessInstanceQuery().unfi…

uniapp封装并全局挂载request请求

前言 日常开发中,前端项目中需要调用服务端api完成页面渲染,uniapp提供的请求api:uni.request相对繁琐;另外服务端提供的不同api仅子路径不同,api域名以及根路径都是相同的,一旦接口api变更,需要更改地方就会很多.鉴于以上可以将uni.request进行封装,简化开发. 目前uniapp项…

MySQL(四):B+树索引、聚簇索引、二级索引、联合索引

目录一、B树索引1.1 在没有索引时进行查找记录1.2 索引方案1.3 InnoDB中的索引方案二、聚簇索引三、二级索引四、联合索引五、InnoDB中B树索引的注意事项5.1 根页面的位置不会改变5.2 内节点中目录项记录的唯一性5.3 一个页面至少容纳两条记录一、B树索引 数据库中的用来存储数…

MySQL进阶篇之索引1

02、索引 2.1、索引概述 1、介绍 索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种方式引用&#xff08;指向&#…

Cepstral Analysis 倒谱分析

源过滤器分离 倒谱分析是另一种将声道滤波器响应与激励分开的方法&#xff08;如线性预测&#xff09; 它基于以下观察&#xff1a;语音信号的频谱是激励频谱和声道频率响应的乘积 可以使用log将乘法转换为加法&#xff0c;因此&#xff0c;“对数频谱”可以看作是对数激励频…

十七、Gtk4-Menu and action

Menu 用户经常使用菜单向计算机发出命令。它是这样的: 现在让我们分析一下上面的菜单。对象有两种类型。 “File”, “Edit”, “View”, “Cut”, “Copy”, “Paste” and “Select All”. 它们被称为“菜单项&#xff08;menu item&#xff09;”或简单地称为“item”。当…

字节青训前端笔记 | 前端调试

在程序员的世界中&#xff0c;BUG 一词相信同学们再熟悉不过了&#xff0c;本节课将围绕前端开发中所遇见的 BUG 出发&#xff0c;讲解作为一名合格的前端开发人员&#xff0c;你应该掌握哪些开发调试知识 Chorme DevTools Chorme DevTools 是 chorme内核为大家提供的高效的前…