WebGL基础
在正式进入webgl之前,我想有必要简单了解一下渲染管线,毕竟它贯穿webgl学习的整个过程。
渲染管线流程图:
webgl着色器简单语法:
在GLSL(OpenGL Shading Language)中,常见的变量类型可以归纳为以下几种,并根据提供的参考文章进行详细的解释:
- 基本数据类型:
- 整型(int):用于存放自然数的变量。
- 浮点型(float):用于存放小数点数值的变量。
- 布尔型(bool):用于存放0值或非0值,0代表false,1代表true。
- 向量(vec):
- GLSL中的向量类型可以是2维(vec2)、3维(vec3)或4维(vec4)。
- 向量中的数据类型可以是整数、浮点数或布尔值。
- 向量在GLSL中常用于描述颜色、坐标和纹理等。
- GLSL内置变量:
- 顶点属性(attribute):指顶点的信息,如顶点坐标、纹理坐标、颜色等。GLSL会为这些传统的顶点属性设置内置变量与之对应,以便在需要时可以在顶点或片元着色器中直接引用。
- 一致变量(uniform):是外部应用程序传递给(顶点和片元)着色器的变量。它在着色器程序内部是不可修改的,通常用于表示变换矩阵、材质、光照参数和颜色等信息。
- 易变变量(varying):由顶点程序向片元程序插值传递的变量。这些变量在顶点着色器中定义,并在片元着色器中使用,用于传递从顶点计算得到的插值数据。
- 其他内置变量:如
gl_ModelViewMatrix
(模型视图变换矩阵)、gl_ProjectMatrix
(投影矩阵)等,这些变量描述了OpenGL的状态,并在着色器之间共享。
- 特殊变量:
- gl_Position:在顶点着色器中用于写顶点位置,其类型是
highp vec4
。 - gl_FragColor:在片元着色器中用于写片元颜色,其类型是
mediump vec4
。 gl_PointSize
:在顶点着色器中用于写光栅化后的点大小,其类型是mediump float
。
- gl_Position:在顶点着色器中用于写顶点位置,其类型是
向量与矩阵 | 常见内置变量 | 常见数据类型 | 常见修饰符 |
vec3 | gl_PointSize | float | attribute |
vec4 | gl_Position | int | uniform |
mat3 | gl_FragColor | bool | varying |
mat4 | gl_FragCoord | sampler2D | — |
gl_ModelViewMatrix | |||
gl_ProjectMatrix |
说明:
- in和out
glsl版本不同,attribute和uniform在glsl 新本本对应着in
和out
是两种特殊的变量限定符,它们用于在着色器阶段之间传递数据。
in
限定符用途:
in
用于在着色器阶段之间输入数据。在顶点着色器中,in
变量通常表示从CPU传入的顶点数据,如顶点位置、纹理坐标等。在片元着色器中,in
变量表示从顶点着色器插值传递过来的数据。示例:
// 顶点着色器示例 #version 330 core layout(location = 0) in vec3 aPos; // aPos是一个in变量,表示顶点位置 out vec4 vertexColor; // vertexColor是一个out变量,用于将颜色数据传递给片元着色器 void main() { gl_Position = vec4(aPos, 1.0); // 设置顶点位置 vertexColor = vec4(1.0, 0.0, 0.0, 1.0); // 设置顶点颜色为红色 }
out
限定符
用途:
out
用于在着色器阶段之间输出数据。在顶点着色器中,out
变量用于将计算得到的数据传递给下一个着色器阶段(通常是片元着色器)。在片元着色器中,out
变量表示最终的像素颜色或其他输出数据。示例:
// 片元着色器示例 #version 330 core in vec4 vertexColor; // vertexColor是一个in变量,从顶点着色器接收颜色数据 out vec4 FragColor; // FragColor是一个out变量,表示最终的像素颜色 void main() { FragColor = vertexColor; // 直接将顶点着色器传递过来的颜色作为最终像素颜色 }
- 坐标变换:【3D基础】坐标转换——地理坐标投影到平面_将世界地理坐标投影到平面的方法-CSDN博客
- Three.js和Cesium.js中坐标_threejs 物体缩放后需要更新世界坐标吗-CSDN博客
- 最后在正式开始前,先大致了解整个流程:
-
整个流程:
(注:上述内容为搭建一个框架,此外课程学习前可能需一些计算机图形学和线性代数的基本知识,若还未学习相关内容的小伙伴可以去B站学习相关内容,推荐《计算机图形学入门》和《线性代数的本质》两门课程,当然学习webgl还是很推荐郭老师的课程哈!课程+博客!)
了解大致流程后,开始正式webgl入门课程的学习:
1.绘制一个点
1.1实现效果:
1.2实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用WebGL绘制一个点</title>
</head>
<body>
<!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
<canvas id="webgl" width="500" height="500" style="background-color: greenyellow"></canvas>
<!-- 写法一(推荐) -->
<!-- 顶点着色器源码 -->
<script id="vertexShader" type="x-shader/x-vertex">
void main() {
//给内置变量gl_PointSize赋值像素大小
gl_PointSize=20.0;
//顶点位置,位于坐标原点
gl_Position =vec4(0.0,0.0,0.0,1.0);
}
</script>
<!-- 片元着色器源码 -->
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
<!-- 写法二(仅大致了解) -->
<!-- //顶点着色器源码
var vertexShaderSource = '' +
'void main(){' +
//给内置变量gl_PointSize赋值像素大小
' gl_PointSize=10.0;' +
//顶点位置,位于坐标原点
' gl_Position =vec4(0.0,0.0,0.0,1.0);' +
'}';
//片元着色器源码
var fragShaderSource = '' +
'void main(){' +
//定义片元颜色
' gl_FragColor = vec4(1.0,0.0,0.0,1.0);' +
'}'; -->
<script>
//通过getElementById()方法获取canvas画布
var canvas=document.getElementById('webgl');
//通过方法getContext()获取WebGL上下文
var gl=canvas.getContext('webgl');
//顶点着色器源码
var vertexShaderSource = document.getElementById( 'vertexShader' ).innerText;
//片元着色器源码
var fragShaderSource = document.getElementById( 'fragmentShader' ).innerText;
//初始化着色器
var program = initShader(gl,vertexShaderSource,fragShaderSource);
//开始绘制,显示器显示结果
gl.drawArrays(gl.POINTS,0,1);
//声明初始化着色器函数
function initShader(gl,vertexShaderSource,fragmentShaderSource){
//创建顶点着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
//创建片元着色器对象
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入顶点、片元着色器源代码
gl.shaderSource(vertexShader,vertexShaderSource);
gl.shaderSource(fragmentShader,fragmentShaderSource);
//编译顶点、片元着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着顶点着色器和片元着色器到program
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
</script>
</body>
</html>
1.3解析:刚入门第一个案例较为简单,通过本案例了解代码编写整个流程即可
2. 绘制一个矩形
2.1实现效果:
2.2实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>使用WebGL绘制一个矩形</title>
</head>
<body>
<!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
<canvas id="webgl" width="500" height="500" style="background-color: greenyellow"></canvas>
<!-- 顶点着色器源码 -->
<script id="vertexShader" type="x-shader/x-vertex">
//非单点,用类型化数组
//attribute声明vec4类型变量apos
attribute vec4 apos;
void main() {
//顶点坐标apos赋值给内置变量gl_Position
//逐顶点处理数据
gl_Position = apos;
}
</script>
<!-- 片元着色器源码 -->
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
<script>
//通过getElementById()方法获取canvas画布
var canvas=document.getElementById('webgl');
//通过方法getContext()获取WebGL上下文
var gl=canvas.getContext('webgl');
//顶点着色器源码
var vertexShaderSource = document.getElementById( 'vertexShader' ).innerText;
//片元着色器源码
var fragShaderSource = document.getElementById( 'fragmentShader' ).innerText;
//初始化着色器
var program = initShader(gl,vertexShaderSource,fragShaderSource);
//获取顶点着色器的位置变量ap os,即aposLocation指向apos变量。
var aposLocation = gl.getAttribLocation(program,'apos');
//类型数组构造函数Float32Array创建顶点数组
var data=new Float32Array([0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5]);
//创建缓冲区对象
var buffer=gl.createBuffer();
//绑定缓冲区对象,激活buffer
// gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
//顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER,data,gl.STATIC_DRAW);
//缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation,2,gl.FLOAT,false,0,0);
//允许数据传递
gl.enableVertexAttribArray(aposLocation);
//开始绘制,显示器显示结果
gl.drawArrays(gl.LINE_LOOP,0,4);
//声明初始化着色器函数
function initShader(gl,vertexShaderSource,fragmentShaderSource){
//创建着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入着色器源代码
gl.shaderSource(vertexShader,vertexShaderSource);
gl.shaderSource(fragmentShader,fragmentShaderSource);
//编译着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着着色器到program
gl.attachShader(program,vertexShader);
gl.attachShader(program,fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
</script>
</body>
</html>
2.3解析:本案例,在之前案例的基础上,增加了声明变量和为变量传递数据的过程。在webgl中先需创建一个类型化数组存储“顶点”信息,然后在绘制的过程,指定绘制的类型,这时进行图元装配时就会将点按指定要求绘制成几何体。如本案例就是绘制类型gl.LINE_LOOP(闭合线)就会将点连城闭合线(矩形)
3.绘制三角形|投影(UV坐标)
3.1实现效果
3.2实现代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>绘制三角形|投影</title>
</head>
<body>
<!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
<canvas id="webgl" width="500" height="500" style="background-color: greenyellow"></canvas>
<!-- 顶点着色器源码 -->
<script id="vertexShader" type="x-shader/x-vertex">
//非单点,用类型化数组
//attribute声明vec4类型变量apos
attribute vec4 apos;
void main() {
//顶点坐标apos赋值给内置变量gl_Position
//逐顶点处理数据
gl_Position=apos;
}
</script>
<!-- 片元着色器源码 -->
<script id="fragmentShader" type="x-shader/x-fragment">
void main() {
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
}
</script>
<script>
//通过getElementById()方法获取canvas画布
var canvas = document.getElementById('webgl');
//通过方法getContext()获取WebGL上下文
var gl = canvas.getContext('webgl');
//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;
//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;
//初始化着色器
var program = initShader(gl, vertexShaderSource, fragShaderSource);
//获取顶点着色器的位置变量apos,即aposLocation指向apos变量。
var aposLocation = gl.getAttribLocation(program, 'apos');
//类型数组构造函数Float32Array创建顶点数组
var data = new Float32Array([
// 将对z轴进行投影,投影为二维平面,所以第一个顶点的1.0,可以取值[-1.0,1.0],效果都一样,若超过1.0或少于-1.0,测试不难发现会有部分被裁掉,因为在webgl中坐标为[-1.0,1.0]
0.0, 0.0, 2.0, //三角形顶点1坐标
0.0, 1.0, 0.0, //三角形顶点2坐标
1.0, 0.0, 0.0 //三角形顶点3坐标
]);
//创建缓冲区对象
var buffer = gl.createBuffer();
//绑定缓冲区对象,激活buffer
// gl.bindBuffer(gl.ARRAY_BUFFER,buffer);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
//顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
//缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(aposLocation, 3, gl.FLOAT, false, 0, 0);
//允许数据传递
gl.enableVertexAttribArray(aposLocation);
//开始绘制,显示器显示结果
gl.drawArrays(gl.TRIANGLES, 0, 3);
//声明初始化着色器函数
function initShader(gl, vertexShaderSource, fragmentShaderSource) {
//创建着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//编译着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
}
</script>
</body>
</html>
3.3解析:本案例将对z轴进行投影,投影为二维平面,所以第一个顶点的1.0,可以取值[-1.0,1.0],效果都一样,若超过1.0或少于-1.0,测试不难发现会有部分被裁掉,因为在webgl中坐标为[-1.0 ,1.0],所以效果左边的图设置三角形顶点1的z值[-1.0,1.0]皆是那个效果,右图为z值为2.0的效果
中级案例:
绘制一个时钟
1.实现效果:
2.实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>时针</title>
<style>
#myCanvas{
background-color: rgb(246, 246, 249);
}
</style>
<script src="glMatrix-0.9.6.min.js"></script>
<script>
let vertexstring = `
attribute vec4 a_position;
uniform mat4 u_formMatrix;
void main(void){
gl_Position =u_formMatrix * a_position;
}
`;
let fragmentstring = `
precision mediump float;
void main(void){
gl_FragColor =vec4(0.0,0.0,0.0,1.0);
}
`;
var projMat4 = mat4.create();
var webgl;
var uniformTexture = 0;
var uniformTexture1 = 0;
var uniformAnim = 0;
var count = 0;
function webglStart() {
init();
tick();
}
function tick() {
window.setTimeout(tick, 1000);
initbuffer(0.8);
initbuffer(0.5);
initbuffer(0.4);
count = count +1;
};
function init() {
initWebgl();
initShader();
}
function initWebgl() {
let webglDiv = document.getElementById('myCanvas');
webgl = webglDiv.getContext("webgl");
webgl.viewport(0, 0, webglDiv.clientWidth, webglDiv.clientHeight);
//mat4.ortho(0, webglDiv.clientWidth, webglDiv.clientHeight, 0, -1.0, 1.0, projMat4)
}
function initShader() {
let vsshader = webgl.createShader(webgl.VERTEX_SHADER);
let fsshader = webgl.createShader(webgl.FRAGMENT_SHADER);
webgl.shaderSource(vsshader, vertexstring);
webgl.shaderSource(fsshader, fragmentstring);
webgl.compileShader(vsshader);
webgl.compileShader(fsshader);
if (!webgl.getShaderParameter(vsshader, webgl.COMPILE_STATUS)) {
var err = webgl.getShaderInfoLog(vsshader);
alert(err);
return;
}
if (!webgl.getShaderParameter(fsshader, webgl.COMPILE_STATUS)) {
var err = webgl.getShaderInfoLog(fsshader);
alert(err);
return;
}
let program = webgl.createProgram();
webgl.attachShader(program, vsshader);
webgl.attachShader(program, fsshader)
webgl.linkProgram(program);
webgl.useProgram(program);
webgl.program = program
}
function initbuffer(radius){
//秒分时针,根据长度分且角度需合理设置
let angle;
if(radius>0.6){
//角度为负值
angle = -Math.PI/30 *count;
}
else if(radius>0.4){
angle = -Math.PI/1800 *count;
}
else{
angle = -Math.PI/1800 *count *5/60;
// angle = -Math.PI/30 *count;
}
const cos = Math.cos(angle)*radius;
const sin = Math.sin(angle)*radius;
var matArr= new Float32Array([
cos,sin,0,0,
-sin,cos,0,0,
0,0,1,0,
0,0,0,1
]);
let arr = [
0, 0, 0, 1,
0, 0.05, 0, 1,
radius, 0, 0, 1,
radius, 0, 0, 1,
0, 0.05, 0, 1,
radius, 0.05, 0, 1
]
let pointPosition = new Float32Array(arr);
let aPsotion = webgl.getAttribLocation(webgl.program, "a_position");
let triangleBuffer = webgl.createBuffer();
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.bufferData(webgl.ARRAY_BUFFER, pointPosition, webgl.STATIC_DRAW);
webgl.enableVertexAttribArray(aPsotion);
webgl.vertexAttribPointer(aPsotion, 4, webgl.FLOAT, false, 0, 0);
let uniformMatrix = webgl.getUniformLocation(webgl.program, "u_formMatrix");
webgl.uniformMatrix4fv(uniformMatrix, false, matArr)
webgl.drawArrays(webgl.TRIANGLES, 0, 6);
}
3.核心讲解:
3.1动画设置:每隔1秒钟绘制新的图形(图形不断进行旋转)
function tick() {
window.setTimeout(tick, 1000);
initbuffer(0.8);
initbuffer(0.5);
initbuffer(0.4);
count = count +1;
};
3.2时针旋转角度计算、旋转矩阵构建:根据常识计算旋转角度,需要注意的是这角度为负值,旋转方向才为顺时针,旋转矩阵可看作绕Z轴旋转所得的矩阵
//秒分时针,根据长度分且角度需合理设置
let angle;
if(radius>0.6){
//角度为负值
angle = -Math.PI/30 *count;
}
else if(radius>0.4){
angle = -Math.PI/1800 *count;
}
else{
angle = -Math.PI/1800 *count *5/60;
// angle = -Math.PI/30 *count;
}
const cos = Math.cos(angle)*radius;
const sin = Math.sin(angle)*radius;
var matArr= new Float32Array([
cos,sin,0,0,
-sin,cos,0,0,
0,0,1,0,
0,0,0,1
]);
多层纹理实现雾效果
1.实现效果:
2.实现代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>多重纹理</title>
</head>
<body>
<!--canvas标签创建一个宽高均为500像素,背景为蓝色的矩形画布-->
<canvas id="webgl" width="500" height="500" style="background-color: greenyellow"></canvas>
<!-- 顶点着色器源码 -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;//顶点位置坐标
attribute vec2 a_TexCoord;//纹理坐标
varying vec2 v_TexCoord;//插值后纹理坐标
void main() {
//顶点坐标apos赋值给内置变量gl_Position
gl_Position = a_Position;
//纹理坐标插值计算
v_TexCoord = a_TexCoord;
}
</script>
<!-- 片元着色器源码 -->
<script id="fragmentShader" type="x-shader/x-fragment">
//所有float类型数据的精度是highp
precision highp float;
// 接收插值后的纹理坐标
varying vec2 v_TexCoord;
// 纹理图片像素数据
uniform sampler2D u_Sampler;
uniform sampler2D u_Sampler1;
uniform float u_Ani;
void main() {
// 采集纹素,逐片元赋值像素值
vec4 color1 = texture2D(u_Sampler,v_TexCoord);
vec4 color2 = texture2D(u_Sampler1,vec2(v_TexCoord.x + u_Ani , v_TexCoord.y));
gl_FragColor = color1 + color2;
}
</script>
<script>
//通过getElementById()方法获取canvas画布
var canvas = document.getElementById('webgl');
//通过方法getContext()获取Gl上下文
var gl = canvas.getContext('webgl');
//顶点着色器源码
var vertexShaderSource = document.getElementById('vertexShader').innerText;
//片元着色器源码
var fragShaderSource = document.getElementById('fragmentShader').innerText;
var count = 0;
//初始化着色器
var program = initShader(gl, vertexShaderSource, fragShaderSource);
var a_Position = gl.getAttribLocation(program, 'a_Position');
var a_TexCoord = gl.getAttribLocation(program, 'a_TexCoord');
var u_Sampler = gl.getUniformLocation(program, 'u_Sampler');
var u_Sampler1 = gl.getUniformLocation(program, 'u_Sampler1');
var u_Ani = gl.getUniformLocation(program, 'u_Ani');
//拉伸改变其坐标值
var data = new Float32Array([
-0.5, 0.5, //左上角——v0
-0.5, -0.5, //左下角——v1
0.5, 0.5, //右上角——v2
0.5, -0.5 //右下角——v3
]);
/**
* 创建UV纹理坐标数据textureData
**/
var textureData = new Float32Array([
0, 1, //左上角——uv0
0, 0, //左下角——uv1
1, 1, //右上角——uv2
1, 0 //右下角——uv3
]);
//创建缓冲区对象
var buffer = gl.createBuffer();
//绑定缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
//顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
//缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(a_Position, 2, gl.FLOAT, false, 0, 0);
//允许数据传递
gl.enableVertexAttribArray(a_Position);
//创建缓冲区对象
var textureBuffer = gl.createBuffer();
//绑定缓冲区对象,激活buffer
gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
//顶点数组data数据传入缓冲区
gl.bufferData(gl.ARRAY_BUFFER, textureData, gl.STATIC_DRAW);
//缓冲区中的数据按照一定的规律传递给位置变量apos
gl.vertexAttribPointer(a_TexCoord, 2, gl.FLOAT, false, 0, 0);
//允许数据传递
gl.enableVertexAttribArray(a_TexCoord);
//要调用呀!写了函数不用是什么鬼
loop();
/**
创建缓冲区textureBuffer,传入图片纹理数据,然后执行绘制方法drawArrays()
**/
texture1 = initTexture("fog.png");
texture0 = initTexture("山水.png");
function handleLoadedTexture(texture) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
// 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);
}
function initTexture(imageFile) {
let textureHandle = gl.createTexture();
textureHandle.image = new Image();
textureHandle.image.src = imageFile;
textureHandle.image.onload = function () {
handleLoadedTexture(textureHandle)
}
return textureHandle;
}
function loop() {
requestAnimationFrame(loop);
draw();
}
function draw() {
gl.clearColor(0.0, 1.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
count = count + 0.01;
// console.log(count);
gl.uniform1f(u_Ani, count);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture0);
gl.uniform1i(u_Sampler, 0);
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.uniform1i(u_Sampler1, 1);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
}
//声明初始化着色器函数
function initShader(gl, vertexShaderSource, fragmentShaderSource) {
//创建着色器对象
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
//引入着色器源代码
gl.shaderSource(vertexShader, vertexShaderSource);
gl.shaderSource(fragmentShader, fragmentShaderSource);
//编译着色器
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
//创建程序对象program
var program = gl.createProgram();
//附着着色器到program
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
//链接program
gl.linkProgram(program);
//使用program
gl.useProgram(program);
//返回程序program对象
return program;
};
</script>
</body>
</html>
3.核心拆解:
3.1 片元着色器中调用 texture2D(texture,texcoord),方法有两个参数,一个为所需要的纹理,另一部分为纹理坐标。纹理坐标好办,创建类型化数组传递即可。本案例涉及多重纹理叠加,所以最后片源着色器的颜色为两种颜色的和。
vec4 color1 = texture2D(u_Sampler,v_TexCoord);
vec4 color2 = texture2D(u_Sampler1,vec2(v_TexCoord.x + u_Ani , v_TexCoord.y));
gl_FragColor = color1 + color2;
本案例还涉及动态雾,所以需要利用requestAnimationFrame()方法,进行不断绘制。
function tick() {
requestAnimFrame(tick)
draw();
};
下面重点讲述纹理数据的创建与传递,纹理的创建可参考缓冲区的创建:
1.webgl.createTexture创建纹理对象
2.配置纹理对象的属性 .image .image.src .image.onload
3.绑定纹理、设置二维纹理、设置纹理属性,将其封装归类到handleLoaderTexture()方法中
4.最后在绘制函数draw()中激活、绑定、传递纹理数据
function handleLoadedTexture(texture) {
webgl.bindTexture(webgl.TEXTURE_2D, texture);
webgl.texImage2D(webgl.TEXTURE_2D, 0, webgl.RGBA, webgl.RGBA, webgl.UNSIGNED_BYTE, texture.image);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.LINEAR);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.LINEAR);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.REPEAT);
webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.REPEAT);
// webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
// webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);
}
function initTexture(imageFile, num) {
let textureHandle = webgl.createTexture();
textureHandle.image = new Image();
textureHandle.image.src = imageFile;
textureHandle.image.onload = function () {
handleLoadedTexture(textureHandle, num)
}
return textureHandle;
}
function draw() {
webgl.clearColor(0.0, 1.0, 0.0, 1.0);
webgl.clear(webgl.COLOR_BUFFER_BIT | webgl.DEPTH_BUFFER_BIT);
webgl.enable(webgl.DEPTH_TEST);
//纹理变动
uniformAnim = webgl.getUniformLocation(webgl.program, "anim");
count = count + 0.01;
webgl.uniform1f(uniformAnim, count);
webgl.activeTexture(webgl.TEXTURE0);
webgl.bindTexture(webgl.TEXTURE_2D, texture0);
webgl.uniform1i(uniformTexture, 0);
webgl.activeTexture(webgl.TEXTURE1);
webgl.bindTexture(webgl.TEXTURE_2D, texture1);
webgl.uniform1i(uniformTexture1, 1);
webgl.drawArrays(webgl.TRIANGLES, 0, 6);
}
(注:在WebGL渲染纹理时,纹理的像素也有要求的,需要为2的幂次方,所以有时用自己的纹理图片去贴图时,明明代码都一样却总出来一个黑色照片,不是代码的问题,是原始数据的问题)