WebGL编程指南 - 颜色与纹理

news2024/10/23 22:12:29
  • 将顶点的其他(非坐标)数据——如颜色等——传入顶点着色器。

  • 发生在顶点着色器和片元着色器之间的从图形到片元的转化,又称为图元光栅化 (rasterzation process)。

  • 将图像(或称纹理)映射到图形或三维对象的表面上。

创建多个缓冲区将非坐标数据传入顶点着色器

// MultiAttributeSize.js
// 顶点着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute float a_PointSize;\n' +
  'void main(){\n' +
  ' gl_Position = a_Position;\n' +
  ' gl_PointSize = a_PointSize;\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'void main(){\n' + ' gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n' + '}\n'
// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取上下文
  let gl = getWebGLContext(canvas)
  if (!gl) {
    console.log('Failed to get rendering context for WebGL')
    return
  }
  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to initialize shaders')
    return
  }
  // 设置顶点信息
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the position of the vertices')
    return
  }
  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  // 清空绘图区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制
  gl.drawArrays(gl.POINTS, 0, n)
}

function initVertexBuffers(gl) {
  // 数据准备
  let vertices = new Float32Array([
    0.0,
    0.5,
    -0.5,
    -0.5,
    0.5,
    -0.5, // 点的位置
  ])
  let n = 3 // 点的数量
  let sizes = new Float32Array([
    10.0,
    20.0,
    30.0, // 点的尺寸
  ])
  // 创建缓冲区
  let vertexBuffer = gl.createBuffer()
  let sizeBuffer = gl.createBuffer()
  if (!vertexBuffer || !sizeBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }

  // 将顶点坐标写入缓冲区对象并开启
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer) // 绑定缓冲区
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) // 向缓冲区存入数据
  // 将缓冲区分配给attribute变量
  let a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position')
    return -1
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  // 开启attribute变量(开启缓冲区)
  gl.enableVertexAttribArray(a_Position)

  // 将顶点尺寸写入缓冲区对象并开启
  gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW)
  let a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
  if (a_PointSize < 0) {
    console.log('Failed to get the storage location of a_PointSize')
    return -1
  }
  gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0)
  gl.enableVertexAttribArray(a_PointSize)

  return n
}

代码中都是以往应用的功能,此处我们在原绘制多个点的示例基础上,做了如下改变:

  • 新增了attribute变量a_PointSize,该变量为Float类型,负责接收JavaScript程序传入的顶点尺寸数据并被赋值给gl_PointSize变量,
  • 在initVertexBuffers()函数中也增加了a_PointSize相关内容。

当执行 gl.drawArrays() 函数时,存储在缓冲区对象中的数据将按照其在缓冲区中的顺序依次传给对应的 attribute 变量

一个缓冲区对象传输多类型信息——步进和偏移参数与交错组织

相关内容:通过gl.vertexAttribPointer()函数的stride(步进)参数和offset(偏移)参数,在一个缓冲区对象中存储并读取多类型数据,示例中一个缓冲区对象包含有顶点位置和大小两类数据。
相关函数:gl.vertexAttribPointer(), TypedArray.BYTES_PER_ELEMENT

使用多个缓冲区对象向着色器传递多种数据,比较适合数据量不大的情况。当程序中的复杂三维图形具有成千上万个顶点时,维护所有的顶点数据是很困难的。然而,WebGL允许我们把顶点的坐标和尺寸数据打包到同一个缓冲区对象中(交错组织interleaving),并通过步进和偏移有差别地访问缓冲区对象中不同种类的数据,即使用gl.vertexAttribPointer()函数的第5个参数stride和第6个参数offset。

// MultiAttributeSize_Interleaved.js
// 顶点着色器程序
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute float a_PointSize;\n' +
  'void main(){\n' +
  ' gl_Position = a_Position;\n' +
  ' gl_PointSize = a_PointSize;\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'void main(){\n' + ' gl_FragColor = vec4(1.0,0.0,0.0,1.0);\n' + '}\n'
// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取上下文
  let gl = getWebGLContext(canvas)
  if (!gl) {
    console.log('Failed to get rendering context for WebGL')
    return
  }
  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to initialize shaders')
    return
  }
  // 设置顶点信息
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the position of the vertices')
    return
  }
  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  // 清空绘图区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制
  gl.drawArrays(gl.POINTS, 0, n)
}

function initVertexBuffers(gl) {
  // 数据准备
  let verticesSizes = new Float32Array([
    // 顶点坐标和点的尺寸
    0.0,    0.5,    10.0, // 第一个点
    -0.5,    -0.5,    20.0, // 第二个点
    0.5,    -0.5,    30.0, // 第三个点
  ])
  let n = 3 // 点的数量
  // 创建缓冲区
  let vertexSizeBuffer = gl.createBuffer()
  if (!vertexSizeBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }
  // 绑定缓冲区
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexSizeBuffer)
  // 向缓冲区存入数据
  gl.bufferData(gl.ARRAY_BUFFER, verticesSizes, gl.STATIC_DRAW)

  // 获取单个数据的大小
  let FSIZE = verticesSizes.BYTES_PER_ELEMENT

  // 获取a_Position的存储位置
  let a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position')
    return -1
  }
  // 将缓冲区分配给a_Position
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0)
  // 开启attribute变量(开启缓冲区)
  gl.enableVertexAttribArray(a_Position)

  // 获取a_PointSize的存储位置
  let a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
  if (a_PointSize < 0) {
    console.log('Failed to get the storage location of a_PointSize')
    return -1
  }
  // 将缓冲区分配给a_PointSize
  gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2)
  // 开启attribute变量(开启缓冲区)
  gl.enableVertexAttribArray(a_PointSize)

  return n
}

注意第五个参数是 字节 数,所以需要 verticesSizes.BYTES_PRE_ELEMENT 获取单个元素的字节长度

  • 每个attribute变量都要先获取位置,再分配缓冲区,最后开启变量,两个变量分配缓冲区的方式不同。
  // 将缓冲区分配给a_Position
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0)
  // 将缓冲区分配给a_PointSize
  gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2)

改变颜色(初识varying变量)——为顶点单独设置颜色

相关内容:通过varying变量从顶点着色器向片元着色器传值
相关函数:varying

小结:1. varying变量的作用:从顶点着色器向片元着色器传输数据;2. varying变量可以赋值的数据类型:float及相关数据类型;3. varying变量的行为(如何传值):末尾有图展示——在WebGL中,如果顶点着色器与片元着色器中有类型和命名都相同的varying变量,那么顶点着色器赋给该变量的值会自动传入片元着色器。

// MultiAttributeColor.js
// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_Position = a_Position;\n' +
  ' gl_PointSize = 10.0;\n' +
  ' v_Color = a_Color;\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_FragColor = v_Color;\n' +
  '}\n'
// 主程序
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取上下文
  let gl = getWebGLContext(canvas)
  if (!gl) {
    console.log('Failed to get rendering context for WebGL')
    return
  }
  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to initialize shaders')
    return
  }
  // 获取顶点位置
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the position of the vertices')
    return
  }
  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  // 清空绘图区
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制
  gl.drawArrays(gl.POINTS, 0, n)
}
// 顶点位置相关函数
function initVertexBuffers(gl) {
  // 数据准备
  let verticesColors = new Float32Array([
    // 顶点坐标和颜色
    0.0, 0.5, 1.0, 0.0, 0.0, 
    -0.5, -0.5, 0.0, 1.0, 0.0, 
    0.5, -0.5, 0.0, 0.0, 1.0,
  ])
  let n = 3
  // 创建缓冲区对象
  let vertexColorBuffer = gl.createBuffer()
  if (!vertexColorBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }
  // 向缓冲区存储数据
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer)
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)

  let FSIZE = verticesColors.BYTES_PER_ELEMENT
  // 获取a_Position的存储位置,分配缓冲区并开启
  let a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position')
    return -1
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 5, 0)
  gl.enableVertexAttribArray(a_Position) // Enable buffer assignment
  // 获取a_Color的存储位置,分配缓冲区并开启
  let a_Color = gl.getAttribLocation(gl.program, 'a_Color')
  if (a_Color < 0) {
    console.log('Failed to get the storage location of a_Color')
    return -1
  }
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 5, FSIZE * 2)
  gl.enableVertexAttribArray(a_Color) // 开启缓冲区分配

  return n
}

着色器部分:

// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec4 a_Color;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_Position = a_Position;\n' +
  ' gl_PointSize = 10.0;\n' +
  ' v_Color = a_Color;\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'precision mediump float;\n' +
  'varying vec4 v_Color;\n' +
  'void main(){\n' +
  ' gl_FragColor = v_Color;\n' +
  '}\n'

顶点着色器中声明了attribute变量a_Color用以接收颜色数据,同时在两个着色器中都声明了varying变量v_Color变量,该变量将值从顶点着色器传递到片元着色器,最终赋值给gl_FragColor(在WebGL中,如果顶点着色器与片元着色器中又类型和命名都相同的varying变量,那么顶点着色器赋给该变量的值会自动传入片元着色器)。

varying变量只能是float(以及相关的vec2,vec3,vec4,mat2,mat3,mat4)类型。

片元着色器在声明新的变量的时候,需要在前面加上精度限定语句,此处为precision mediump float,这一语法在后面GLSL ES语言的介绍中会进行说明。

ColoredTriangle.js与几何图形装配、光栅化、执行片元着色器(varying变量的varying过程-内插)

相关内容:着色器工作细节:顶点着色器-几何图像装配-光栅化-片元着色器;varying变量的特性—传值和内插
相关函数:varying变量;片元着色器内置对象gl_FragCoord

小结:

1. 顶点着色器在提供顶点坐标之后,会进入几何图像装配流程,根据gl.drawArrays()中的参数选择配装方式;

2. 配装之后,光栅化将配装后的图像转化为片元,每个片元中保存有窗口坐标系统坐标等信息(目前已知);

3. 片元着色器接收光栅化后的图像,为每个片元确定颜色,绘制到颜色缓冲区中;

4. 通过gl_FragCoord,我们可以获得片元在canvas坐标系统(窗口坐标系统)下的坐标,据此对gl_FragColor进行操作,可以根据坐标控制片元的颜色

  // 绘制
  gl.drawArrays(gl.TRIANGLES, 0, n)

  将上一个例子中的 函数参数改一下就绘制了一个彩色的三角形

为了理解这种效果的产生方式,我们需要厘清顶点着色器和片元着色器之间的数据传输细节

几何形状的装配和光栅化:

一个普通的绘制红色三角形的代码:

// HelloTriangle.js
// 顶点着色器
var VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'void main(){\n' +
  '   gl_Position = a_Position;\n' +
  '}\n'
// 片元着色器
var FSHADER_SOURCE =
  'void main() {\n' + ' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + '}\n'
// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取WebGL上下文
  let gl = getWebGLContext(canvas)
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL')
    return
  }
  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to initialize shaders')
    return
  }
  // 设置顶点位置
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the positions of the vertices')
    return
  }
  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)
  // 清空canvas
  gl.clear(gl.COLOR_BUFFER_BIT)
  // 绘制
  gl.drawArrays(gl.TRIANGLES, 0, n)
}

function initVertexBuffers(gl) {
  let vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5])
  let n = 3 // 点的个数

  // 创建缓冲区对象
  let vertexBuffer = gl.createBuffer()
  if (!vertexBuffer) {
    console.log('Failed to create the buffer object')
    return -1
  }
  // 将缓冲区对象绑定到目标
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
  // 向缓冲区对象中写入数据
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
  // 将缓冲区对象分配给a_Position变量
  let a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position')
    return -1
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0)
  // 连接a_Position变量与分配给它的缓冲区对象
  gl.enableVertexAttribArray(a_Position)

  return n
}

程序向 gl_Position 给出了三个顶点的坐标,谁来确定这三个点就是三角形的三个顶点?最终,为了填充三角形内部,谁来确定哪些像素需要被着色?谁来负责调用片元着色器,片元着色器又是怎样处理每个片元的?

如上图所示,顶点着色器和片元着色器之间有以下两个步骤:

  • 图元装配过程:将孤立的顶点坐标装配成几何图形,几何图形的类别由 gl.drawArrays() 第一个参数决定。
  • 光栅化过程:将装配好的几何图形转化为片元。(于是就可以进行逐片元操作了)

在上述代码示例中,顶点着色器会被执行三次,之后进行装配和光栅化,最后执行片元着色器,流程如下:

  • 执行顶点着色器,缓冲区对象中第一个最表(0.0,0.5)传递给 attribute 变量 a_Position (自动补全为(0.0,0.5,0.0,1.0)),最后赋值给 gl_Position ,他就进入了图形装配区域并暂时储存在那里
  • 执行顶点着色器,传入第二个坐标(-0.5,-0.5,0.0,1.0)
  • 执行顶点着色器,传入第三个坐标(0.5,-0.5,0.0,1.0)
  • 开始装配图形。使用传入的点的坐标,根据 gl.drawArrays() 第一个参数决定如何装配
  • 将图形转化为片元,即光栅化(rasterization),光栅化之后,我们就得到了组成这个三角形的所有片元,片元的数目实际上是三角形最终在屏幕上所覆盖的像素数

调用片元着色器:

光栅化之后,就可以逐片元调用片元着色器。

假如图形只有如下图所示实心正方形所示的10个片元,那么片元着色器会调用10次,每调用一次处理一个片元。对于每个片元(本身带有坐标信息),片元着色器计算出该片元的颜色,写入颜色缓冲区。

全部处理完毕后,浏览器会显示最终的结果。

对片元着色器内部操作:

片元本身带有坐标信息,调用片元着色器时,这些坐标信息也随着片元传了过去,我们可以通过片元着色器内置变量来访问该坐标信息:

varing 变量的作用和内插过程

内插之后,每个片元都根据三个顶点的v_Color变量得到了自己的v_Color变量,该变量和坐标信息(可能还有其他信息)一起保存在片元中。进入片元着色器后,新声明的varying变量v_Color接收原varying变量v_Color,然后赋值给gl_FragColor,最后会知道颜色缓冲区中。

所谓的内插过程(interpolation process)如下所示:

在矩形表面贴上图像

相关内容:WebGL中纹理映射的流程
相关函数:gl.createTexture(), gl.deleteTexture(), gl.pixelStorei(), gl.activeTexture(), gl.bindTexture(), gl.texParameteri(), gl.texImage2D(), gl.uniform1i(), texture2D()

小结:1.纹理映射主体流程:着色器设置-顶点坐标和对应纹理坐标设置-加载图像,加载完成后配置纹理;2. 纹理流程细节:创建纹理对象-获取变量存储地址-加载图像,加载完成后:纹理对象预处理(Y轴反转)-开启纹理单元-绑定并开启纹理对象-配置纹理参数-配置纹理图像-根据纹理单元传输纹理;3. 纹理相关扩展:非2的n次幂图像的呈现问题。

除了通过给定数值为片元着色,在三维图形学中,有一项重要的技术可以帮助我们使用现有的图像为图形着色,被称为纹理映射(texture mapping)。

纹理映射的作用,就是根据纹理图像,为之前光栅化后的每个片元涂上合适的颜色。通俗说法,就是将一张图像(就像一张贴纸)映射(贴)到一个几何图形的表面上去。该图片又可以称为纹理图像(texture image)或纹理(texture),组成纹理图像的像素又被称为纹素(texels,texture elements),每一个纹素的颜色都是用RGB(jpg)或RGBA格式(png)编码。

在 WebGL中,要进行纹理映射,需要四步:

  • 准备好映射到几何图形上的纹理图像
  • 为几何图形配置纹理映射方式
  • 加载纹理图像,对其进行一些配置,以在 WebGL 中使用它
  • 在片元着色器中将相应的纹素从纹理中抽取出来,并将纹素的颜色赋给片元

确定“几何图形的某个片元”的颜色如何取决于“纹理图像中哪个(或哪几个)像素”的问题(即前者到后者的映射)。此处利用图形的顶点坐标来确定屏幕上哪部分被纹理图像覆盖,使用纹理坐标(texture coordinates)确定纹理图像的哪部分将覆盖到几何图形上。

纹理坐标:

纹理坐标就是纹理图像上的坐标,通过纹理坐标可以在纹理图像上获取纹理颜色。WebGL 系统中的纹理坐标系统是二维的。为了与 xy坐标分开,使用 st 坐标系统

因为坐标值与图像自身的尺寸无关,不管是 128 * 128 还是 256 * 256 ,其右上角的纹理坐标始终是 (1.0,1.0)

将纹理图像粘贴到几何图形上

示例程序:

  • 首先在顶点着色器中为每个顶点指定纹理坐标,
  • 然后在片元着色器中根据每个片元的纹理坐标从纹理图像中抽取纹素颜色
// 顶点着色器
let VSHADER_SOURCE =
  'attribute vec4 a_Position;\n' +
  'attribute vec2 a_TexCoord;\n' +
  'varying vec2 v_TexCoord;\n' +
  'void main(){\n' +
  ' gl_Position = a_Position;\n' +
  ' v_TexCoord = a_TexCoord;\n' +
  '}\n'
// 片元着色器
let FSHADER_SOURCE =
  'precision mediump float;\n' +
  'uniform sampler2D u_Sampler;\n' +
  'varying vec2 v_TexCoord;\n' +
  'void main(){\n' +
  ' gl_FragColor = texture2D(u_Sampler,v_TexCoord);\n' +
  '}\n'
// 主函数
function main() {
  // 获取canvas元素
  let canvas = document.getElementById('webgl')
  // 获取上下文
  let gl = getWebGLContext(canvas)
  if (!gl) {
    console.log('Failed to get the rendering context for WebGL')
    return
  }
  // 初始化着色器
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
    console.log('Failed to initialize shaders')
    return
  }
  // 设置顶点坐标
  let n = initVertexBuffers(gl)
  if (n < 0) {
    console.log('Failed to set the positions of the vertices')
    return
  }
  // 设置背景色
  gl.clearColor(0.0, 0.0, 0.0, 1.0)

  // 配置纹理信息
  if (!initTextures(gl, n)) {
    console.log('Failed to configure the texture')
    return
  }
}
// 顶点坐标
function initVertexBuffers(gl) {
  // 数据准备
  let verticesTexCoords = 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,
  ])
  let n = 4
  // 创建缓冲区
  let vertexTexCoordBuffer = gl.createBuffer()
  if (!vertexTexCoordBuffer) {
    console.log('Failed to create vertexTexCoordBuffer')
    return -1
  }
  // 绑定缓冲区
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexTexCoordBuffer)
  // 缓冲区存储数据
  gl.bufferData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW)

  // 顶点坐标相关配置
  let FSIZE = verticesTexCoords.BYTES_PER_ELEMENT
  let a_Position = gl.getAttribLocation(gl.program, 'a_Position')
  if (a_Position < 0) {
    console.log('Failed to get the storage location of a_Position')
    return -1
  }
  gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0)
  gl.enableVertexAttribArray(a_Position)

  // 纹理坐标相关配置
  let a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord')
  if (a_TexCoord < 0) {
    console.log('Failed to get the storage location of a_TexCoord')
    return -1
  }
  gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2)
  gl.enableVertexAttribArray(a_TexCoord)

  // 一切顺利,返回顶点数量
  return n
}
// 初始化纹理
function initTextures(gl, n) {
  // 创建纹理对象
  let texture = gl.createTexture()
  if (!texture) {
    console.log('Failed to create texture')
    return
  }
  // 获取u_Sampler(纹理图像)的存储位置
  let u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler')
  if (!u_Sampler) {
    console.log('Failed to get the storage loaction of u_Sampler')
    return
  }
  // 创建一个image对象
  let image = new Image()
  // 注册图像加载事件的响应函数
  image.onload = function () {
    loadTexture(gl, n, texture, u_Sampler, image)
  }
  // 浏览器加载图像
  image.src = './funaimu.jpg'

  return true
}

function loadTexture(gl, n, texture, u_Sampler, image) {
  // 对纹理图像进行Y轴反转
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1)
  // 开启0号纹理单元
  gl.activeTexture(gl.TEXTURE0)
  // 向target绑定纹理对象
  gl.bindTexture(gl.TEXTURE_2D, texture)

  // 配置纹理参数
  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, image)

  // 将0号纹理传递给着色器
  gl.uniform1i(u_Sampler, 0)

  // 绘制矩形
  gl.clear(gl.COLOR_BUFFER_BIT)
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)
}

可能的问题:

  • 1,浏览器访问不了本地文件,这个需要设置一下
  • 图片的尺寸不是 2 的倍数,代码报错:WebGL warning: drawArraysInstanced: TEXTURE_2D at unit 0 is incomplete: Non-power-of-two textures must have a wrap mode of 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);

使 webgl设置图形纹理的时候,在设置水平和垂直如何填充的时候,设置成水平和垂直拉伸

纹理相关操作流程可分为以下五个部分:

  • 顶点着色器中接收顶点的纹理坐标,光栅化后传递给片元着色器(GL SL ES) 
  • 片元着色器根据片元的纹理坐标,从纹理图像中抽取出纹素颜色,赋给当前片元(GL SL ES)
  • 设置顶点的纹理坐标(initVertexBuffers())
  • 准备待加载的纹理图像,令浏览器读取它(initTextures()) 
  • 监听纹理图像的加载事件,一旦加载完成,就在 WebGL 系统中使用纹理(loadTexture())

参考:【《WebGL编程指南》读书笔记-颜色与纹理】_webgl warning: texsubimage: texture has not been i-CSDN博客

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

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

相关文章

C++笔记---哈希表

1. 哈希的概念 哈希(hash)又称散列&#xff0c;是一种组织数据的方式。从译名来看&#xff0c;有散乱排列的意思。 本质就是通过哈希函数把关键字Key跟存储位置建立一个映射关系&#xff0c;查找时通过这个哈希函数计算出Key存储的位置&#xff0c;进行快速查找。 STL中的un…

推荐IDE中实用AI编程插件,目前无限次使用

插件介绍 一款字节跳动推出的“基于豆包大模型的智能开发工具” 以vscode介绍【pycharm等都可以啊】&#xff0c;这个插件提供智能补全、智能预测、智能问答等能力&#xff0c;节省开发时间 直接在IDE中使用&#xff0c;就不用在网页中来回切换了 感觉还可以&#xff0c;响应速…

Excel表格如何修改“打开密码”,简单几步,轻松搞定

在保护Excel文件的安全性时&#xff0c;设置打开密码是常见且有效的方式。然而&#xff0c;有时我们需要修改已经设置的打开密码&#xff0c;以确保文件安全性或更新密码信息。今天小编来分享一下修改Excel文件打开密码的方法&#xff0c;操作简单&#xff0c;一起来看看吧&…

设置OpenAI API的环境变量

获取openai API 密钥 https://platform.openai.com/api-keys 设置环境变量 为什么不在代码中直接写入&#xff0c;而是设置环境变量&#xff1f; 安全性&#xff1a;将 API 密钥存储在环境变量中&#xff0c;而不是直接写在代码中&#xff0c;可以降低泄露密钥的风险。易于…

第二期:第15节,beep 大海

首先是 代码的编写&#xff1a; 里面已经有了解释了。 1 /*2 * main.c3 *4 * Created on: 2023-3-85 * Author: pengdan6 */7 #include "exynos_4412.h"89 void delay_ms(unsigned int num)10 {11 int i,j;12 for(inum; i>0;i--)13 …

『完整代码』坐骑召唤

创建一个按钮 作为召唤/消失坐骑的开关 将预制体放入指定文件夹 命名为Mount01 创建脚本并编写&#xff1a;CallMount.cs using UnityEngine; using UnityEngine.UI; public class CallMount : MonoBehaviour{public Button callBtn;GameObject mountPrefab;GameObject mountIn…

[项目详解][boost搜索引擎#1] 概述 | 去标签 | 数据清洗 | scp

目录 一、前言 二、项目的相关背景 三、搜索引擎的宏观原理 四、搜索引擎技术栈和项目环境 五、正排索引 VS 倒排索引--原理 正排索引 分词 倒排索引 六、编写数据去除标签和数据清洗模块 Parser 1.数据准备 parser 编码 1.枚举文件 EnumFile 2.去标签ParseHtml(…

使用Vscode配置ftp连接远程服务器(上传本地文件)

1.安装插件 扩展商店搜sftp,点击进行安装。 2.配置json文件 crtl+shift+p 输入ftp配置命令 sftp:config {"name": "My Server", //设置名字"host": "localhost"</

android app执行shell命令视频课程补充android 10/11适配-千里马android

(https://blog.csdn.net/learnframework/article/details/120103471) https://blog.csdn.net/learnframework/article/details/120103471 hi&#xff0c;有学员在学习跨进程通信专题课程时候&#xff0c;在实战app执行一个shell命令的项目时候&#xff0c;对课程本身的android …

JVM、字节码文件介绍

目录 初识JVM 什么是JVM JVM的三大核心功能 JVM的组成 字节码文件的组成 基础信息 Magic魔数 主副版本号 其它基础信息 常量池 字段 方法 属性 字节码常用工具 javap jclasslib插件 阿里Arthas 初识JVM 什么是JVM JVM的三大核心功能 1. 解释和运行虚拟机指…

js实现点击随机点名效果

代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </he…

uniapp+vue3+uview-plus修改默认样式

最近使用uniappvue3uview-plus开发微信小程序中&#xff0c;使用uview-plus自定义底部导航栏tabbar时&#xff0c;遇到修改默认样式不生效问题 使用传统的 ::v-deep、:deep、::v-deep&#xff0c;或者style标签中去掉scoped也是无效的&#xff0c;有好的方案欢迎交流&#xff…

深入剖析 C 与 C++ 动态内存管理之术

亲爱的读者朋友们&#x1f603;&#xff0c;此文开启知识盛宴与思想碰撞&#x1f389;。 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f970;&#xff0c;共创活力社区。 &#x1f525;&#x1f525;&#x1f525;【C】进阶&#xff1a;类相关…

Java 二分查找算法详解及通用实现模板案例示范

1. 引言 二分查找&#xff08;Binary Search&#xff09;是一种常见的搜索算法&#xff0c;专门用于在有序数组或列表中查找元素的位置。它通过每次将搜索空间缩小一半&#xff0c;从而极大地提高了查找效率。相比于线性查找算法&#xff0c;二分查找的时间复杂度为 O(log n)&…

利用Docker搭建一套Mycat2+MySQL8一主一从、读写分离的最简单集群(保姆教程)

文章目录 1、Mycat介绍1.1、mycat简介1.2、mycat重要概念1.3、Mycat1.x与Mycat2功能对比1.2、主从复制原理 2、前提准备3、集群规划4、安装和配置mysql主从复制4.1、master节点安装mysql8容器4.2、slave节点安装mysql8容器4.2、配置主从复制4.3、测试主从复制配置 5、安装mycat…

【TDA】持续同调的矢量化方法

Topological deep learning: a review of an emerging paradigm 持续同调与深度学习协同下的3D点云分类_苏潇 Applications of Topology to Data Analysis Computational Topology: An Introduction 持续同调对于特征的方式有条形码PB和持续图表PD两种形式,它们包含了持续同调的…

Qt开发技巧(十八):新窗口控件用智能指针,将一些配置类变量封装起来,Qt窗体的Z序叠放,子窗体的释放,Qt中的事件发送,Qt的全局头文件

继续讲一些Qt开发中的技巧操作&#xff1a; 1.新窗口控件用智能指针 通过对Qt自带Examples的源码研究你会发现&#xff0c;越往后的版本&#xff0c;越喜欢用智能指针QScopedPointer来定义对象&#xff0c;这样有个好处就是用的地方只管new就行&#xff0c;一直new下去&#xf…

2025 年最佳的 Retool 开源替代方案

自 2017 年推出以来&#xff0c;Retool 已迅速成为开发者的热门选择。 Retool 的出现&#xff0c;填补了当时企业在快速构建内部工具上的空白。传统的应用开发往往需要耗费大量时间和资源&#xff0c;尤其是对于定制的内部业务应用。而 Retool 提供了一个灵活的平台&#xff0…

element设置时间和日期框早于现在的时间和日期禁用

效果: 今日此时此刻之前的日期、时间禁止选用&#xff0c;切换日期和时间为“2024-10-19 00:00:00"&#xff0c;再切换为”2024-10-18 00:00:00"时&#xff0c; 会给form.time默认赋值为今日此时此刻&#xff08;日期时间少于今日此时此刻则重新赋值&#xff09; 安…

datax连接池泄漏问题排查及解决

1、问题描述 频繁调用datax服务&#xff08;从oracle同步到mysql&#xff09;出现报错&#xff0c;获取不到连接 oracle读取时报错信息 "errorMessage": "Code:[DBUtilErrorCode-10], Description:[连接数据库失败. 请检查您的 账号、密码、数据库名称、IP、…