前文我们对 WebGL 需要的着色器程序进行了简单介绍,本章将使用 WebGL 做一个简单的示例,这篇教程将编写一个简单的着色器程序对,并链接成一个着色器程序。
创建着色器程序
WebGL 只关心两件事:裁剪空间中的坐标值和颜色值,使用 WebGL 就是提供这两个信息。你需要提供两个着色器来做这两件事,一个顶点着色器提供裁剪空间坐标值,一个片断着色器提供颜色值。
无论画布有多大,裁剪空间的坐标范围永远是 -1 到 1 。这里有一个简单的 WebGL 示例,展示 WebGL 的简单用法。
让我们从顶点着色器开始:
// 一个属性值,将会从缓冲中获取数据
attribute vec4 a_position;
// 所有着色器都有一个main方法
void main() {
// gl_Position 是一个顶点着色器主要设置的变量
gl_Position = a_position;
}
如果用 JavaScript 模拟 GLSL,当它运行的时候,你可以想象它做了类似以下的事情:
// *** 伪代码!! ***
var positionBuffer = [
0, 0, 0, 0,
0, 0.5, 0, 0,
0.7, 0, 0, 0,
];
var attributes = {};
var gl_Position;
drawArrays(..., offset, count) {
var stride = 4;
var size = 4;
for (var i = 0; i < count; ++i) {
// 从positionBuffer复制接下来4个值给a_position属性
attributes.a_position = positionBuffer.slice((offset + i) * stride, size);
runVertexShader();// 运行顶点着色器
...
doSomethingWith_gl_Position();
}
实际情况没有那么简单,因为 positionBuffer
将会被转换成二进制数据(见后文),所以真实情况下从缓冲中读取数据有些麻烦,但是希望这个例子能够让你想象出顶点着色器是怎么执行的。
接下来我们需要一个片段着色器:
// 片断着色器没有默认精度,所以我们需要设置一个精度
// mediump是一个不错的默认值,代表“medium precision”(中等精度)
precision mediump float;
void main() {
// gl_FragColor是一个片断着色器主要设置的变量
gl_FragColor = vec4(1.0, 0.0, 0.5, 1.0); // 返回“瑞迪施紫色”
}
上方我们设置 gl_FragColor
为 1.0, 0.0, 0.5, 1.0
,其中 1.0 代表红色通道的值,0.0 代表绿色通道的值,0.5 代表蓝色通道值,最后一个 1.0 表示阿尔法通道值。WebGL 中的颜色值范围是 0.0 到 1.0 。
现在有了两个着色器方法,让我们开始使用 WebGL。
首先需要一个 HTML 中的 canvas 对象:
<canvas id="webgl-canvas"></canvas>
然后可以用 JavaScript 获取它:
var canvas = document.getElementById("webgl-canvas");
创建一个 WebGL 渲染上下文(WebGLRenderingContext):
var gl = canvas.getContext("webgl");
if (!gl) {
// 你不能使用WebGL!
...
现在需要编译着色器对,提交到 GPU。你可以利用 JavaScript 中创建字符串的方式创建 GLSL 字符串:用串联的方式(concatenating),用AJAX下载,用多行模板数据。或者在这个例子里,将它们放在非 JavaScript 类型的标签中。
<script id="2d-vertex-shader" type="notjs">
// 一个属性变量,将会从缓冲中获取数据
attribute vec4 a_position;
// 所有着色器都有一个main方法
void main() {
// gl_Position 是一个顶点着色器主要设置的变量
gl_Position = a_position;
}
</script>
<script id="2d-fragment-shader" type="notjs">
// 片断着色器没有默认精度,所以我们需要设置一个精度
// mediump是一个不错的默认值,代表“medium precision”(中等精度)
precision mediump float;
void main() {
// gl_FragColor是一个片断着色器主要设置的变量
gl_FragColor = vec4(1, 0, 0.5, 1); // 返回“瑞迪施紫色”
}
</script>
事实上,大多数三维引擎在运行时利用模板,串联等方式创建GLSL。对于这个教程中的例子来说,没有复杂到要在运行时创建 GLSL 的程度。
接下来我们使用的方法将会创建一个着色器,只需要上传 GLSL 数据,编译成着色器。你可能注意到这段代码没有任何注释,因为可以从方法名很清楚的了解方法的作用(这里作为翻译版本我还是稍微注释一下)。
// 创建着色器方法,输入参数:渲染上下文,着色器类型,数据源
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);
}
现在可以使用以上方法创建两个着色器:
var vertexShaderSource = document.getElementById("2d-vertex-shader").text;
var fragmentShaderSource = document.getElementById("2d-fragment-shader").text;
var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
然后我们将这两个着色器 link(链接)到一个 program(着色程序):
function createProgram(gl, vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
var success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
然后调用它:
var program = createProgram(gl, vertexShader, fragmentShader);
到此为止我们已经创建了一个着色器程序,下一篇将完成这个 WebGL 示例。