WebGL编程指南 - 颜色与纹理
-
将顶点的其他(非坐标)数据——如颜色等——传入顶点着色器。
-
发生在顶点着色器和片元着色器之间的从图形到片元的转化,又称为图元光栅化 (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次,每调用一次处理一个片元。对于每个片元(本身带有坐标信息),片元着色器计算出该片元的颜色,写入颜色缓冲区。
全部处理完毕后,浏览器会显示最终的结果。
对片元着色器内部操作:
片元本身带有坐标信息,调用片元着色器时,这些坐标信息也随着片元传了过去,我们可以通过片元着色器内置变量来访问该坐标信息:
参考:【《WebGL编程指南》读书笔记-颜色与纹理】_webgl warning: texsubimage: texture has not been i-CSDN博客