site infoHacknerd | Tech Blog
blog cover

🕹️ [WebGL编程指南] 5. 颜色与纹理

WebGL

gl.vertextAttribPointer 的步进和偏移参数

WeblGL允许把顶点坐标和尺寸打包到同一个缓冲区对象中。如将“逐顶点”数据(坐标和尺寸)交叉存储在一个数组中。

javascriptCopy
const verticesSizes = new Float32Array([
  // 按顺序分别代表
	// x y 顶点大小
	0.0,  0.5,  10.0,
	-0,5, -0.5, 20.0,
	0.5,  -0.5, 30.0
])

然后使用gl.vertextAttribPointer 的第五个参数stride 和第6个offset参数,有差别的从缓冲区获取特定数据。

javascriptCopy
// ...
const verticesSizes = new Float32Array([
  // 按顺序分别代表
	// x y 顶点大小
	0.0,  0.5,  10.0,
	-0,5, -0.5, 20.0,
	0.5,  -0.5, 30.0
])


const vertexSizeBuffer = gl.createBuffer()

gl.binBuffer(gl.ARRAY_BUFFER, vertexSizeBuffer)
gl.bufferData(gl.ARRAY_BUFFER, verticesSizes, gl.STATIC_DRAW)

const FSIZE = verticesSizes.BYTES_PER_ELEMENT;

const a_Position = gl.getAttribLocation(gl.program, 'a_Position')

// ...

gl.vertextAttribPointer(a_Pointer, 2, gl.FLOAT, false, FSIZE * 3, 0)
// 开启分配
gl.enableVertexAttribArray(a_Pointer)

// ...

const a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize')
// ...

gl.vertextAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2)
// 开启分配
gl.enableVertexAttribArray(a_PointSize)

  • Typed Array 有BYTES_PER_ELEMENT 属性,可以获取数组中每个元素所占用的字节数。
  • stride表示单个顶点的所有数据的字节数,也就是相邻两个顶点间的数据,即步进参数。
  • offset表示当前数据项距离首个元素的距离,即便宜参数
  • image

    传递颜色

    javascriptCopy
    // 顶点着色器
    const VSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_Color;\n" +
      "void main() {\n" +
      "  gl_Position = a_Position;\n" + // 设置坐标
      "  gl_PointSize = 10.0;\n" + // 设置大小
      "  v_Color = a_Color;\n" + // 将数据传递给片元着色器
      "}\n";
    // 片元着色器
    const FSHADER_SOURCE =
    	"varying vec4 v_color;\n" +
      "void main() {\n" +
      "  gl_FragColor = v_color;\n" + // 设置颜色
      "}\n";
    
    // ...
    const verticesSizes = new Float32Array([
      // 按顺序分别代表
    	// x y r g b
    	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
    ])
    
    // ... 
    
    const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
    // ...
    
    gl.vertextAttribPointer(a_Color, 1, gl.FLOAT, false, FSIZE * 5, FSIZE * 2)
    // 开启分配
    gl.enableVertexAttribArray(a_Color)

  • 在片元着色器中声明v_color 变量,即可通过v_Color = a_Color; ,从顶点着色器向片元着色器传递变量。
  • 将gl.drawArrays 第一次参数更换为gl.TRIANGLES ,会发现程序绘制出了一个颜色平滑过渡,三个角分别是红、绿、蓝颜色的三角形。
  • image

    几何形状的装配和光栅化

    顶点着色器和片元着色器之间,存在两个步骤。

  • 图形装配过程: 将孤立的顶点坐标,装配成几何图形。几何图形的类别由gl.drrawArrays 的第一个参数决定
  • 光栅化过程:将装配好的几何图形转换成片元
  • gl_Position实际上是几何图形装配阶段的输入数据。几何图形装配过程被又称为图元装配过程,因为装配出来的基本图形(点、线、面)又被称为图元。

    当gl.drawArrays()的参数n为3时,顶点着色器将执行三次。整个绘制过程可以分成几步:

    第一步:执行顶点着色器,将缓冲区的第一个坐标传递给a_Position 变量。一旦一个顶点坐标被赋值给gl_Position,它就进入了图形装配区,并暂时储存在那。

    第二步:再次执行顶点着色器,将第二个坐标储存在装配区

    第三部:最后执行顶点着色器,将第三个坐标储存在装配区

    第四步:开始装配图形。使用传入的点坐标,根据gl.drawArrays的第一个参数(gl.TRIAGLES)来决定如何装配。比如用三个顶点装配出一个三角形

    第五步:显示在屏幕上的三角形是由片元(像素)组成的,还需要将图形转换成片元,这个过程被称为光栅化

    image
  • 一旦光栅化过程结束后,程序就开始逐片元调用片元着色器。比如有10个片元就调用10次。对于每个片元,片元着色器会计算该片元的颜色,并写入颜色缓冲区。直到最后一个片元被处理,浏览器会显示最终的结果。
  • 光栅化过程产生的片元都是带有坐标信息的(可以在片元着色器中通过 gl_FragCoor.x / gl_FragCoor.y 访问),调用片元着色器时,这些坐标也随着片元传了进去。
  • 顶点着色器中的v_Color 在传入片元着色器之前,经历了内插过程。所以顶点着色器变量和顶点着色器中的v_Color其实不是一回事。所以要用 varying变量。
  • 在矩形表面贴图像

    纹理映射。是将一张图像映射到一个几何图形的表面上。这张图片又可以称为纹理图像或者纹理。纹理映射的作用,是根据纹理图像,为之前光栅化后的每个片元涂上合适的颜色。组成纹理图像的像素被称为纹素。每个纹素的颜色使用RGB 或 RGBA编码。

    image
  • 纹理坐标。WebGL纹理坐标是二维的,为了与x y 坐标区分开来,WebGL是呀st坐标系。不管图片尺寸多大,s,t最大值始终为1.0.
  • image
  • 将纹理图像粘贴到几何图形上,比如
  • image
    javascriptCopy
    // 顶点着色器
    const VSHADER_SOURCE =
    	"attribute vec4 a_Position;\n" +
    	"attribute vec4 a_TexCoord;\n" +
      "void main() {\n" +
      "  gl_Position = a_Position;\n" + // 设置坐标
      "  v_TextCoord = a_TextCoord;\n" + // 纹理坐标
      "}\n";
    // 片元着色器
    const FSHADER_SOURCE =
    	"uniform sampler2D u_Sampler;\n" +
    	"varying vec2 v_TextCoord;\n" +
      "void main() {\n" +
      "  gl_FragColor = texture2D(u_Sampler, v_TextCoord);\n" + // 设置纹素
      "}\n";
      
    /* ---- 初始化着色器 ---- */ 
    const 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
    ])
    
    const n = 4;
    
    const vertextTexCoordBuffer = gl.createBuffer()
    gl.bindBuffer(gl.ARRAY_BUFFER, vertextTexCoordBuffer)
    gl.bindData(gl.ARRAY_BUFFER, verticesTexCoords, gl.STATIC_DRAW)
    
    const FSIZE = vertextTexCoordBuffer.BYTES_PER_ELEMENT;
     
    // ...
    const a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord')
    gl.vertextAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE*4, FSIZE*2)
    gl.enableVertextAttribArray(a_TexCoord)
     
    // ...
    /* ---- 初始化纹理 ---- */
    const texture = gl.createTexture(); // 创建纹理对象
    
    // ...
    const u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler')
    
    const image = new Image()
    image.onload = () => {
    	// 对纹理图像进行y轴反转
    	gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) 
    	// 开启0号纹理
    	gl.activeTexture(gl.TEXTURE0)
    	// 绑定纹理对象
    	gl.bindTexture(gl.TEXTURE_2D, texture)
    	
    	// 配置纹理参数
    	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILLTER, gl.LINEAR)
    	// 配置纹理图像
    	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image)
    	
    	// 将纹理发送给着色器
    	gl.uniformi(u_Sampler, 0)
    	
    	
    	// ...
    	// 绘制举矩阵形状
    	gl.drawArrays(gl.TRIANGLE_STRIP, 0, n)
    }
    
    image.src = '...'
    
    

  • Sampler意为取样器,输入坐标,从纹理图像中获取颜色值。当取样的坐标没有落在某个像素的中心时,颜色值时通过附近若干个像素共同计算而来。
  • gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1) 。PNG、JPG、BMP等格式图片的坐标Y轴方向和WebGL时相反的,所以需要进行Y轴反转
  • image
  • WebGL通过纹理单元来管理多个纹理。内置变量gl.TEXTURE0 gl.TEXTURE1 … gl.TEXTURE7各代表一个纹理。使用gl.activeTexture 激活纹理。
  • 绑定纹理对象gl.bindTexture。webGL支持gl.TEXTURE_2D 和gl.TEXTURE_CUBE_MAP两种纹理。
  • image
  • 配置纹理参数texParameteri。第二个参数支持4个纹理参数。1. 放大方法 gl.TEXTURE_MAG_FILLTER 当绘制范围比文本大时,通过放大填充像素间的空隙。2. 缩小方法TEXTURE_MIN_FILLTER 3. 水平填充 TEXTURE_WRAP_S 对纹理左侧或右侧区域进行填充。4. 垂直填充TEXTURE_WRAP_T 。
  • image
  • gl.texParameteri 第三个参数
  • image
  • 配置纹理图像gl.texImage2D 。
  • image

    使用多幅纹理

    javascriptCopy
    
    // 片元着色器
    const FSHADER_SOURCE =
    	"uniform sampler2D u_Sampler0;\n" +
    	"uniform sampler2D u_Sampler1;\n" +
    	"varying vec2 v_TextCoord;\n" +
      "void main() {\n" +
      "  vec4 color0 = texture2D(u_Sampler0, v_TextCoord);\n" + // 1号纹理
      "  vec4 color1 = texture2D(u_Sampler1, v_TextCoord);\n" + // 2号纹理
      "  gl_FragColor = color0 * color1;\n" + // 设置纹素
      "}\n";
      
     // ...
     // 导入两个纹理图像,在两个纹理图像加载后,调用gl.drawArray()

  • 勇敢两个文素来计算最终片元颜色,可以用多种方法。当前使用的是矢量相乘。
  • Contents

    • gl.vertextAttribPointer 的步进和偏移参数
    • 传递颜色
    • 几何形状的装配和光栅化
    • 在矩形表面贴图像
    • 使用多幅纹理

    2024/10/15 02:56