WebGL学习笔记02
上一篇笔记,我梳理了自己对WebGL的一些观点,这一篇笔记开始梳理我所学习到的知识。
因为这是一篇学习笔记,所以我不会很系统很详细的一一介绍技术点和API参数,只会对知识做一些归纳终结和坑点介绍,所以系统的学习还是得靠书籍,我学习的时候看的是《WebGL Programming Guide》,可以在网上找到英文版pdf,也可以在亚马逊上找到中文版,我极力推荐这本书,因为它图文结合,让人直观的理解很多复杂的概念。
好了,接下来就进入主题。
我在刚接触到WebGL,制作第一个demo的时候,我学习到以下几个概念:
- Canvas
- WebGL Context
- Vertex Shader
- Fragment Shader
- Program
Canvas是HTML5加入的新标签,可以用来做2D图形绘制,用起来有点像用GDI+自己绘图。
在使用WebGL相关的API之前,先得从一个Canvas上获得WebGL Context,然后才能开始使用WebGL相关接口。
WebGL Context的获取过程是一个段比较通用的代码,可以封装成一个函数,代码在文章最后给出。
在刚开始学习WebGL相关API的时候,我拿出之前买的几本OpenGL书(买来都没有看)想作为参照,发现API差别很大。后来经过了解,才知道老版本的OpenGL是命令式的编程接口,新版本的OpenGL是基于Shader的编程接口,两者风格差异很大。
简单来讲,命令式的编程接口就是你让它画什么它就画什么,比如你让它画个点它就画个点,OpenGL内部知道如何去画一个点。而基于Shader的编程接口则是你教它怎么画它就怎么画,Shader编程就是你教OpenGL怎么画的过程。基于Shader的接口设计,让调用者能获得更高的灵活性,也可以获得更好的性能。
在OpenGL中,把Shader分为两类,Vertex Shader和Fragment Shader,我以下简称VShader和FShader。VShader的作用是对所有顶点进行计算和转换,能实现坐标移动和旋转等操作。FShader则是对光栅化之后的图形碎片进行计算和转换,通常是颜色转换,能实现光照阴影等效果。
VShader和FShader必须配套使用,一对VShader和FShader组成一个Program,OpenGL每次渲染的时候只能使用一个Program,但是一个程序可以同时有多个Program。
VShader看起来像这样:
void main() {\n' +
gl_Position = vec4(0.0, 0.0, 0.0, 1.0); // Set the vertex coordinates of the point
gl_PointSize = 10.0; // Set the point size
}
VShader的以顶点为单位执行的,传给WebGL的每个顶点都会放到VShader执行一次计算,所以顶点数量的多少会影响到程序性能,VShader计算的复杂度也会影响到程序性能。
FShader看起来像这样:
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Set the point color
}
FShader的执行是以光栅化之后的碎片为单位执行的,我们传递给WebGL的顶点数据是一组矢量数据,没有具体到屏幕像素,WebGL的主要工作就是把这些矢量数据进行光栅化,转换成屏幕上的一个个像素,FShader则用来让WebGL知道各个像素的颜色值。
VShader和FShader用的是一种专有的编程语言,叫做OpenGL Shading Language,简称GLSL。因为它们是一种编程语言,所以需要先编译链接成Program后才能使用。
WebGL Context的获取,以及Shader的编译和Program的链接,都是比较通用的过程,做实验的时候我把它们封装成一个一组工具函数:
var GLUtil = function() {
var self = {};
var loadShader = function(gl, type, source) {
// Create shader object
var shader = gl.createShader(type);
if (shader == null) {
console.log('unable to create shader');
return null;
}
// Set the shader program
gl.shaderSource(shader, source);
// Compile the shader
gl.compileShader(shader);
// Check the result of compilation
var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (!compiled) {
var error = gl.getShaderInfoLog(shader);
console.log('Failed to compile shader: ' + error);
gl.deleteShader(shader);
return null;
}
return shader;
}
self.GetContext = function(canvas, opt_attribs) {
var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
var context = null;
for (var ii = 0; ii < names.length; ++ii) {
try {
context = canvas.getContext(names[ii], opt_attribs);
} catch(e) {}
if (context) {
break;
}
}
return context;
}
self.CreateProgram = function(gl, vshader, fshader) {
// Create shader object
var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader);
var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader);
if (!vertexShader || !fragmentShader) {
console.log('Failed to create program for WebGL');
return null;
}
// Create a program object
var program = gl.createProgram();
if (!program) {
return null;
}
// Attach the shader objects
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
// Link the program object
gl.linkProgram(program);
// Check the result of linking
var linked = gl.getProgramParameter(program, gl.LINK_STATUS);
if (!linked) {
var error = gl.getProgramInfoLog(program);
console.log('Failed to link program: ' + error);
gl.deleteProgram(program);
gl.deleteShader(fragmentShader);
gl.deleteShader(vertexShader);
return null;
}
return program;
}
return self;
}();
我做的第一个实验是绘制一个红点,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebGL Labs 01 - Point</title>
<script src="GLUtil.js"></script>
<script type="text/javascript">
// Vertex shader program
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';
// Fragment shader program
var FSHADER_SOURCE =
'void main() {\n' +
' gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' + // Set the point color
'}\n';
function main() {
// Retrieve <canvas> element
var canvas = document.getElementById('webgl');
// Get the rendering context for WebGL
var gl = GLUtil.GetContext(canvas);
if (!gl) {
console.log('Failed to get the rendering context for WebGL');
return;
}
// Create shader program.
var program = GLUtil.CreateProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
if (!program) {
console.log('Failed to create program for WebGL');
return;
}
gl.useProgram(program);
// Specify the color for clearing <canvas>
gl.clearColor(0.0, 0.0, 0.0, 1.0);
// Clear <canvas>
gl.clear(gl.COLOR_BUFFER_BIT);
// Draw a point
gl.drawArrays(gl.POINTS, 0, 1);
}
</script>
</head>
<body onload="main()">
<center style="margin-top:100px">
<canvas id="webgl" width="400" height="400">
Please use a browser that supports "canvas"
</canvas>
</center>
</body>
</html>