WebGL编程指南二:将顶点的信息从JavaScript程序中传给顶点着色器

目标:

  • WebGL程序可以将顶点的位置坐标和颜色从JavaScript传到着色器中

基本概念

将顶点的信息从JavaScript程序中传给顶点着色器有两种方式:

使用attribute变量

attribute变量是一种GLSL ES变量, 传输与顶点相关的数据。被用来从外部(JavaScript)向顶点着色器代码(GLSL ES)内传输数据,只有顶点着色器能使用它
使用attribute变量需包含以下步骤:
1. 在顶点着色器代码中声明attribute变量;
2. 将attribue变量赋值给gl_Position变量;
3. 在js中获取attribute变量的地址;
4. 向attribute变量传输数据;
eg:

var VSHADER_SOURCE = `
	attribute vec4 a_Positon;
	attribute float a_PointSize;
	void main(){
		gl_Position = a_Position;
		gl_PointSize = a_PointSize;
		...
	}
...
initShaders...
...
var a_Position = gl.getAttribLocation(gl.program,'a_Position');// 向WebGL系统请求变量的存储地址,第一个参数是一个程序对象,它包括了顶点着色器和片元着色器,必须在initShader()之后才能访问到,第二个参数是attribute变量的名称,返回值是attribute变量的存储地址
gl.vertexAttrib3f(a_Position, 0.0,0.0,0.0);

var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
gl.vertexAttrib1f(a_PointSize, 5.0);
...

var a_Position = gl.getAttribLocation(gl.program,‘a_Position’);// 向WebGL系统请求变量的存储地址,第一个参数是一个程序对象,它包括了顶点着色器和片元着色器,必须在initShader()之后才能访问到,第二个参数是attribute变量的名称,返回值是attribute变量的存储地址
关键词attribute被称为存储限定符,它表示接下来的变量(eg中指a_Position)是一个attribute变量。attribute变量必须声明成(GLSL ES中的)全局变量,数据从着色器代码外部传给该变量。

变量声明格式:<存储限定符> <类型> <变量名>
例如:attribute vec4 a_Position;

使用uniform变量

传输那些对于所有顶点都相同(或与顶点无关)的数据
关键词 attribute和uniform都被称为存储限定符,使用方式和步骤基本相同,区别是uniform不是在顶点着色器中声明,而是在片元着色器代码中声明;
eg:

...
var FSHADER_SOURCE = `
	uniform vec4 u_FragColor;
	void main(){
		gl_FragColor = u_FragColor;
		...
	}
...
initShaders...
...
var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');
gl.uniform4f(u_FragColor, 5.0);

使用辅助函数initShaders()在WebGL系统中建立顶点着色器后,WebGL会对着色器进行解析,辨识出着色器具有的attribute变量和uniform变量,每个变量都有一个存储地址,以便通过存储地址向变量传输数据。比如:当你想要向顶点着色器的a_Posiition变量传输数据时,首先需要向WebGL系统请求该变量的存储地址

  • 必须掌握的方法:
  1. gl.getAttribLocation(program,name) 获取由name参数指定的attribute变量的存储地址;
    参数: program 由initShader()生成的包含顶点着色器和片元着色器的着色器程序对象,name 指定要获取其存储地址的attribute变量的名称
    返回值: 大于等于0 attribute变量的存储地址; -1 指定的attribute变量不存在,或者其命名具有gl_或webgl_前缀
  2. gl.getUniformLocation(program,name) 获取由name参数指定的uniform变量的存储地址;
    参数: program 由initShader()生成的包含顶点着色器和片元着色器的着色器程序对象,name 指定要获取其存储地址的uniform变量的名称
    返回值: non-null uniform变量的存储地址; null 指定的attribute变量不存在,或者其命名具有gl_或webgl_前缀
  3. gl.vertexAttrib3f(location,v0,v1,v2) 将数据(v0,v1,v2)传给由location参数指定的attribute变量
    参数: location 将要修改的attribute变量的存储位置; v0,v1,v2 传入变量的三个浮点数类型的值
    返回值: 无
    gl.vertexAttrib3f() 是一系列同族函数中的一个,该系列函数的任务就是从JavaScript向顶点着色器中的attribute变量传值。
    gl.vertexAttrib1f(location,v0)传输1个单精度值(v0),
    gl.vertexAttrib2f(location,v0,v1)传输两个值(v0和v1),
    gl.vertexAttrib4f(location,v0,v1,v2,v3)传输4个值。
    gl.uniform4f()同理

    你也可以使用这些方法的矢量版本,它们的名字以v(vector)结尾,并接受类型化数组作为参数,函数名中的数字表示数组中的元素个数
    如:
    var position = new Float32Array([1,2,3,1]);
    gl.vertexAttrib4fv(a_Position, position)
  4. gl.uniform4f(location,v0,v1,v2,v3) 将数据v0,v1,v2,v3传给由location参数指定的uniform变量
    参数: location 将要修改的uniform变量的存储位置; v0,v1,v2.v3 传入变量的四个浮点数类型的值
    返回值: 无
    同族函数:
    gl.uniform1f(location,v0);
    gl.uniform2f(location,v0,v1);
    gl.uniform3f(location,v0,v1,v2);

    WebGL中函数命名规范:<基础函数名><参数个数><参数类型>。如:gl.vertexAttrib3f()中,基础函数名是vertexAttrib,参数个数是3个,参数类型是f(float,浮点数类型)。另一种参数类型是i,代表整型数。你可以使用gl.vertexAttrib[1234]f() 这样的符号来表示从gl.vertexAttrib1f()到 gl.vertexAttrib4f() 的所有函数

示例程序

// 通过鼠标点击绘点
// 只有顶点着色器可以使用attribute变量,片元着色器的变量有uniform和varying变量
var VSHADER_SOURCE = `
	attribute vec4 a_Position;\n
	void main(){\n
		gl_Position = a_Position;\n
		gl_PointSize = 10.0;\n
	}\n
`
// precision精度限定词指定变量的范围(最大值最小值)和精度
var FSHADER_SOURCE = `
	precision mediump float;\n
	uniform vec4 u_FragColor;\n
	void main(){\n
		gl_FragColor = u_FragColor;\n
	}\n
`
function main () {
	var canvas = document.getElementById('webgl');
	var gl = getWebGLContext(canvas);
	gl.clearColor(0.0,0.0,0.0,1.0);
	if (!initShaders(gl,VSHADER_SOURCE,FSHADER_SOURCE)) {
		return;
	}

	// 获取attribute变量和uniform变量存储地址;
	var a_Position = gl.getAttribLocation(gl.program,'a_Position');
	var u_FragColor = gl.getUniformLocation(gl.program,'u_FragColor');

	// 鼠标点击绘制点
	canvas.onmousedown = function (ev) {
		click(ev,gl,canvas,a_Position,u_FragColor);
	}
	gl.clear(gl.COLOR_BUFFER_BIT);
}
main();
var g_points = [];// 用于存储所有点的位置,每次clear以后都将所有点重绘
var g_colors = [];// 存储所有点的颜色
function click(ev,gl,canvas,a_Position,u_FragColor) {
	var x = ev.clientX;
	var y = ev.clientY;
	// 获取canvas在浏览器客户区的坐标
	var rect = ev.target.getBoundingClientRect();
	// 浏览器坐标转为canvas的坐标再转为WebGL坐标;
	x = ((x - rect.left) - canvas.width / 2) / (canvas.width / 2);
	y = ((canvas.height / 2 - (y - rect.top))) / (canvas.height / 2);

	g_points.push([x, y]);

	if (x >= 0.0 && y >= 0.0) {
		g_colors.push([1.0,0.0,0.0,1.0]); // 第一象限红色
	} else if (x < 0.0 & y < 0.0) {
		g_colors.push([0.0,1.0,0.0,1.0]); // 第三象限绿色
	} else {
		g_colors.push([1.0,1.0,1.0,1.0]); // 白色
	}
	// 用指定的背景色清空canvas
	gl.clear(gl.COLOR_BUFFER_BIT);
	
	// 将所有点都绘制在canvas上
	for (let i = 0, len = g_points.length; i < len; i++) {
		var xy = g_points[i];
		var rgba = g_colors[i];

		// 修改attribute变量的值
		gl.vertexAttrib3f(a_Position,xy[0],xy[1],0.0);
		gl.uniform4f(u_FragColor,rgba[0],rgba[1],rgba[2],rgba[3]);

		gl.drawArrays(gl.POINTS,0,1);
	}
}
实现结果:

鼠标点击哪里就在点击位置绘制一个点,点击位置的象限决定点的颜色
在这里插入图片描述

  • 坐标系的转换

    canvas的坐标系统与WebGL的坐标系统,其原点位置和Y轴的正方向都不一样。在绘制顶点时,需要将坐标从浏览器客户区坐标系转换到canvas坐标系下,然后再转换到WebGL坐标系下。
  1. 用clientX和clientY获取鼠标点击位置:x,y;
  2. 获取canvas在浏览器中的坐标(canvas左上角距离浏览器左上角的坐标):rect.left,rect.top
  3. 使用 (x - rect.left)和(y - rect.top)得到原坐标(x,y)在canvas坐标系下的坐标位置
  4. 将canvas坐标系下的坐标转换到WebGL坐标系统中,为了进行转换,需要知道canvas的中心点。
    中心点坐标是(canvas.width/2,canvas.height/2)。
    然后,就可以用((x - rect.left) - canvas.width/2)和 -((y - rect.top) - canvas.height/2))将canvas的原点平移到WebGL坐标系的原点。
    接下来,由于canvas的x轴坐标区间从0到canvas.width,y轴坐标区间从0到canvas.height,而WebGL中轴的坐标区间为-1.0到1.0,所以最后我们将x坐标除以canvas.width/2,将y坐标除以canvas.height/2,从而将canvas坐标映射到WebGL坐标

代码中为什么要把鼠标每次点击的位置记录下来,而不是仅仅记录最近一次鼠标点击的位置?
因为WebGL使用的是颜色缓冲区。WebGL系统中的绘制操作实际上是在颜色缓冲区中进行绘制的,绘制结束后系统将缓冲区中的内容显示在屏幕上,然后_颜色缓冲区_就会被重置,其中的内容会丢失(这是默认操作),因此,我们必须把每次鼠标点击的位置都记录下来,鼠标每次点击后,程序都重新绘制了所有的点。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值