WebGL编程指南学习(4)
在学会处理顶点,包括处理顶点的坐标、Javascript和WebGL管线的数据通信、坐标变换之后,还需要处理顶点的其他数据——如颜色等。此外,还需要处理将图像(或纹理)映射到图形或三维对象表面上。这就是WebGL的最后一块拼图。
回顾:顶点坐标传入着色器的仪式感
顶点相关数据送入顶点着色器的步骤是一模一样的
注意:WebGL系统创建的缓冲区对象是不同的,然后分配给不同的attribute变量
新加代码如下:
// 将顶点size写入顶点着色器 gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer); gl.bufferData(gl.ARRAY_BUFFER, sizes, gl.STATIC_DRAW); var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize'); gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(a_PointSize);
![在这里插入图片描述](https://www.www.zyiz.net/i/ll/?i=45c69ad2915a493ea0d92b43f705b617.png?,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA55WM5piO5Z-O,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
问题:使用多个缓冲区对象传递多种数据,适合数据量不大的情况。当顶点数据很多时,有没有高效的办法?
答:可以把顶点的所有相关数据打包到同一个缓冲区对象中
再问:那打包到一起以后,怎么将它们分配给不同的attribute啊?
答:有办法的,而且不止一种办法
verticesSizes
里BYTES_PER_ELEMENT
获得数组每个元素的大小,方面后续按地址查找vertexAttribPointer
里的stride(步长)和offset(步长内的偏移),正确找到不同类型的数据(坐标、点的尺寸)// 顶点坐标和点的尺寸 var verticesSizes = new Floay32Array([ 0.0, 0.5, 10.0 // 第一个点 -0.5, -0.5, 20.0 // 第二个点 0.5, -0.5, 30.0 // 第三个点 ])
// 获取结构化数组元素的大小 var FSIZE = verticesSizes.BYTES_PER_ELEMENT; ... // 获取a_Position的存储位置,分配缓冲区并开启 var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); ... gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 3, 0); gl.enableVertexAttribArray(a_Position); // 步长为3(3个float,其中坐标是第一个,所以offset是0 // 获取a_PointSize的存储位置,分配缓冲区并开启 var a_PointSize = gl.getAttribLocation(gl.program, 'a_PointSize'); ... gl.vertexAttribPointer(a_PointSize, 1, gl.FLOAT, false, FSIZE * 3, FSIZE * 2) gl.enableVertexAttribArray(a_PointSize); // 步长为3, offset是当前步长内偏移两个元素
gl.vertexAttribPointer(location, size, type, normalized, stride, offset)
方法和前面一样,只不过将顶点尺寸数据改为顶点颜色
顶点的颜色数据从JavaScript传给顶点着色器中的attribute变量,但真正影响绘制颜色的gl_FragColor在片元着色器中
顶点着色器使用varying变量向片元着色器传输数据(uniform变量也可以)
提问:就给了3个点的坐标,怎么就变成了三角形呢?三角形的内部像素是怎么填充的?
答:很多细节被掩盖了
在顶点着色器和片元着色器中间,还有两个步骤:图形装备和光栅化过程
gl.drawArray()
的第一个参数决定;vec4 gl_FragCoord // 该内置变量的第1个和第2个分量表示片元在<canvas>坐标系统(窗口)中的坐标值
// 使用gl_FragCoord参与颜色计算 // Fragment Shader var FSHADER_SOURCE = 'precision mediump float;\n' + 'uniform float u_Width;\n' + 'uniform float u_Height;\n' + 'void main(){\n' + ' gl_FragColor = vec4(gl_FragCoord/u_Width, 0.0, gl_FragCoord.y/u_Height, 1.0);\n' + '}\n';
// 从JavaScript传数据 var u_Width = gl.getUniformLocation(gl.program, 'u_Width'); ... var u_Height = gl.getUniformLocation(gl.program, 'u_Height'); ... // 传递u_Width和u_Height gl.uniform1f(u_Width, gl.drawingBufferWidth); gl.uniform1f(u_Height, gl.drawingBufferHeight);
提问:为什么把v_Color赋给每个顶点,最后得到的三角形是渐变的呢?
回答:我们把顶点的颜色付给了顶点着色器中的varying变量,v_Color,它的值被传给片元着色器中的同名、同类型的变量
但是,更准确地说,顶点着色器中的v_Color在传入片元着色器前,经过了内插过程。因此,片元着色器中的v_Color变量和顶点着色器的v_Color实际上并不是一回事——它是变化的(varying)!
为每个小图元设置不同的颜色和位置,可以模拟真实的场景——但是太繁琐了!
使用纹理映射,可以简单地将一张图像映射到一个几何图形的表面上去。此时,这张图片又可称为纹理图像或纹理
纹理映射,就是根据纹理图像,为光栅化后的每个片元涂上合适的颜色
组成纹理图像的像素又可称为纹素(texels)
说明
纹理坐标
var 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';
var 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(){ ... var n = initVertexBuffers(gl); ... gl.clearColor(0.0, 0.0, 0.0, 1.0); // 设置纹理 if (!initTextures(gl, n)){ console.log('Failed to intialize the texture.'); return; } } function initVertexBuffers(gl){ var 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, ]); var n = 4; // 创建VBO ... // 传入顶点着色器 var FSIZE = verticesTexCoords.BYTES_PER_ELEMENT; var a_Position = gl.getAttribLocation(gl.program, 'a_Position'); ... gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, FSIZE * 4, 0); gl.enableVertexAttribArray(a_Position); var a_TexCoord = gl.getAttribLocation(gl.program, 'a_TexCoord'); ... gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, FSIZE * 4, FSIZE * 2); gl.enableVertexAttribArray(a_TexCoord); return n; }
function initTextures(gl, n){ var texture = gl.createTexture(); // 创建纹理对象 ... // 找到u_Sampler var u_Sampler = gl.getUniformLocation(gl.program, 'u_Sampler'); ... // 创建图像对象 var image = new Image(); ... // 注册事件处理器,响应图像加载调用 image.onload = function(){loadTexture(gl, n, texture, u_Sampler, image);}; // 告诉浏览器加载图像 image.src = '../resources/sky.jpg'; return true; }
function loadTexture(gl, n, texture, u_Sampler, image){ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); // 沿着y轴翻转图像 // 启用纹理单元0 gl.activeTexture(gl.TEXTURE0); // 绑定纹理对象 gl.bindTexture(gl.TEXTURE_2D, texture); // 设置纹理参数 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); // 设置纹理图像 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image); // 设置纹理单元0的采样器 gl.uniform1i(u_Sampler, 0); ... }
没啥好说的
gl.createTexture()
创建纹理对象;纹理对象管理WebGL系统中的纹理u_Sampler
(取样器,采样器),用来接收纹理图像gl.pixelStorei(pname, param)
gl.activeTexture()
。WebGL通过纹理单元机制同时使用多个纹理
gl.TEXTURE0...gl.TEXTURE7
gl.TEXTURE_2D
和立方体纹理gl.TEXTURE_CUBE_MAP
; 注意gl.bindTexture
做了两件事,开启纹理对象,以及将纹理对象绑定到纹理单元上。所以纹理缓冲这里没有enable函数gl.texParameteri(target, pname, param)
pname
指定4中纹理参数:
gl.TEXTURE_MAG_FILTER
:放大方法,当纹理绘制范围比纹理大时,如何填充空隙gl.TEXTURE_MIN_FILTER
:缩小方法,当纹理绘制范围比纹理小时,如何提出像素gl.TEXTURE_WRAP_S|gl.TEXTURE_WRAP_T
:水平|垂直填充方法,如何对纹理图像左右侧|上下方区域进行填充param
指定具体方式
gl.LINEAR
线性加权;gl.NEAREST
最近邻像素的颜色值gl.REPEAT
平铺重复; gl.MIRRORED_REPEAR
镜像对称式的重复;gl.CLAMP_TO_EDGE
使用纹理图像边缘值拉伸gl.NEAREST_MINMAP_LINEAR
,构造金字塔纹理gl.texImage2D(target, level, internalformat, format, type, image)
target
TEXTURE_2D还是TEXTURE_CUBE_MAPlevel
金字塔纹理的层级internalformat
图像的内部格式
format
纹理数据的格式,必须使用与internalformat相同的值type
纹理数据的类型gl.uniform1i()
initTexture()
将u_Sampler
传给了loadTexture()
u_Sampler
,第二个参数指定了纹理单元编号u_Sampler
)抽取出纹素的颜色,然后涂到当前的片元上vec4 texture2D(sampler2D u_Sampler, vec 2 v_TexCoord)
)
internalformat
参数决定