6、webgl 基本概念 + 四边形纹理
基本概念
片元着色器执行过程
顶点坐标 -》 图形装配 -》 光栅化 -》执行片元着色器
光栅概念
图形装配过程:这一步的任务是,将孤立的顶点坐标装配成几何图形,几何图形的类别由 gl.drawArrays() 函数的第一个参数决定。
光栅化过程:这一步的任务是,将装配好的几何图形转换为片元。
备注: gl_Position 实际上是 几何图形装配(geometric shape assembly) 阶段的输入数据。注意,几何图形装配过程又被称为图元装配过程(primitive assembly process) ,因为被装配出的基本图形(点、线、面)又被称为 图元(primitive)
纹理映射
可以理解为皮肤,我们可以给绘制的图形贴上一张图片。
纹理映射就是为图像添加上皮肤,将一张图片/图像映射到一个几何图形上面去,即将一张真实世界的图片贴到一个由两个三角形组成的矩形上,这样矩形表面看上去就是这样图片,这张图片也称为纹理图像或纹理。
作用: 根据纹理图像,为之前光栅化的每个片元涂上颜色。
组成纹理图像的像素又被称为纹素。
纹理映射过程
1、准备纹理图形 —》 new Image();
2、配置纹理映射方式 —》 将纹理图像如何放置在图形上;
3、加载纹理图像,并且对其配置,以在 webgl 中使用;
4、在片元着色器中将相应的纹素从纹理中取出,并将纹素的颜色赋值给片元
注意:纹理图像的适用需要打开服务器(因为纹理图像在使用的过程中会收到同源策略的影响)
简单的添加纹理程序
1、顶点着色器中接收顶点的纹理坐标,光栅化后传递给片元着色器
2、片元着色器根据片元的纹理坐标,从纹理图像中抽取出纹素颜色,赋给当前片元
3、色值顶点的纹理坐标(initVertexBuffers())
4、准备待加载的纹理图像,令浏览器读取它(initTextures())
5、监听纹理图像的加载事件,一旦加载完成,就在 webgl 系统中使用纹理(loadTexture())
创建纹理
1、顶点着色器中接收顶点的纹理坐标,光栅化后传递给片元着色器
2、片元着色器根据片元的纹理坐标,从纹理图像中取出纹素颜色赋给当前片元
取样器类型:sampler2D,samplerCube
取样器只能是 uniform 变量
注意:唯一能赋值给取样器变量的是纹理单元编号,必须使用 gl.uniform1i() 赋值
sampler2D:2d 的图像
samplerCube: 立方体的图像
3、设置顶点的纹理坐标
4、配置和加载纹理
4.1 创建纹理对象— gl.createTexture() 方法可以创建纹理对象
4.2 删除纹理对象 — gl.deleteTexture(texture)
5、为 webgl 配置纹理
(1)图片坐标轴旋转(沿 y 轴旋转)
(2)激活纹理单元
webgl 通过一种叫做纹理单元的机制来同时使用多个纹理,每个纹理单元有一个单元编号来管理一张纹理图像。即使你的程序只需要一个纹理,也需要为其指定一个纹理单元。
系统支持纹理单元的个数取决于硬件和浏览器的 webgl 实现,但在默认情况下,webgl 支持 8 哥纹理单元,一些其他的系统支持得更多。内置 gl.TEXTURE0()、gl.TEXTURE1 。。。 gl.TEXTURE7 各表示一个纹理单元.
在使用纹理单元之前,还需要调用 gl.activeTexture() 来激活它
(3)绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture)
纹理类型:
二维纹理: gl.TEXTURE_2D
立方体纹理: gl.TEXTURE_CUBE_MAP
(4)配置纹理参数
gl.texParameteri(target, pname, param)
target: gl.TEXTURE_2D 或者 gl.TEXTURE_CUBE_MAP
pname: 纹理参数【放大方法(gl.TEXTURE_MAG_FILTER)、缩小方法(gl.TEXTURE_MIN_FILTER)、水平填充方法(gl.TEXTURE_WRAP_S)、垂直填充方法(gl.TEXTURE_WRAP_T)】
param: 纹理参数的值
param 的可选值:
1)可以赋值给【 放大方法(gl.TEXTURE_MAG_FILTER)、缩小方法(gl.TEXTURE_MIN_FILTER)】 的非金字塔纹理类型常量:
gl.NEAREST : 使用原纹理上距离映射后像素(新像素)中心最近的哪个像素的颜色值,作为新像素的值(使用曼哈顿距离)
gl.LINEAR:使用距离新像素中心最近的四个像素的颜色值的加权平均,作为新像素的值
2)可以赋值给【水平填充方法(gl.TEXTURE_WRAP_S)、垂直填充方法(gl.TEXTURE_WRAP_T)】的常量
gl.REPEAT: 平铺式的重复纹理
gl.MIRRORED_REPEAT: 镜像对称式的重复纹理
gl.CLAM_TO_EDGE: 使用纹理图像边缘值
(5)配置纹理图像
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
(6)将纹理单元传递给片元着色器

注意:图片自身的坐标系统跟 webgl 的是不一致的,贴图片的时候需要将坐标位置进行调整
纹理
给四边形进行纹理绘制
图片:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><canvas width="500" height="500" id="myCanvas"></canvas><script type="vertex" id="vertex">/*a_position: 顶点坐标a_TexCoord: 纹理坐标screenSize:canvas 画布大小v_TexCoord: 传递给片元着色器的纹理坐标*/attribute vec2 a_position;attribute vec2 a_TexCoord;uniform vec2 screenSize;varying vec2 v_TexCoord;void main() {/*坐标转换, 将 canvas 坐标转化为 webgl 的坐标*/float x = a_position.x * 2.0 / screenSize.x - 1.0;float y = 1.0 - a_position.y * 2.0 / screenSize.y;gl_Position = vec4(x, y, 0, 1);gl_PointSize = 10.0;/*将纹理坐标传递给片元着色器 */v_TexCoord = a_TexCoord;}</script><script type="fragment" id="fragment">/*片元着色器:绘制颜色或者纹理的 —— 水彩的作用*/precision mediump float;varying vec2 v_TexCoord;/*纹理图像源数据*/uniform sampler2D u_Sampler;void main() {/*texture2D: 将纹理图像上指定的位置(纹理坐标)纹素颜色信息取出*/gl_FragColor = texture2D(u_Sampler, v_TexCoord);}</script><script>let myCanvas = document.getElementById("myCanvas");let gl = myCanvas.getContext("webgl");// IE8 之前的浏览器不兼容if(!gl) {alert("浏览器不支持 webgl!")}// 创建着色器function createShader(gl, type, source) {const shader = gl.createShader(type);gl.shaderSource(shader, source);gl.compileShader(shader);let isSuccess = gl.getShaderParameter(shader, gl.COMPILE_STATUS);return isSuccess ? shader : console.log(gl.getShaderInfoLog(shader))}// 获取着色器中的文本内容字符串function getInnerText(id) {return document.getElementById(id).innerText;}const vertexStr = getInnerText("vertex");const fragmentStr = getInnerText("fragment");const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexStr)const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentStr);// 创建程序function createProgram(gl, vertexShader, fragmentShader) {let program = gl.createProgram();gl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);gl.linkProgram(program);let isSuccess = gl.getProgramParameter(program, gl.LINK_STATUS);return isSuccess ? program : console.log(gl.getProgramInfoLog(program));}let program = createProgram(gl, vertexShader, fragmentShader);gl.useProgram(program);// 获取顶点着色器中变量let a_position = gl.getAttribLocation(program, 'a_position');let screenSize = gl.getUniformLocation(program, 'screenSize');let a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord');let u_Sampler = gl.getUniformLocation(program, "u_Sampler");// 获取画布的宽高,因为顶点着色器中已经gl.uniform2f(screenSize, myCanvas.width, myCanvas.height);let positionBuffer = gl.createBuffer();let indexBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer); // 绑定索引缓冲区gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);let img = new Image()img.src = './1.jpg';let texture = gl.createTexture();img.onload = () => {// 激活纹理单元gl.activeTexture(gl.TEXTURE0);// 绑定纹理对象sgl.bindTexture(gl.TEXTURE_2D, texture);gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);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.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);gl.uniform1i(u_Sampler, 0);draw();}function draw() {// 矩形的正面,要逆时针的顶点进行写, 矩形就是两个三角形,也就是需要写 6个 顶点,  gl.ARRAY_BUFFER 这个缓冲区需要把所有的顶点都写上,但其实例如矩形的时候有些顶点是重复的,当面越来越多就会造成严重的性能问题,此时我们可以使用 gl.ELEMENT_ARRAY_BUFFER(也就是顶点索引)缓冲区来进行优化-只需要传不重复的点//  图片自身的坐标系统跟 webgl 的是不一致的,贴图片的时候需要将坐标位置进行调整gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([50, 50, 0, 0, 50, 200, 0, 1, 250, 200, 1, 1,  250, 200, 1, 1,  250, 50, 1, 0,50, 50, 0, 0,]), gl.STATIC_DRAW);// gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([//     0, 1, 2,//     2, 3, 0// ]), gl.STATIC_DRAW);gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 4 * 4, 0);// 为纹理坐标传递数据gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 4 * 4, 4* 2);// 启用这个位置数据gl.enableVertexAttribArray(a_position);gl.enableVertexAttribArray(a_TexCoord);// 绘制三角形 drawArrays 直接从数据的缓冲区取数据// gl.drawElements(mode, count, type, offset) // mode: 制定绘制图元的类型, gl.POINTS, gl.TRIANGLES, gl.LINES...// count: 指定绘制图形的顶点个数// type:指定索引缓冲区的值的类型,常用的值有两个 gl.UNSINGED_TYPE(无符号8位整数值) 和 gl.UNSINGED_SHORT(无符号短整型16位)// offset:指定索引数组中开始绘制的位置,以字节为单位// gl.drawElements(gl.TRIANGLES_FAN, 6, gl.UNSIGNED_SHORT, 0);gl.drawArrays(gl.TRIANGLES, 0, 6);}draw()</script>
</body>
</html>
