为什么我的WebGL开发这么丝滑 🌊

10,905 阅读4分钟

前言

为了更好在vscode的webGL开发体验,本文将从vscode插件和手写一个node插件,几分钟让你可以快速提高几倍的书写效率,从此告别glsl书写的恐惧症。

噩梦

之前的webgl文章写了简单的绘制动态点,但是有个问题一直砸在心中,比如下面的代码:

const VERTEX_SHADER_SOURCE = `
    attribute vec4 aPosition
    void main(){
      gl_Position = aPosition;
      gl_PointSize = 10.0;
    }  
`
const FRAGMENT_SHADER_SOURCE = `
    void main() {
      gl_FragColor = vec4(1.0,0.0,0.0,1.0); 
    }
`

glsl与html

看了上面的代码也能感觉出来写glsl的恶心之处,在html中写glsl就是写个字符串,没有校验、高亮、语法提示、格式化等。翻来覆去网上的文章也没有很好的说明,那么你很容易因为结尾少个分号,单词少个字符就无法执行。devTools调试glsl真的是个噩梦,本文将以本人的经验快速给大家排掉这些坑。

插件

vscode插件介绍
Shader languages support for VS Code让vscode支持glsl语言
glsl-snippetshtml中获得glsl的语法提示
glsl-canvas提供了语法的校验、高亮、格式化,预览效果,注意是在glsl文件中,在glsl的文件中uniform变量名一定要注意用u_<...>变量的形式,不然无法渲染。vscode中按住ctrl + shift + P 输入show glskCanvas直接渲染glsl文件在vscode中预览
glsl lint提供了基础的校验,注意这里可以配置对应的文件匹配如fs、vs等
heist支持将glsl文件导入html

提升体验

安装好插件后我们可以直接在glsl文件中写顶点着色器源码和片元着色器的源码,如下

image.png

image.png

vs、verx对应的是顶点着色器源码的文件, fs、frag的文件后缀对应片元着色器的源码,我们可以看到安装好插件后高亮、校验、格式化等都有了很好的支持。但是引出了一个问题,我们怎么导入到我们的html中呢?后面会说明,我们先看看glsl在html的书写方式有哪些

glsl书写方式

1. 字符串

const vertexShader = `void main() {}`

2. createShader和shaderSource

<script type="shader-source" id="vertexShader">
        void main(){ }
</script>
<script type="shader-source" id="fragmentShader">
        void main(){ }
</script>
function createShader(gl, type, source) {
  let shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
  let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (success) {
    return shader;
  }
  console.error(gl.getShaderInfoLog(shader));
  gl.deleteShader(shader);
}

这种方式核心用gl的createShader和shaderSource加载script的方式,但是同样由于在html中无法更好的跟vscode配合。

3.直接将glsl转换为js

这种方式相对比较简单,根据监听文件变动做对应的正则替换

glsl 转换 js

我们知道web肯定需要导入html,但是目前没有很好的办法直接glsl导入到浏览器的环境中,我们与gpu的交互只是字符串的形式,glsl也无法直接导入html中。我们只能将glsl文件简单的转为js,那么我们简单用node实现一个读写处理就行。

转变过程

image.png

image.png

也就是fs图元着色器源码转为如上的js源码, 顶点着色器也同理,下面简单写个node的脚本

Node脚本

const Chokidar = require('Chokidar')
const path = require('path')
const fs = require('fs')

const fragmentPath = path.resolve(__dirname, './tran/index.fs')
const vertexPath = path.resolve(__dirname, './tran/index.vs')

const sourceFragmentPath = path.resolve(__dirname, './tran/dist/fragment.js')
const sourceVertexPath = path.resolve(__dirname, './tran/dist/vertex.js')

function replaceShader (target, filePath, sourcePath) {
  fs.readFile(filePath, 'utf-8', (err, data) => {
    if (err) console.error("err1", err)
    const fragment_source = data

    let replaceData = `const ${target} = ` + '`' + `${fragment_source}` + '`'
    fs.writeFile(sourcePath, replaceData, 'utf-8', (err) => {
      if (err) console.error("err3", err)
    })
  })
}

Chokidar.watch(vertexPath).on('change', function () {
  replaceShader('vertexShader', vertexPath, sourceVertexPath)
})

Chokidar.watch(fragmentPath).on('change', function () {
  replaceShader('fragmentShader', fragmentPath, sourceFragmentPath)
})
  <script src="../../utils/index.js"></script>
  <script src="./tran/dist/fragment.js"></script>
  <script src="./tran/dist/vertex.js"></script>
  <canvas id="canvas">
    不支持canvas
  </canvas>

我们可以通过node的Chokidar兼容包来监听文件改变,然后fs的读写操作做个转换就可以了。然后在html引入对应的js文件,这样就实现了glsl文件 -> 字符串 -> gpu渲染,是不是很简单?

WebGL 辅助工具

WebGL Inspector (benvanik.github.io) 有兴趣的可以下载看看 !

image.png

通过WebGL 辅助工具可以捕获某一帧并查看这一帧中WebGL的详细调用。 它在你的程序正常运行时或运行并崩溃的情况下很有用。 但是如果你初始化阶段出了问题或你没有在每一帧中使用动画它就不会捕捉任何东西。

总结

看完后,我们是不是感觉glsl书写过程变丝滑了,有了本文的基础以后在写其他glsl的时候,其实可以考虑写个命令行解析路径实现自动生成模板,这样是不是可以更丝滑了。

如果觉得文章对你有帮助,不要忘了一键三连 👍

附录

  1. 内卷年代,是该学学WebGL了
  2. 为什么我的WebGL开发这么丝滑 🌊
  3. 立体感十足的数据可视化:我的WebGL 3D环状图制作分享

本文正在参加「金石计划」