webgl开始使用webgl


备注

WebGL是一种光栅化API,通常在您的GPU上运行,使您能够快速绘制2D和3D图形。 WebGL还可用于对数据数组进行计算。

WebGL是一个非常低级的API。在基础级别,WebGL是一个在GPU上运行2个用户提供的功能的引擎。一个函数称为顶点着色器 。顶点着色器的工作是计算顶点位置。根据函数输出WebGL的位置,然后可以栅格化各种基元,包括点,线或三角形。当栅格化这些基元时,它调用第二个用户提供的函数,称为片段着色器 。片段着色器的工作是为当前正在绘制的图元的每个像素计算颜色。

这些函数是用一种名为GLSL的语言编写的,它有点像C / C ++并且是严格类型的。

由程序员提供这些功能,使WebGL绘制2d,3d或计算某些东西。几乎每一个WebGL都是关于设置这两个函数然后向它们提供数据。

数据可以从4个来源提供。

  • 制服

制服是着色器函数的输入,非常类似于函数参数或全局变量。它们在着色器执行之前设置一次,并在执行期间保持不变

  • 属性

属性仅向顶点着色器提供数据。属性定义了如何从缓冲区中提取数据。例如,您可以将位置,法线和纹理坐标放入缓冲区。通过属性,您可以告诉WebGL如何从缓冲区中提取数据并将其提供给顶点着色器。通过调用gl.drawArraysgl.drawElements并指定计数,顶点着色器被称为用户指定的次数。每次调用当前顶点着色器时,下一组数据将从用户指定的缓冲区中提取并放入属性中

  • 纹理

纹理是4个通道的2D数据阵列。最常见的是,这4个通道是图像中的红色,绿色,蓝色和alpha。 WebGL并不关心数据是什么。与属性和缓冲区不同,着色器可以通过随机访问从纹理中读取值。

  • Varyings

变量是顶点着色器将数据传递到片段着色器的一种方式。当使用片段着色器对基元进行光栅化时,在顶点着色器输出的值之间插入变量

你好,世界

就像它在备注部分提到的那样,我们需要提供两个功能。顶点着色器和片段着色器

让我们从顶点着色器开始

// an attribute will receive data from a buffer
attribute vec4 position;

// all shaders have a main function
void main() {

  // gl_Position is a special variable a vertex shader 
  // is responsible for setting
  gl_Position = position;
}
 

如果整个事情是用JavaScript而不是GLSL编写的,你可以想象它会像这样使用

// *** PSUEDO CODE!! ***

var positionBuffer = [
  0, 0, 0, 0,
  0, 0.5, 0, 0,
  0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;

drawArrays(..., offset, count) {
  for (var i = 0; i < count; ++i) {
     // copy the next 4 values from positionBuffer to the position attribute
     attributes.position = positionBuffer.slice((offset + i) * 4, 4); 
     runVertexShader();
     ...
     doSomethingWith_gl_Position();
}
 

接下来我们需要一个片段着色器

// fragment shaders don't have a default precision so we need
// to pick one. mediump, short for medium precision, is a good default.
precision mediump float;

void main() {
  // gl_FragColor is a special variable a fragment shader
  // is responsible for setting
  gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple 
}
 

上面我们设置gl_FragColor1, 0, 0.5, 1 为1红,0绿色,0.5蓝色,1阿尔法。 WebGL中的颜色从0到1。

现在我们已经编写了2个函数,让我们开始使用WebGL

首先,我们需要一个HTML canvas元素

 <canvas id="c"></canvas>
 

然后在JavaScript中我们可以看一下

 var canvas = document.getElementById("c");
 

现在我们可以创建一个WebGLRenderingContext

 var gl = canvas.getContext("webgl");
 if (!gl) {
    // no webgl for you!
    ...
 

现在我们需要编译这些着色器以将它们放在GPU上,所以首先我们需要将它们变成字符串。您可以以正常的方式获取字符串。通过使用AJAX连接,将它们放在非JavaScript类型的脚本标记中,或者在这种情况下使用多行模板文字

var vertexShaderSource = `
// an attribute will receive data from a buffer
attribute vec4 position;

// all shaders have a main function
void main() {

  // gl_Position is a special variable a vertex shader 
  // is responsible for setting
  gl_Position = position;
}
`;

var fragmentShaderSource = `
// fragment shaders don't have a default precision so we need
// to pick one. mediump is a good default
precision mediump float;

void main() {
  // gl_FragColor is a special variable a fragment shader
  // is responsible for setting
  gl_FragColor = vec4(1, 0, 0.5, 1); // return redish-purple 
}
`;
 

然后我们需要一个函数来创建着色器,上传源并编译着色器

function createShader(gl, type, source) {
  var shader = gl.createShader(type);
  gl.shaderSource(shader, source);  
  gl.compileShader(shader);
  var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }

  console.log(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}
 

我们现在可以调用该函数来创建2个着色器

var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
 

然后,我们需要将这两个着色器链接到一个程序中

function createProgram(gl, vertexShader, fragmentShader) {
  var program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);
  var sucesss = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (success) {
    return program;
  }

  console.log(gl.getProgramInfoLog(program));
  gl.deleteProgram(program);
}
 

并称之为

var program = createProgram(gl, vertexShader, fragmentShader);
 

现在我们已经在GPU上创建了一个GLSL程序,我们需要为它提供数据。大多数WebGL API都是关于设置状态以向我们的GLSL程序提供数据。在这种情况下,我们对GLSL程序的唯一输入是position ,它是一个属性。我们应该做的第一件事是查找我们刚刚创建的程序的属性位置

var positionAttributeLocation = gl.getAttribLocation(program, "position");
 

属性从缓冲区获取数据,因此我们需要创建缓冲区

var positionBuffer = gl.createBuffer();
 

WebGL允许我们在全局绑定点上操作许多WebGL资源。您可以将绑定点视为WebGL内部的全局变量。首先,将绑定点设置为资源。然后,所有其他函数通过绑定点引用资源。所以,让我们绑定位置缓冲区。

gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
 

现在我们可以通过绑定点引用数据将数据放入该缓冲区

// three 2d points
var positions = [
  0, 0, 
  0, 0.5,
  0.7, 0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
 

这里有很多事情要做。首先,我们有一个JavaScript数组的positions 。另一方面,WebGL需要强类型数据,因此new Float32Array(positions)new Float32Array(positions) 创建一个32位浮点数的新数组,并从positions 复制值。然后gl.bufferData 将该数据复制到GPU上的positionBuffer 。它使用位置缓冲区,因为我们将它绑定到上面的ARRAY_BUFFER 绑定点。

最后一个参数gl.STATIC_DRAW 是WebGL关于我们如何使用数据的提示。它可以尝试使用该信息来优化某些事物。 gl.STATIC_DRAW 告诉WebGL我们不太可能更改这些数据。

既然我们已将数据放入缓冲区,我们需要告诉属性如何从中获取数据。首先,我们需要打开属性

gl.enableVertexAttribArray(positionAttributeLocation);
 

然后我们需要指定如何拉出数据

var size = 2;          // 2 components per iteration
var type = gl.FLOAT;   // the data is 32bit floats
var normalize = false; // use the data as is
var stride = 0;        // 0 = move size * sizeof(type) each iteration
var offset = 0;        // start at the beginning of the buffer
gl.vertexAttribPointer(
   positionAttributeLocation, size, type, normalize, stride, offset)
 

gl.vertexAttribPointer 一个隐藏部分是它将当前的ARRAY_BUFFER 绑定到属性。换句话说,现在这个属性绑定到positionBuffer 我们可以自由地将其他东西绑定到ARRAY_BUFFER 绑定点。

请注意,从我们的GLSL顶点着色器的角度来看,position属性是vec4

attribute vec4 position;
 

vec4 是4浮点值。在JavaScript中你可以想到它像position = {x: 0, y: 0, z: 0, w: 0} 。上面我们设置size = 2 。属性默认为0, 0, 0, 1 因此该属性将从缓冲区中获取其前2个值(x和y)。 z和w分别是默认值0和1。

毕竟我们终于可以要求WebGL执行GLSL程序了。

var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 3;
gl.drawArrays(primitiveType, offset, count);
 

这将执行我们的顶点着色器3次。我们的顶点着色器中第一次position.xposition.y 将被设置为position.y 中的前2个值。第二次将position.xy 设置为第二个2值。最后一次将其设置为最后2个值。

因为我们将primitiveType 设置为gl.TRIANGLES ,所以每次我们的顶点着色器运行3次时,WebGL将根据我们设置gl_Position 的3个值绘制一个三角形。无论我们的画布大小是多少,这些值都在剪辑空间坐标中,每个方向从-1到1。

因为我们的顶点着色器只是将positionBuffer值复制到gl_Position 所以三角形将在剪辑空间坐标处绘制

  0, 0, 
  0, 0.5,
  0.7, 0,
 

这些值如何转换为像素取决于gl.viewport 设置。 gl.viewport 默认为画布的初始大小。由于我们没有为画布设置大小,因此默认大小为300x150。从剪辑空间转换为像素(在WebGL和OpenGL文献中通常称为屏幕空间)WebGL将绘制一个三角形

 clip space      screen space
   0, 0       ->   150, 75
   0, 0.5     ->   150, 112.5
 0.7, 0       ->   255, 75
 

WebGL现在将渲染该三角形。对于要绘制它的每个像素,它将调用我们的片段着色器。我们的片段着色器只是设置gl_FragColor1, 0, 0.5, 1 。由于Canvas是每通道8位画布,这意味着WebGL将把值[255, 0, 127, 255] 写入画布。

我们仍然没有从评论中提到三件大事。纹理,变化和制服。每个都需要它自己的主题。

安装或设置

WebGL是一种浏览器技术,因此除了拥有浏览器之外没有太多设置。您可以开始使用JSFiddleCodepenJSBIn上的WebGL或任何其他允许您在线编辑HTML,CSS和JavaScript的网站,尽管会有一些限制(见下文)。您还可以在github页面或类似服务上托管开源文件。

另一方面,在某些时候你可能会在本地工作。为此,建议您运行一个简单的Web服务器。有很多可供选择,使用简单,只需很少的设置。

使用node.js作为服务器

  1. 安装node.js
  2. 打开终端或节点命令提示符并键入npm install -g http-server (在OSX上放置sudo
  3. 键入http-server 以开始提供当前文件夹中的文件或http-server path-to-folder 以服务于另一个文件夹
  4. 将浏览器指向http://localhost:8080/name-of-file 以查看WebGL网页

使用devd作为服务器

  1. 下载devd
  2. 打开终端并使用devd . 运行devd . 从当前文件夹或devd path-to-folder 服务器文件以提供不同的文件夹
  3. 将浏览器指向http://localhost:8000/name-of-file 以查看WebGL网页

使用Servez作为服务器

  1. 下载Servez
  2. 安装它,运行它
  3. 选择要投放的文件夹
  4. 选择“开始”
  5. 转到http://localhost:8080 或选择“启动浏览器”

servez

使用“ Web Server for Chrome ”Chrome扩展程序

  1. 从Chrome安装Web服务器

  2. 从新标签页上的“ 应用”图标启动它。

    如何在chrome上启动app

  3. 设置文件所在的文件夹,然后单击http://127.0.0.1:8787 链接

    设置对话框

WebGL在线服务的局限性

在WebGL中,加载图像非常常见。在WebGL中,对图像的使用方式存在限制。特别是未经托管图像的服务器的许可,WebGL不能使用来自其他域的图像。目前授予使用图像权限的服务包括imgur和flickr。请参阅加载跨域图像。否则,您需要将图像放在与webgl页面相同的服务器上,或使用其他创意解决方案,例如使用canvas标签生成图像