目标

在二维平面绘制一个点。

开始

你可以用自己喜欢的方式构建本地开发环境。作者使用WebStorm开发本书中的所有例子。

什么是着色器?

要使用WebGL进行绘图就必须使用着色器。在代码中,着色器程序是以字符串的形式“嵌入”在JavaScript脚本中的,在程序真正运行前它就已经设置好了。着色器主要分两种:

  1. 顶点着色器(Vertex shader):顶点着色器是用来描述顶点特性(如位置、颜色、尺寸等)的程序。顶点(Vertex)是指二维或三维空间中的一个点,比如二维或三维图形的端点或交点;
  2. 片元着色器(Fragment shader):进行逐片元处理过程(如光照效果)的程序。片元(fragment)是一个WebGL术语,你可以将其理解为像素(图像的单元)。

WebGL绘图区的坐标体系

在此后webgl渲染显示时都将使用如下坐标系。

箭头所指方向为该轴正方向。

如果你记不住也没关系,这个坐标系有个别名叫右手坐标系。你可以用右手帮助自己想起它。

创建html5模板

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello Point</title>
    <style>
        body {
            margin: 0;
        }

        canvas {
            width: 100%;
            height: 100%
        }
    </style>
</head>
<body onload="loaded()">
<div>
    <canvas id="c" width="500" height="500"></canvas>
</div>
<p><b id="msg"></b></p>
<script id="shader-vs" type="x-shader/x-vertex">
    void main(){
        gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
        gl_PointSize = 10.0;
    }


</script>
<script id="shader-fs" type="x-shader/x-fragment">
    void main(){
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
    }


</script>
<script src="../lib/detector.js"></script>
<script src="./HelloPoint.js"></script>
</body>
</html>

这里我们主要做了4件事。

  1. 通过stylesheet设置canvas的高宽占满整个页面。注意着这只是css指定的element高宽,而非canvas画布的高宽。在canvas element内我们又用attribute的width和height设置了它画布的真正高宽为500 x 500。如果我们不人为设置cnavas的高宽,它将自动被赋值为300 x 150;
  2. 在body中注册一个onload事件。我们在之后js初始化页面时要用到它;
  3. 申明了两块着色器(shader)代码——shader-vs、shader-fs。它是用GLSL ES编写的,IDE对它的识别不太友好,不会给予任何代码提示和高亮处理。将来我们渲染页面时需要用到它;
  4. 导入detector.js文件和HelloPoint.js文件。其中HelloPoint文件是接下来着重要分析的。

../lib/detector.js 是一个用来检测浏览器对webgl兼容性的小工具,我们暂时不管它。接下来让我们先分析主控制程序 ./HelloPoint.js,分析完它之后再来看着色器代码。

./HelloPoint.js

var gl, canvas;

function loaded() {
if (!detect()) return;//检测浏览器兼容性
canvas = document.getElementById("c");//获得用于展示的canvas元素
initGL(canvas);//获得webgl绘图上下文
initShaders();//初始化着色器程序
gl.clearColor(0.0, 0.0, 0.0, 1.0);//指定绘图区域的背景色
gl.clear(gl.COLOR_BUFFER_BIT);//将指定的缓冲器设定为预定的值
gl.drawArrays(gl.POINTS, 0, 1);//执行定点着色器,按照第一个参数指定的方式绘制

}
function detect(){...}
function initGL(){...}
function initShaders(){...}

我们看到了前文HTML模板中出现过得onload事件回调函数loaded。该函数将会在被添加的element完全加载所有内容(包括图像、脚本文件、CSS 文件等)后立即执行。loaded中做了很多事,每行代码之后都加了相应注释。其中有三个方法是本地编写的,它们是detect、initGL和initShaders。接下来让我们来逐个分析它们。

detect():检测浏览器对webgl的兼容性

该函数使用之前导入的 ../lib/detector.js 文件注册的一个全局变量 Detector 来对浏览器的webgl兼容性进行测试检测。对其实现机制感兴趣的读者可以在文末成果给出的链接中找到找到该文件。

function detect() {
if (Detector.webgl) {//浏览器支持webgl
return true;
} else {//浏览器不支持webgl
var warning = Detector.getWebGLErrorMessage();//获得错误信息
document.getElementById('msg').appendChild(warning);//添加到错误信息提示框
return false;
}
}
initGL():获得webgl绘图上下文

关于gl.createProgram()的相关文档请点击这里查阅。

function initGL(canvas) {
try {
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");//获得webgl绘图上下文
gl.viewportWidth = canvas.width;//记录canvas宽度,方便之后使用
gl.viewportHeight = canvas.height;//记录canvas高度,方便之后使用

gl.program = gl.createProgram();//创建并初始化一个WebGLProgram对象
} catch (e) {
}
if (!gl) {
alert("Could not initialise WebGL, sorry :-(");
}
}
initShaders():获得着色器文本并将其加入webgl编程对象。

这个方法的主要作用是获得着色器文本并使其与WebGLProgram产生关联,然后将该WebGLProgram设置为下次渲染状态的一部分。

function initShaders() {
var fragmentShader = getShader(gl, 'shader-fs');//获得片元着色器
var vertexShader = getShader(gl, 'shader-vs');//获得顶点着色器

var shaderProgram = gl.program;
gl.attachShader(shaderProgram, vertexShader);//向WebGLProgram对象添加一个顶点着色器
gl.attachShader(shaderProgram, fragmentShader);//向WebGLProgram对象添加一个片元着色器
gl.linkProgram(shaderProgram);//将WebGLProgram对象与其拥有的着色器连接起来

// If creating the shader program failed, alert

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.log('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
}

gl.useProgram(shaderProgram);//将指定的WebGLProgram对象作为下次渲染状态的一部分

function getShader(gl, id, type) {//获得script中关于着色器文本的内容
var shaderScript, theSource, currentChild, shader;

shaderScript = document.getElementById(id);//获得着色器元素

if (!shaderScript) {
return null;
}

theSource = shaderScript.text;//获得着色器文本

if (!type) {
if (shaderScript.type == 'x-shader/x-fragment') {//是片元着色器
type = gl.FRAGMENT_SHADER;//准备设置着色器类型为片元着色器
} else if (shaderScript.type == 'x-shader/x-vertex') {//是顶点着色器
type = gl.VERTEX_SHADER;//准备设置着色器类型为顶点着色器
} else {
// Unknown shader type
return null;
}
}
shader = gl.createShader(type);//按此前准备的类型新建着色器

gl.shaderSource(shader, theSource);//为着色器设置源码(GLSL ES)

// Compile the shader program
gl.compileShader(shader);//将着色器源码编译成二进制码,以供WebGLProgram使用

// See if it compiled successfully
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {//检查是否通过编译
console.log('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);//删除着色器程序
return null;
}

return shader;//返回着色器对象
}
}

本示例的js代码基本上就这么多了。下面来看着色器代码。

还记得在之前提到的html5模板里我们嵌入了两段着色器代码么?和html代码混在一起可能令它们变得难以辨认,让我们把它们单独拎出来观察下。

顶点着色器(vertex shader)

void main(){
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 10.0;
}

片元着色器(fragment shader)

void main(){
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

在顶点着色器中,我们为gl_Position和gl_PointSize两个变量赋值。它们内置在webgl顶点着色器中,有着特殊的含义:gl_Position表示要绘制点的位置,gl_PointSize表示点的尺寸。

类型 变量名 描述
float gl_PointSize 所绘点尺寸(像素数)
vec4 gl_Position 所绘点位置

GLSL ES是一种类C语言,因此它也是一种强类型编程语言。float与其他语言的浮点型无异。注意10和10.0的数据类型是不同的,前者是整形数,后者是浮点数。vec4是一个由4个浮点数组成的向量(vector)。

GLSL ES中的数据类型:

类型 描述
float 表示浮点数
vec4 表示由4个浮点数组成的矢量 [float, float, flaot, float]

你可能会产生疑问,为什么三维坐标系需要用4个浮点数来表示其位置。在此引入齐次坐标的概念。齐次坐标使用如下符号描述:(x, y, z, w)。齐次坐标(x, y, z, w)等价于三维坐标(x/w, y/w, z/w)。因此当我们将w设为1时,三维坐标的三个矢量分量等价于齐次坐标的前三个分量。当w趋于0时,那么它所表示的点趋于无穷远。在处理三维数据时,为了提高处理效率,往往采用齐次坐标来描述点的位置。

我们在赋值时并没有直接将某个vec4变量赋给gl_Position,而是使用着色器内置的vec4()来帮助创建了一个vec4变量后完成赋值。这么做是为了方便此后我们能通过api动态传值改变gl_Position。

vec4 vec4(v0, v1, v2, v3)
根据v0, v1, v2, v3 创建vec4对象
参数 v0, v1, v2, v3 四个浮点型分量
返回值 由v0, v1, v2, v3组成的vec4对象

片元着色器的作用就是处理片元(显示在屏幕上的一个像素的位置、颜色及其他信息)。片元着色器将颜色信息赋给gl_FragColor变量,它是片元着色器唯一的内置变量,它控制着像素在屏幕上的最终颜色。

类型 变量名 描述
vec4 gl_FragColor 指定片元颜色(RGBA格式)

成果

https://tabrisk.github.io/webgl-getting-start/example/chapter_1/HelloPoint.html

results matching ""

    No results matching ""