WebGL – ntzyz's blog https://archive.ntzyz.io Mon, 18 Sep 2017 12:11:28 +0000 zh-CN hourly 1 https://wordpress.org/?v=5.8 [WebGL] 更加灵活的描点实现 https://archive.ntzyz.io/2016/04/10/more-flexible-way-to-draw-a-point/ https://archive.ntzyz.io/2016/04/10/more-flexible-way-to-draw-a-point/#respond Sat, 09 Apr 2016 16:23:47 +0000 https://blog.dimension.moe/?p=412 继续阅读[WebGL] 更加灵活的描点实现]]> p.indent {text-indent: 2em;margin-top: 0.75em; margin-bottom: 0.75em;}

之前的代码实现了描绘一个巨大的、正方形的、红色的点,但是,稍有常识的人都能看出,那个点的颜色、位置、大小,都是无法在运行时改变的,这就比较尴尬了……

毫无疑问,Web 前端的编程语言是 ECMAScript,然而顶点着色器和片元着色器的控制用的却是 GLSL ES。为了能动态的修改着色器代码,我们就需要在着色器代码里添加需要动态编辑的变量,并且通过已有的方法来通过 JavaScript 获取并修改他们的值。

与之前相同,我们需要一个具有 canvas 的网页,其 ID 为 webgl。此处 HTML 代码可以直接参考之前的。同样我们也需要获取 WebGL 上下文,这些代码也是和之前一致的:

	var canvas = document.getElementById('webgl');
	var gl = getWebGLContext(canvas);

	if (!gl) {
		console.log('Error on getting context of WebGL');
		return;
	}

我们需要在着色器代码中添加相应的变量。其中,顶点着色器需要用 attribute 修饰符,是一个四维向量,而片元着色器则是使用的 uniform 修饰符,同样也是一个四维向量。片元着色器里的第一行 precision mediump float; 指明了数据精度:

	var VSHADER_SOURCE =
		'attribute vec4 a_Position;\n' +
		'void main() {\n' +
		'    gl_Position = a_Position;\n' +
		'    gl_PointSize = 10.0;\n' +
		'}\n';
	var FSHADER_SOURCE =
		'precision mediump float;\n' + 
		'uniform vec4 u_FragColor;\n' + 
		'void main() {\n' +
		'    gl_FragColor = u_FragColor;\n' +
		'}\n';

初始化这两个着色器。其实这里的工作不是调用一个函数这么简单。initShaders 函数是我使用的参考书中的库提供的,稍微看了下代码,似乎涉及了 shader 的编译与链接,还有程序对象的生成等内容。按照书本的意思,这些细节都会在后面详细说明,这里我也不去关注他们了。

	if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
		console.log('Error on initialize shaders.');
		return;
	}

嗯……接着就是使用 WebGL 提供的 getAttribLocation 函数和 getUniformLocation 函数,来获得那两个变量的位置。当获得位置出现错误时,函数将返回一个负值:

	var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
	if (a_Position < 0) {
		console.log('Error on getting the storage location of a_Position');
		return;
	}
	var u_FragColor = gl.getUniformLocation(gl.program, 'u_FragColor');
	if (u_FragColor < 0) {
		console.log('Error on getting the storage location of u_FragColor');
		return;
	}

这样下来,我们就可以直接的调用 gl.vertexAttrib3f 方法和 gl.uniform4f 方法,来动态的修改两个着色器的内容。为了体现出动态,做一个点到哪儿画到哪儿的页面,无疑是最简明的了。

因为涉及到了多个点的存储,我们需要新增加一个变量来保存已经单击过的点的位置,在 app.js 最开头增加一行:

	var points = new Array();

然后再写一个函数,来描绘 points 数组内的所有的点。其中,右上、左上、左下、右下的点的颜色依次为红、绿、蓝、白。

	function drawPoints() {
		gl.clearColor(0, 0 , 0, 1);
		gl.clear(gl.COLOR_BUFFER_BIT);
		for (var i = 0; i != points.length; ++i) {
			gl.vertexAttrib3f(a_Position, points[i].x, points[i].y, 0.0);
			if (points[i].x > 0 && points[i].y > 0)
				gl.uniform4f(u_FragColor, 1, 0, 0, 1);
			else if (points[i].x < 0 && points[i].y < 0)
				gl.uniform4f(u_FragColor, 0, 0, 1, 1);
			else if (points[i].x < 0)
				gl.uniform4f(u_FragColor, 0, 1, 0, 1);
			else
				gl.uniform4f(u_FragColor, 1, 1, 1, 1);
			gl.drawArrays(gl.POINT, 0, 1);
		}
	};

点的位置来源可以通过增加一个对 canvas 的 click 事件的监听,将获得的信息转换后 push 到 points 中。这里需要注意的就是 HTML 坐标与 GL 坐标的转换。说起来我还没说 WebGL 的默认坐标系呢(躺

想了想感觉还是没啥好说的,随手糊两张图应该就都能理解了……吧

名稱未設定 1

请叫我灵魂画师

然后就是代码了,理解了坐标系后转换应该也没啥难度了吧 pup

	canvas.onmousedown = function (e) => {
		var mx = e.clientX;
		var my = e.clientY;
		var rect = e.target.getBoundingClientRect();
		
		points.push({
			x: (2 * (mx - rect.left) - canvas.height) / canvas.height,
			y: (canvas.width - 2 * (my - rect.top)) / canvas.width
		});
		drawPoints();
	}

这样我们就做到了一个稍复杂但是更加灵活的描点网页了(

webgl2

]]>
https://archive.ntzyz.io/2016/04/10/more-flexible-way-to-draw-a-point/feed/ 0
[WebGL] 简单的 WebGL 描点实现 https://archive.ntzyz.io/2016/04/07/simple-point-drawing-program-in-webgl/ https://archive.ntzyz.io/2016/04/07/simple-point-drawing-program-in-webgl/#respond Thu, 07 Apr 2016 15:32:10 +0000 https://blog.dimension.moe/?p=391 继续阅读[WebGL] 简单的 WebGL 描点实现]]> p.indent {text-indent: 2em;margin-top: 0.75em; margin-bottom: 0.75em;}

相关库的地址:WebGLBook/lib at master

效果预览见最后。

注意:这只是个人的理解,不保证正确。如有错误欢迎指正!

首先准备具有一个画布(canvas)的页面,所有的 WebGL 程序都会通过这个 canvas 展现出来:



    
        
        Hello Canvas
    
    
        
            你可能是正版IE的受害者。
        
        
        
        
        
        
    

然后就是 app.js,即实现描点的相关代码。通过 DOM 操作,可以很容易地获得 canvas 的 DOM 节点,进而获得 WebGL 的上下文:

    var canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    if (!gl) {
        console.log('Error on getting context of WebGL');
        return;
    }

初始化完毕后,分别准备顶点着色器和片元着色器的GLSL ES代码。值得注意的是,JavaScript 是一个弱类型的语言,但 GLSL ES 不是,所以不能手滑将 0.0 写成 0,否则会出现类型错误。

在每一行 GLSL ES 代码后添加换行符 ‘\n’ 后可以在发生错误时输出行号,降低调试难度。当然,不写也是完全可以的。

    var VSHADER_SOURCE =
        'void main() {' +
        '    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);' +
        '    gl_PointSize = 10.0;' +
        '}';
    var FSHADER_SOURCE =
        'void main() {' +
        '    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);' +
        '}';

继续初始化 shader:

    if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) {
        console.log('Error on initialize shaders.');
        return;
    }

设置画布默认空颜色,并清空画布:

    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

然后,描点:

    gl.drawArrays(gl.POINT, 0, 1);

简单说说我的理解吧。这些代码里获得 GL 上下文没啥好说的,主要的问题就在着色器(Shader)。此处一共出现了两个着色器:顶点着色器、片元着色器。描绘一个点所必须提供的参数就是点的位置与颜色,可以认为这里的顶点着色器指明了点的位置,而片元着色器则指明了其颜色。

顶点着色器代码 VSHADER_SOURCE 中有一个 main 函数,其中存在两个变量。其中 gl_Position 是一个四维向量,由四个浮点分量组成,是一个齐次坐标。其次坐标[x, y, z, w]等价于通常理解的[x/w, y/w, z/w]。使用齐次坐标进行三维处理可以获得更高的效率(?)。此处将 w 分量置为 1.0,即可将齐次坐标直接当成三位笛卡尔坐标使用。第二个变量 gl_PointSize,指明了点的大小。

片元着色器中只存在一个变量:gl_FragColor。它也是一个四维的矢量,其意义分别为红、绿、蓝与透明度的值。与常规的表示方法不同,GL 使用[0, 1]来表示单个颜色的强度,例如[0., 0., 0., 0.]表明了完全透明黑色,而[1., 1., 1., 1.]则表明完全不透明白色。

清空画布后,我们使用了 gl.drawArrays 来描点,其本质就是执行着色器代码。三个参数的意义分别为 描绘类型,从哪个点开始描绘,以及描绘的次数。当此函数被调用时,顶点着色器将会被执行 n 次,顶点着色器代码执行完毕后立即执行片元着色器。全部完成后,就可以看到灰色屏幕中的黑色画布里,描绘了一个红色的点。

顺便这哪里是点啊 这么大明明就是个正方形了嘛好伐)

效果预览~

QQ截图20160408000243

]]>
https://archive.ntzyz.io/2016/04/07/simple-point-drawing-program-in-webgl/feed/ 0