基本概念
片元着色器执行过程
顶点坐标 -》 图形装配 -》 光栅化 -》执行片元着色器
光栅概念
图形装配过程 :这一步的任务是,将孤立的顶点坐标装配成几何图形,几何图形的类别由 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 的是不一致的,贴图片的时候需要将坐标位置进行调整
纹理
给四边形进行纹理绘制
图片:
html
<!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);
// 绑定纹理对象s
gl.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>