我的webgl学习之路(二) 画一个点
我的webgl学习之路(二) 画一个点
想学webgl那么必须知道shader着色器,这是webgl最核心的部分,也是最难的部分;shader是一系列的指令,它会对屏幕的每个像素同时下达命名,也就是说,你要根据屏幕的每个位置(也就是每个像素)执行不同的操作;为什么shader运行特别快?上节我已经大概的讲过了(它是并行处理);
在这里不得不介绍一下GLSL,GLSL是什么?
GLSL全称是 openGL Shading Language,openGL 着色语言,它们是在图形卡的GPU (Graphic Processor Unit图形处理单元)上执行的,代替了固定的渲染管线的一部分,使渲染管线中不同层次具有可编程性;它的主要目的是对图形图像顶点和片段像素的渲染;
着色器中的两种着色器:
顶点着色器(vertexshader):它是用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点;
片元着色器(fragmentshader):进行逐片元处理过程,就是对像素颜色处理;一个像素也就是一个单元;
Webgl的执行的整个过程(我就引用webgl编程指南一书中的图,其实要多看看其它的资料,每个人的思维是不一样的,并不能完全的帮人理解,我就是这样的人,不同的资料有着不同的理解):
页面加载执行JavaScript程序,然后执行webgl相关的方法,先执行顶点着色器,然后执行片元着色器,把最后的结果给缓冲区,让它渲染到屏幕中,这就是整个过程;
绘制一个点的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>绘制点</title>
<script id="vs" type="x-shader/x-vertex">
void main(void){
gl_PointSize = 20.0;
gl_Position =vec4(0.0,0.0,0.0,1.0);
}
</script>
<script id="fs" type="x-shader/x-fragment">
void main(void){
gl_FragColor =vec4(1.0,0.0,0.0,1.0);
}
</script>
</head>
<body>
<canvas id="canvas" style="background-color:black"></canvas>
<script>
onload =function () {
var canvas = document.getElementById("canvas");
canvas.width = 500;
canvas.height = 500;
var gl = canvas.getContext("webgl");
/*清空画板上的颜色,并初始化颜色*/
gl.clearColor(0.0, 0.0, 0.0, 1.0);
//设定canvas初始化时候的深度
gl.clearDepth(1.0);
//清空画面上的颜色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//顶点着色器和片段着色器生成
var v_shader= create_shader("vs");
var f_shader = create_shader("fs");
// 程序对象的生成和连接
var program= create_program(v_shader,f_shader);
//开始绘制,显示器显示结果
gl.drawArrays(gl.POINT, 0, 1);
function create_program(v_shader,f_shader) {
varprogram = gl.createProgram();
gl.attachShader(program, v_shader);
gl.attachShader(program, f_shader);
gl.linkProgram(program);
gl.useProgram(program);
returnprogram;
}
functioncreate_shader(id) {
//用来保存着色器的变量
var shader;
//根据id从HTML中获取指定的script标签
var scriptElement = document.getElementById(id);
//如果指定的script标签不存在,则返回
if (!scriptElement) {
return;
}
//判断script标签的type属性
switch (scriptElement.type){
// 顶点着色器的时候
case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER);//生成顶点着色器
break;
// 片段着色器的时候
case 'x-shader/x-fragment':
shader = gl.createShader(gl.FRAGMENT_SHADER);//生成片元着色器
break;
default:
return;
}
//将标签中的代码分配给生成的着色器
gl.shaderSource(shader, scriptElement.text);
//编译着色器
gl.compileShader(shader);
//判断一下着色器是否编译成功
if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)){
// 编译成功,则返回着色器
return shader;
} else{
// 编译失败,弹出错误消息
alert(gl.getShaderInfoLog(shader));
}
}
function create_program(vs,fs) {
//程序对象的生成
var program = gl.createProgram();
//向程序对象里分配着色器
gl.attachShader(program, vs);
gl.attachShader(program, fs);
//将着色器连接
gl.linkProgram(program);
//判断着色器的连接是否成功
if (gl.getProgramParameter(program,gl.LINK_STATUS)) {
// 成功的话,将程序对象设置为有效
gl.useProgram(program);
// 返回程序对象
return program;
} else {
// 如果失败,弹出错误信息
alert(gl.getProgramInfoLog(program));
}
}
}
</script>
</body>
</html>
绘制一个点的流程:
1、获取webgl的上下文;
2、创建shader,并获取;
3、获取程序对象,并把着色器放入程序对象中,执行;
4、绘制;
顶点着色器的代码如下:
<script id="vs" type="x-shader/x-vertex">
void main(void){
gl_PointSize = 20.0;
gl_Position = vec4(0.0,0.0,0.0,1.0);
}
</script>
片元着色器代码如下:
<script id="fs" type="x-shader/x-fragment">
void main(void){
gl_FragColor =vec4(1.0,0.0,0.0,1.0);
}
</script>
type="x-shader/x-vertex" 和type="x-shader/x-fragment" 不是js内部属性;所以js执行的时候回直接省略过去;在生成着色器的时候只是把字符串赋值给着色器;所以你会看到有人直接用字符串拼接着色器;
着色器代码是给gup执行的,它使用的是类C语言;着色器里必须有main函数;并且无返回值类型;gl_* 是内部命名方式;gl_PointSize 是内部定义好的变量;gl_PointSize = 20.0表示这个点的大小是20.0,float类型(注意别搞错了,在着色器内部可不会转型,它是会报错的);gl_Position 也是内部属性,表示的是点位置及透明度;前面三个是颜色,后面一个是透明度,rgba模式;范围是0到1;gl_FragColor内部定义属性,表示片元颜色;前三个表示颜色,最后一个表示透明度;
create_shader()方法是获取着色器过程;因为它们的过程都是一样的,所有封装在一起;
Vec4 参数是(float,float,float,float,float)里面存放四个float类型的参数;
gl.drawArrays(gl.POINT,0,1); 参数一:是绘制模式;参数二:表示从哪个点开始绘制;参数三:表示绘制几个点;
drawArrays会告诉GPU开始把设置好的顶点绘制出来,顶点从 gl_Position =vec4(0.0,0.0,0.0,1.0);设置的空间坐标,到显示在显示器上的像素,经历一个比较复杂的过程,也就是渲染管线,过去是固定管线,现在支持可编程;
上面的代码你会发现你想要不同位置的点的时候做不到;那么怎么得到它不同位置的点呢?
修改顶点着色里的gl_Position = vec4(0.0,0.0,0.0,1.0);把vec4里的前三个数,范围是-1到1之间;比如gl_Position = vec4(-1.0,0.0,0.0,1.0);你会发现点的位置改变了;为什么它们的范围是-1到1;
Webgl 的坐标要对应到Canvas的坐标;
Webgl的中心位置(0.0,0.0,0.0)在canvas中心点位置(canvas.width/2,canvas.height/2);
Webgl的上边缘和下边缘(-1.0,0.0,0.0)和(1.0,0.0,0.)在canvas下边缘,canvas上边缘;
Webgl的左边缘和右边缘(0.0,-1.0,0.0)和(0.0,1.0,0.0)和canvas左边缘和右边缘;
如图:(webgl使用的是右手坐标系)
那么,怎么在外边改变点的位置呢?
就要用到 attribute 这个变量,attribute变量传输的是那些与顶点相关的数据;
使用attribute变量有以下步骤:
1、在顶点着色器中声明attribute变量;
2、将attribute变量赋值给gl_Position变量;
3、向attribute变量传输数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在外边改变点的位置</title>
<script id="vs" type="x-shader/x-vertex">
attribute vec4 a_Position;
void main(void){
gl_PointSize = 20.0;
gl_Position = a_Position;
}
</script>
<script id="fs" type="x-shader/x-fragment">
void main(void){
gl_FragColor =vec4(1.0,0.0,0.0,1.0);
}
</script>
</head>
<body>
<canvas id="canvas" style="background-color:black"></canvas>
<script>
onload =function () {
var canvas = document.getElementById("canvas");
canvas.width = 500;
canvas.height = 500;
var gl = canvas.getContext("webgl");
/*清空画板上的颜色,并初始化颜色*/
gl.clearColor(0.0, 0.0, 0.0, 1.0);
//设定canvas初始化时候的深度
gl.clearDepth(1.0);
//清空画面上的颜色
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
//顶点着色器和片段着色器生成
var v_shader= create_shader("vs");
var f_shader = create_shader("fs");
// 程序对象的生成和连接
var program= create_program(v_shader,f_shader);
//获取a_Position变量的存储位置
var a_Position= gl.getAttribLocation(program,"a_Position");
//将顶点位置传递给a_Position变量;
gl.vertexAttrib3f(a_Position,1.0,0.0,0.0);//后面的3f表示传3个float变量;数量的范围是1-4;不够会自动补上0.0,透明度(最后一个1.0补上)
/* gl.clearColor(0.0,0.0,0.0,1.0);
gl.clear(gl.COLOR_BUTTER_BIT);*/
//开始绘制,显示器显示结果
gl.drawArrays(gl.POINT, 0, 1);
functioncreate_program(v_shader, f_shader) {
varprogram = gl.createProgram();
gl.attachShader(program, v_shader);
gl.attachShader(program, f_shader);
gl.linkProgram(program);
gl.useProgram(program);
returnprogram;
}
functioncreate_shader(id) {
//用来保存着色器的变量
var shader;
//根据id从HTML中获取指定的script标签
var scriptElement = document.getElementById(id);
//如果指定的script标签不存在,则返回
if (!scriptElement) {
return;
}
//判断script标签的type属性
switch (scriptElement.type){
// 顶点着色器的时候
case 'x-shader/x-vertex':
shader = gl.createShader(gl.VERTEX_SHADER);//生成顶点着色器
break;
// 片段着色器的时候
case 'x-shader/x-fragment':
shader = gl.createShader(gl.FRAGMENT_SHADER);//生成片元着色器
break;
default :
return;
}
//将标签中的代码分配给生成的着色器
gl.shaderSource(shader, scriptElement.text);
//编译着色器
gl.compileShader(shader);
//判断一下着色器是否编译成功
if (gl.getShaderParameter(shader,gl.COMPILE_STATUS)) {
// 编译成功,则返回着色器
return shader;
} else{
// 编译失败,弹出错误消息
alert(gl.getShaderInfoLog(shader));
}
}
functioncreate_program(vs, fs) {
//程序对象的生成
var program = gl.createProgram();
//向程序对象里分配着色器
gl.attachShader(program, vs);
gl.attachShader(program, fs);
//将着色器连接
gl.linkProgram(program);
//判断着色器的连接是否成功
if (gl.getProgramParameter(program,gl.LINK_STATUS)) {
// 成功的话,将程序对象设置为有效
gl.useProgram(program);
// 返回程序对象
return program;
} else{
// 如果失败,弹出错误信息
alert(gl.getProgramInfoLog(program));
}
}
}
</script>
</body>
</html>
Attribute vec4 a_Position; //attribute被称为存储限定符, vec4 表示类型,a_Position表示变量名;这就是着色器的声明方式;