为GLSL搭建WebGL环境

本文用于创建一个用在 WebGL 上的 GLSL 的快速预览环境,便于后续学习 GLSL 着色器语言。

VS code

首先是 vscode 的本地编写,这里需要两个扩展:

注意第二个插件的使用方式仅支持 vsc 的命令调用,没有按钮可以触发。因此若 vsc 安装在 C 盘且没有管理员权限时,可能会报错:show glsl-canvas 命令错误,可能是写入命令菜单时写权限不够导致,此时建议去 vscode market 下载插件包离线安装。

到此处 vscode 的准备完成,可以创建.vert.frag后缀文件编写顶点着色器(vertexShader)和片元着色器(fragmentShader),片元着色器可以写完后立即用 glsl-canvas 插件预览,不需要顶点着色器,插件会自动提供几个进行渲染。

WebGL demo

此处使用 Vue 框架,但不使用也可以,该示例与框架无关,只要创建一个 canvas 元素并拿到 DOM 执行脚本就可以了。

index.vue

<template>
  <canvas id="renderCanvas" width="1000" height="1000"></canvas>
</template>
<style scoped>
#renderCanvas {
  display: block;
  margin: auto;
  width: min(90vh,90vw);
  height: min(90vh,90vw);
}
</style>
<script>
import main from "./script";
export default {
  mounted() {
    main();
  },
};
</script>

script.js

const vertexShader = `
void main(){
  gl_Position=vec4(0.0,0.0,0.0,1.0);
  gl_PointSize=1000.0;
}`;

const fragmentShader = `
#ifdef GL_ES
precision mediump float;
#endif
void main(){
  vec2 st = gl_FragCoord.xy / vec2(800,800) - 0.5;
  float lengthV = length(st);
  float stepV = step(0.3,lengthV);
  gl_FragColor = vec4(stepV,stepV,0,1.0);
}
`;

export default function main() {
  // 获取webgl的上下文
  var canvas = document.getElementById("renderCanvas");
  var gl = canvas.getContext("webgl");
  if (!gl) {
    return;
  }
  // 设置清空颜色
  gl.clearColor(1, 1, 1, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // 初始化着色器(定点着色器和片段着色器)
  // 顶点着色器:就是定义点的位置、大小
  // 片元着色器:定义画出来的物体的材质(颜色、反光度等...)

  // 创建顶点着色器对象
  var _vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShader);

  // 创建片元着色器对象
  var _fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShader);
  // 给片元着色器对象绑定定义代码

  // 创建一个着色器程序
  var glProgram = gl.createProgram();

  // 把前面创建的二个着色器对象添加到着色器程序中(顶点和片段着色器都需要)
  gl.attachShader(glProgram, _vertexShader);
  gl.attachShader(glProgram, _fragmentShader);

  // 把着色器程序链接成一个完整的程序
  gl.linkProgram(glProgram);

  // 使用这个完整的程序
  gl.useProgram(glProgram);

  // 绘制一个点
  gl.drawArrays(gl.POINTS, 0, 1);
}

function createShader(gl, type, source) {
  let shader = gl.createShader(type);
  gl.shaderSource(shader, source.trim());
  gl.compileShader(shader);
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }
  console.error(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

此时,仅需要修改两个着色器代码就可以进行预览了。

vscode 写进 String Template 的 GLSL 作为字符串可读性不好?可以用下面这两个插件来解决:^1

  • Comment tagged templates
  • Shader languages support
const vertexshader = /* glsl */ `
  void main(){
    ....
  }
`;

注意

引入方式

上述环境为了省事直接将 vertexShader 和 fragmentShader 代码作为字符串使用,效果可以实现但不太利于组织代码。

在 html 文件中支持 script 标签写法,通过document.querySelector("#vertex-shader-2d").text获取 shader 文本。

<script id="vertex_shader" type="shader/x-vertex">
  void main(){
    gl_Position=vec4(0.0,0.0,0.0,1.0);
    gl_PointSize=1000.0;
  }
</script>
<script id="fragment_shader" type="shader/x-vertex">
  #ifdef GL_ES
  precision mediump float;
  #endif
  void main(){
    vec2 st = gl_FragCoord.xy / vec2(800,800) - 0.5;
    float lengthV = length(st);
    float stepV = step(0.3,lengthV);
    gl_FragColor = vec4(stepV,stepV,0,1.0);
  }
</script>

在 webpack 工程中可以通过raw-loader(功能是将被引入的文件内容转换为字符串)读 glsl 文件并编译。例:

{
  test: /\.glsl$/,
  loader: 'raw-loader'
}

版本声明

对代码文本使用trim()方法,shader 的版本声明#version 300 es必须在第一行,不许空行不许空格,并且要在顶点着色器和片元着色器中同时指定

var vsSource = `     // bad, a blank line here.
#version 300 es
`;

var vsSource = `#version 300 es  // good
`;

<script id="vs" type="notjs"> // bad, a blank line here.
#version 300 es
...
</script>

<script id="vs" type="notjs">#version 300 es  // good
...
</script>

参考资源

^1 : 使用 WebGL 和 Shaders 进行高级创意编码 Advanced Creative Coding with WebGL & Shaders