webgl的demo1

717 阅读3分钟

webgl的api地址如下:

www.khronos.org/registry/we… www.khronos.org/registry/we…

着色器语言在网页中的几种写法

1.将着色器语言写在一个单独的文件中,如vtrtex.bns文件。通过获取文件获取文本内容。

着色器语言规范中没有官方扩展名。 OpenGL无法处理从文件加载着色器;您只需将着色器代码作为字符串传递,因此没有特定的文件格式。 但是,Khronos的参考GLSL编译器/验证器glslang使用以下扩展名来确定该文件用于的着色器类型:.vert-顶点着色器   .tesc-镶嵌控件着色器   .tese-镶嵌评估着色器   .geom-几何着色器   .frag-片段着色器   .comp-计算着色器

2.将着色器语言写成字符串赋值给js的变量。

// 顶点着色器程序
var VSHADER_SOURCE = 
  'void main() {\n' +
  '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' + // Set the vertex coordinates of the point
  '  gl_PointSize = 10.0;\n' +                    // Set the point size
  '}\n';

3.通过script标签插入html,然后通过id获取标签内部的文本。

<script id="shader-vs" type="x-shader/x-vertex">
    attribute vec3 v3Position;  //顶点坐标
    attribute vec3 av3Color;    //顶点颜色属性
    varying vec3 vv3Color;      //顶点着色器和片段着色器通讯的变量,注意这里顶点和片段着色器定义的变量名字必须相同
    void main(void){
	 vv3Color = av3Color;
	 gl_Position = vec4(v3Position, 1.0);
    }
</script>

WebGL程序的大致流程

我们常说把大象装进冰箱需要三步,那么写一个WebGL程序应该也只需要三步:

  • 1、把数据放入缓冲区
  • 2、把缓冲区的数据给着色器
  • 3、着色器把数据给GPU。

下面是梳理的一个WebGL程序的大致流程图:

流程图.png demo1

demo1.png

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
		<script>
			var webgl = null;
			function Init(){
				var canvas = document.getElementById('myCanvas');
				webgl = canvas.getContext("webgl");//类似创建绘制的引擎或者初始化
				//设定WebGL的渲染区域(视见区)
				webgl.viewport(0,0,canvas.clientWidth,canvas.clientHeight) 
				//方法用于设置清空颜色缓冲时的颜色值
				webgl.clearColor(0.0,0.0,0.0,1.0);
				//方法使用预设值来清空缓冲
				// gl.COLOR_BUFFER_BIT   //颜色缓冲区
				// gl.DEPTH_BUFFER_BIT   //深度缓冲区
				// gl.STENCIL_BUFFER_BIT  //模板缓冲区
				webgl.clear(webgl.COLOR_BUFFER_BIT);
				// 必须强调 
				// 1. glClearColor只起到Set的作用,并不Clear任何!不要混淆~
				// 2. glClearColor 的作用是,指定刷新颜色缓冲区时所用的颜色。
				// 所以,完成一个刷新过程是要 glClearColor(COLOR) 与 glClear(GL_COLOR_BUFFER_BIT) 配合使用。glClearColor(0.0, 0.0, 1.0, 1.0);//蓝色
				// glClear(GL_COLOR_BUFFER_BIT);
				// 清除颜色缓冲区的作用是,防止缓冲区中原有的颜色信息影响本次绘图(注意!即使认为可以直接覆盖原值,也是有可能会影响的),当绘图区域为整个窗口时,就是通常看到的,颜色缓冲区的清除值就是窗口的背景颜色。所以,这两条清除指令并不是必须的:比如对于静态画面只需要设置一次,比如不需要背景色/背景色为白色。
				// glClear 比手动涂抹一个背景画布效率高且省力,所以通常使用这种方式。
			}
			
			//、、帧缓存区(不只一张)    二维的图片   颜色缓冲区,深度缓冲区,模板缓冲区
		</script>
	</head>
	<body onload='Init()'>
		<canvas id="myCanvas" style="border:1px solid red;" width="600" height="450"></canvas>
	</body>
</html>

demo2

webgl_demo1.gif

WebGL是要与显卡直接交互的,首先是要将shader代码编译:

function initShaders(webgl, vertexShaderId, framentShaderId, bindVariableData) {
	//生成shaderobject 
	var vertexShaderObject = webgl.createShader(webgl.VERTEX_SHADER);
	var fragmentShaderObject = webgl.createShader(webgl.FRAGMENT_SHADER);
	//编译shader
	compileShader(webgl, shaderSourceFromScript(vertexShaderId), shaderSourceFromScript(framentShaderId), vertexShaderObject, fragmentShaderObject);
	//链接shader
	//创建一个程序对象
	programObject = webgl.createProgram();
	//将着色器变量关联到一个属性索引,该操作必须在链接之前进行
	if(!bindVariableData instanceof Array) {
		alert("输入参数有错");
		return;
	}
	for(var i = 0; i < bindVariableData.length; i++) {
		//将通用顶点索引绑定到属性变量
		webgl.bindAttribLocation(programObject, bindVariableData[i][0], bindVariableData[i][1]);
	}
	programObject = linkShader(webgl, programObject, vertexShaderObject, fragmentShaderObject);
	return programObject;
}

//编译shader
//shaderVectexCode shaderFramentCode glsl的字符串
function compileShader(webgl, shaderVectexCode, shaderFramentCode, vertexShaderObj, fragmentShaderObj) {
	//将shader代码装载到shader Object中
	webgl.shaderSource(vertexShaderObj, shaderVectexCode);
	webgl.shaderSource(fragmentShaderObj, shaderFramentCode);
	//编译shader代码
	webgl.compileShader(vertexShaderObj);
	webgl.compileShader(fragmentShaderObj);
	//检查是否编译成功
	if(!webgl.getShaderParameter(vertexShaderObj, webgl.COMPILE_STATUS)) {
		alert("error:vertexShaderObject");
		return;
	}
	if(!webgl.getShaderParameter(fragmentShaderObj, webgl.COMPILE_STATUS)) {
		alert("error:framentShaderObject");
		return;
	}
}

编译两段代码,使其形成能在显卡执行的二进制代码,这两段代码相当于库文件,然后创建一个程序,相当于一个exe,将上面两个shader代码链接起来。再执行链接操作形成一个可以能在显卡上执行的程序。通过programObject句柄关联。

句柄(handle)是C++程序设计中经常提及的一个术语。它并不是一种具体的、固定不变的数据类型或实体,而是代表了程序设计中的一个广义的概念。句柄一般是指获取另一个对象的方法——一个广义的指针,它的具体形式可能是一个整数、一个对象或就是一个真实的指针,而它的目的就是建立起与被访问对象之间的唯一的联系。

运行一个程序需要输入输出,输入是GLSL vertex shader 代码里的vec3 v3Position 和 vec3 vv3Color

webgl.bindAttribLocation(programObject,v3PositionIndex,"v3Position")

将v3PositionIndex 与 v3Position 绑定,v3PositionIndex 为输入入口 现在要将顶点数据传到显卡上去。先在显卡上创建一个缓冲区存储数据,申明缓冲区存储类型,然后将ArrayData数据传到缓冲区上。

triangleBuffer = webgl.createBuffer();//在显卡上创建一个缓冲区
webgl.bindBuffer(webgl.ARRAY_BUFFER,triangleBuffer);//申明缓存区的存储类型为ARRAY_BUFFER
webgl.bufferData(webgl.ARRAY_BUFFER,new Float32Array(ArrayData),webgl.STATIC_DRAW);//给缓存区赋值

接下来就要在显卡运行上面创建的程序了,使用缓冲区(bufferData()),启用v3PositionIndex,告诉显卡程序怎么样使用显卡缓冲区里面的数据(vertexAttribPointer()),然后绘制。

webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
webgl.enableVertexAttribArray(v3PositionIndex);
webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);
webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleColorBuffer);
webgl.enableVertexAttribArray(v3ColorIndex);
webgl.vertexAttribPointer(v3ColorIndex, 3, webgl.FLOAT, false, 0, 0);
//绘制数据
webgl.drawArrays(webgl.TRIANGLES, 0, 3);

完整代码

<html>
	<head>
		<!-- 
		 1、从canvas元素中获取webgl context
		 2、利用GLSL ES语言,编写顶点着色器和片元着色器,并成对应的着色器程序
		 3、准备好你想要绘制的图像的顶点数据,并写入缓冲区
		 4、把着色器中的变量与载有顶点数据的缓冲区对应起来
		 5、最后执行着色器程序,并在canvas上绘制出图形
		-->
		<script id="shader-vs" type="x-shader/x-vertex">
			attribute vec3 v3Position;  //顶点坐标
			attribute vec3 av3Color;    //顶点颜色属性
			varying vec3 vv3Color;      //顶点着色器和片段着色器通讯的变量,注意这里顶点和片段着色器定义的变量名字必须相同
			void main(void){
				vv3Color = av3Color;
				gl_Position = vec4(v3Position, 1.0);
			}
                </script>

		<script id="shader-fs" type="x-shader/x-fragment">
			#ifdef GL_FRAGMENT_PRECISION_HIGH
			precision highp float;
			#else
			precision mediump float;
                        #endif
                        varying vec3 vv3Color;
			void main(void){
				 gl_FragColor = vec4(vv3Color, 1.0);
			}
                </script>

		<script>
			
			var webgl = null;
			var vertexShaderObject = null;
			var fragmentShaderObject = null;
			var programObject = null;
			var triangleBuffer = null;
			var triangleColorBuffer = null;
			
			var v3PositionIndex = 0;
			var v3ColorIndex = 1;
			var r = 1.0;
			var g = 1.0;
			var b = 1.0;
			var isDesc = false;
			
			
			//初始化代码
			function init() {
				//初始化webgl渲染区域
				webgl = initCanvas("webglCanvas");
				//初始化shader程序
				var bind1 = [v3PositionIndex, "v3Position"];
				var bind2 = [v3ColorIndex, "av3Color"];  //av3Color变量名
				var bindData = new Array();
				bindData.push(bind1);
				bindData.push(bind2);
				//初始化Shader程序,返回链接好的程序对象
				var programObject = initShaders(webgl, "shader-vs", "shader-fs", bindData);
				
				//将定义好的WebGLProgram 对象添加到当前的渲染状态中。
				webgl.useProgram(programObject);    
				//变色
				window.setInterval("changeColor()", 10);
				//初始化顶点数据
				triangleBuffer = initVextexData(webgl);
			}
			//初始化Shader程序,返回链接好的程序对象
			function initShaders(webgl, vertexShaderId, framentShaderId, bindVariableData) {
				//生成shaderobject 
				var vertexShaderObject = webgl.createShader(webgl.VERTEX_SHADER);
				var fragmentShaderObject = webgl.createShader(webgl.FRAGMENT_SHADER);
				//编译shader
				compileShader(webgl, shaderSourceFromScript(vertexShaderId), shaderSourceFromScript(framentShaderId),vertexShaderObject, fragmentShaderObject);
			
				//链接shader
				//创建一个程序对象
				programObject = webgl.createProgram();
				//将着色器变量关联到一个属性索引,该操作必须在链接之前进行
				if (!bindVariableData instanceof Array) {
					alert("输入参数有错");
					return;
				}
				for (var i = 0; i < bindVariableData.length; i++) {
					//将通用顶点索引绑定到属性变量
					//console.log(programObject, bindVariableData[i][0], bindVariableData[i][1])
					webgl.bindAttribLocation(programObject, bindVariableData[i][0], bindVariableData[i][1]);
				}
				programObject = linkShader(webgl, programObject, vertexShaderObject, fragmentShaderObject);
				return programObject;
			}
			
			//编译shader
			//shaderVectexCode shaderFramentCode glsl的字符串
			function compileShader(webgl, shaderVectexCode, shaderFramentCode, vertexShaderObj, fragmentShaderObj) {
				//将shader代码装载到shader Object中
				webgl.shaderSource(vertexShaderObj, shaderVectexCode);
				webgl.shaderSource(fragmentShaderObj, shaderFramentCode);
				//编译shader代码
				webgl.compileShader(vertexShaderObj);
				webgl.compileShader(fragmentShaderObj);
				//检查是否编译成功
				if (!webgl.getShaderParameter(vertexShaderObj, webgl.COMPILE_STATUS)) {
					alert("error:vertexShaderObject");
					return;
				}
				if (!webgl.getShaderParameter(fragmentShaderObj, webgl.COMPILE_STATUS)) {
					alert("error:framentShaderObject");
					return;
				}
			}				
			
			//顶点数据
			function initVextexData(webgl) {
				//顶点坐标
				var jsArrayData = [
					0.0, 1.0, 0.0,
					-1.0, 0.0, -1.0,
					1.0, 0.0, 0.0
				]
				//创建一个webgl能够访问的缓冲
				var triangleBuffer = webgl.createBuffer();
				//绑定buffer
				webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
				//将js数据拷贝到buffer上
				webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayData), webgl.STATIC_DRAW);
				//颜色数据
				var jsArrayColor = [
					1.0, 0.0, 0.0, //上顶点
					0.0, 1.0, 0.0, //左顶点
					0.0, 0.0, 1.0 //右顶点
				];
				//创建颜色缓冲,将颜色数据拷贝进颜色缓冲
				triangleColorBuffer = webgl.createBuffer();
				webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleColorBuffer);
				webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayColor), webgl.STATIC_DRAW);
				return triangleBuffer;
			}
			//渲染场景
			function renderScene() {
				//开始绘制
				//清空屏幕
				webgl.clearColor(0.0, 0.0, 0.0, 1.0);
				webgl.clear(webgl.COLOR_BUFFER_BIT);
				//webgl中顶点数组数据可能N个,我们这里需要告诉webgl我们用哪一个,
				
				//使用缓冲区
				//绑定一个顶点数组数据   
				webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleBuffer);
				
				//用以说明 enableVertexArray() 是如何激活顶点属性,并将顶点数据传输到shader函数的。
				//启动关联索引上的数据  //可以打开属性数组列表中指定索引处的通用顶点属性数组
				webgl.enableVertexAttribArray(v3PositionIndex);
				//指定关联索引上的数据元素或者元素数据的正确信息                      变量间隔
				webgl.vertexAttribPointer(v3PositionIndex, 3, webgl.FLOAT, false, 0, 0);

				//绑定颜色buffer
				webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleColorBuffer);
				webgl.enableVertexAttribArray(v3ColorIndex);
				webgl.vertexAttribPointer(v3ColorIndex, 3, webgl.FLOAT, false, 0, 0);
				
		
				//https://blog.csdn.net/ithanmang/article/details/89520623
				//绘制数据
				webgl.drawArrays(webgl.TRIANGLES, 0, 3);
			}

	
			//不断改变rgb值,实现变色的动画
			function changeColor() {  //isDesc默认fasle
				if (isDesc && r <= 0.1){
					isDesc = false;
				}
				if (!isDesc && r >= 1.0){
					isDesc = true;
				}
				!isDesc ? r += 0.01 : r -= 0.01;
				!isDesc ? g += 0.01 : g -= 0.01;
				!isDesc ? b += 0.01 : b -= 0.01;
				var jsArrayColor = [//数据 ——》显卡
					r, 0.0, 0.0, //上顶点
					0.0, g, 0.0, //左顶点
					0.0, 0.0, b //右顶点
				];
				//内存  显卡
				triangleColorBuffer = webgl.createBuffer();  //一个用于储存顶点数据或着色数据的WebGLBuffer对象
				webgl.bindBuffer(webgl.ARRAY_BUFFER, triangleColorBuffer);//将给定的WebGLBuffer绑定到目标   存储的类型,顶点类型
				//赋值   //webgl.STATIC_DRAW 静态还是动态,是不是经常改
				webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(jsArrayColor), webgl.STATIC_DRAW);
				renderScene();
			}

			//初始化canvas
			function initCanvas(canvasId) {
				//获取绘制上下文
				var myCanvasObject = document.getElementById(canvasId);
				var context = null;
				try {
					context = myCanvasObject.getContext("experimental-webgl");
					//设置视口
					context.viewport(0, 0, myCanvasObject.width, myCanvasObject.height);
				} catch (ex) {
					alert(ex.toString());
				}
				if (!context) {
					alert("我靠, 你的浏览器不支持WebGL,换个浏览器吧");
					return null;
				}
				return context;
			}
			//解析Shader代码
			function shaderSourceFromScript(scriptID) {
				var shaderScript = document.getElementById(scriptID);
				if (shaderScript == null) return "";
				var sourceCode = "";
				var child = shaderScript.firstChild;
				while (child) {
					if (child.nodeType == child.TEXT_NODE) sourceCode += child.textContent;
					child = child.nextSibling;
				}
				return sourceCode;
			}

			//链接Shader程序
			function linkShader(webgl, programObj, vertexShaderObj, fragmentShaderObj) {
				//一个程序对象只能并且必须附带一个顶点着色器和片段着色器
				webgl.attachShader(programObj, vertexShaderObj);
				webgl.attachShader(programObj, fragmentShaderObj);
				//将着色器变量关联到一个属性索引
				// webgl.bindAttribLocation(programObj, v3PositionIndex, "v3Position");
				webgl.linkProgram(programObj);
				//检查是否链接成功
				if (!webgl.getProgramParameter(programObj, webgl.LINK_STATUS)) {
					alert("error:ProgramObject");
					return;
				}
				return programObj;
			}
			
		</script>
	</head>


	<body onload="init()">
		<canvas id="webglCanvas" style="border:1px solid red" width="600" height="600"></canvas>
	</body>
</html>