美文网首页JavaScript 进阶营程序员
坐稳咯,webgl入门小贴士!

坐稳咯,webgl入门小贴士!

作者: CharTen | 来源:发表于2017-09-29 16:46 被阅读0次

    经过这一个月来欲仙欲死的摸索,总算在摸索出了一些入门webGl的门道。关于webGl的学习,我建议大家去入手一本《webGl编程指南》和《线性代数》,里面的内容非常详细,这里也不需要在多说了。还有,编程指南那本书的代码结构写得还是值得人吐槽的,所以,遇到问题也请多多善用搜索引擎,或者:https://stackoverflow.com/search?q=你的问题
    本文不做关于webgl的任何教程内容,本文旨在分享一下我在摸索webgl中的一些姿势和一些坑,帮助一些初学者学习得更舒服一点。

    第一,webGL!==web 3D

    我不知道多少人最开始学习webGL是把它当web 3d方面去学习的,至少最开始我以为webGL就是用来绘制3D模型的。
    Naive!
    webGL是比canvas.getContext('2d')更加底层的图形绘制接口。而它的工作原理,实际上就是遍历每一个像素点,然后给各个像素点填充颜色,然后才构成一幅2d或者3d的图像。至于你想直接先搞3d方面的东西, 使用three.js比直接撸webGL舒服多了。
    而且,如果你愿意,webGL更适合去做图像处理。

    第二,shaders

    webGL工作的基本单位是shaders,中文唤作着色器。
    而我们亲爱的js在这个环境里能做的就只有跑跑腿传传值,并不能像ctx.stroke()那样亲自上阵。而着色器,完蛋了,根本就是一门新语言,叫glsl
    我们的js,是跑在浏览器里的语言。而glsl,它是跑在显卡里面的,它需要手动使用js去调用WebGL编译它的方法,然后变成二进制包,然后让浏览器把它塞入显卡里,最后才能够使用。
    所以webGL绘制比js去绘制的好处在于,webGL占用的是显卡里的资源,并不过多占用内存,性能比起canvas2D来,那是不知道高到哪里去了。
    开始学习shaders语言的时候,建议跟着编程指南的例子去敲,不过这里有一个坑,是我学习的时候遇到的。我跟着书里的例子去敲,却发现书里的例子无论如何也无法通过编译,它会报一行这样的错:

    error
    经过谷歌、百度、stackoverflow等多方询问,最终的解决办法也非常简单,在你每一个着色器程序头部加上这样一行:
    precision mediump float;
    这句话的意思是,设定中等精度为float型。显卡程序里面有三种精度:
    • 高精度highp
    • 中等精度mediump
    • 低精度lowp

    那这些精度是干嘛用的呢?当然是用来精确计算的。(隔壁连0.1+0.2都算不准的js酱躲在墙角默默哭泣)。比如说一个3d模型,它每个点的位置最好使用highp精度去计算,这样定位准确。而这个3d模型的贴图纹理,其实都是图片,对于图片像素位置的计算,使用中等精度的mediump就行了。最后的lowp,适合去计算像素的颜色值。
    然后说了这么多,还只是科普一下而已,因为不同设备对这三种精度的支持不一致(前端人深有体会,万恶的兼容),对三种精度的默认设置也不一致,比如某些垃圾的设备就把mediump这个级别设定为int型整数,这个计算精度一下子就下降了。
    所以在webGL里面要加上这句,统一设置mediump的默认值。这样,程序就可以通过编译了。

    第三,shaders,着色器程序glsl的加载

    就目前看到的大部分教程来看,加载shaders程序的方法无非以下几种:

    1. 写在html里面,在html里面插入一个<script type="text/plain">,然后把glsl写在里面。而js这边就需要写一个获取这个script的innerHTML的方法,读取到glsl的源码,再去编译。
      不过这样有个缺点,当你的代码编辑器,比如vscode,存在html代码格式优化这种功能的时候,会傻逼傻逼地将glsl源码压缩成一行。。。
    2. 直接使用字符串拼接,就是
    var vShaderSource='precision mediump float;'+
    'attribute vec4 a_Position;'+
    ...
    

    就跟我们使用 jquery拼接html一样去拼接glsl的源码。不得不说,很烦。

    1. ajax加载,这个就可以舒舒服服把glsl的源码写在.glsl文件里,然后通过ajax加载进来。如果你的代码编辑器可以的话,甚至有.glsl文件的语法高亮,就像vscode安装了高亮插件之后:
    语法高亮 比起写在javascript和html里面好看多了,舒服多了。喜欢语法高亮的个个都是人才,说话都好听,我超喜欢这样写。。。当然前提是你开启了本地服务器才能使用ajax读取。
    不过呢,作为新时代的前端人,掌握了webpack工程化开发习惯的我们怎么能忍受上面几种类似jq时代的写法呢?
    什么?配置babel然后使用es6的字符串模板写源码?
    Naive!
    webpack连css都能读进来,区区glsl!?这里我直接是使用了row-loader这个加载器去加载.glsl文件,然后既不用考虑ajax的异步同步问题,还能够保持.glsl文件语法高亮,通过一句var vGlsl=require('./xxx/xxx/xx.glsl')就能够将源码引入到js中,十分方便。

    懒得配置webpack的同学,这里我给你写好了一个简单的webpack模板了:

    webGL-Webpack-Template
    直接去我的github里面clone一下就好了,里面还有一个我写的小demo:
    https://github.com/Char-Ten/webGl-Webpack-Template

    第四 纹理加载的一些小问题

    如果你参照《webGL编程指南》的demo去写添加纹理,如果你是在网上随便找一张自己的图片的话,你可能会发现纹理渲染不出来,即便你的代码和例子一摸一样。它会报这样一行错:

    image.png

    这里的解决办法非常简单,最简单的解决办法,是先检查你使用的贴图尺寸。如果长和宽的大小都不是2的n次幂(即错误信息里面所说的non-power-of-2),那么请用PS等图像处理软件把它的长和宽分别处理为2的n次幂,如:1x1 2x2 4x4 8x8 16x16 32x32 64x64 128x128 256x256 512x512.....
    一般来说这样就能够解决了,然后参考一下stackoverflow一位dalao给的代码,你可以这样写一个创建纹理的函数:

        /**创建纹理贴图
          * @param {WebGLRenderingContext} webgl - 使用webgl的上下文
          * @param {Canvas||Image} image - 要作为纹理的图片对象
          * @return {WebglTexture} texture对象
          */
        function createTexByImage(webgl, image) {
            var texture = webgl.createTexture();
            webgl.bindTexture(webgl.TEXTURE_2D, texture);
            webgl.texImage2D(webgl.TEXTURE_2D, 0, webgl.RGBA, webgl.RGBA, webgl.UNSIGNED_BYTE, image);
            if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                return texture
            }
            webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MIN_FILTER, webgl.NEAREST);
            webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_MAG_FILTER, webgl.NEAREST);
            webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_S, webgl.CLAMP_TO_EDGE);
            webgl.texParameteri(webgl.TEXTURE_2D, webgl.TEXTURE_WRAP_T, webgl.CLAMP_TO_EDGE);
            return texture
        }
        
        /**检查数字是否为2的指数
          * @param {Number} value - 要检查的值
          * @return {Boolean}
          */
        function isPowerOf2(value) {
            return !(value & (value - 1));
        }
    

    当图片的尺寸不满足2的指数的时候,你要写满四个texParameteri方法。
    这个方法是用来设定纹理贴图参数的,有四个值可以设定,分别是

    1. TEXTURE_MAG_FILTER 设定图片放大后像素点的取值方式
    2. TEXTURE_MIN_FILTER 设定图片缩小后像素点的取值方式
    3. TEXTURE_WRAP_S 设定图片横向平铺样式
    4. TEXTURE_WRAP_T 设定图片垂直平铺样式

    默认贴图在webgl中是平铺的,只有设定为不平铺时(webgl.CLAMP_TO_EDGE),才能够渲染出来。

    至于这个原因呢,很简单,对于尺寸不是2的指数的图片,GPU对其遍历是十分消耗性能的。所以,你想做一个平铺重复的纹理,就必须使用符合规则的图片。

    第五,关于glsl语言的debug

    glsl这门语言不像js那样有console打印或者浏览器断点调试那样方便去调试一个程序。换句话说,当js以buffer的形式丢一个值给glsl,你没办法在glsl里面打印这个值是否正确。
    更何况glsl是静态类型语言,有时候忘记写类型声明,或者不同类型的值赋值的时候就会报错,甚至你后面少写了个分号都会报错,都会导致编译不通过。
    对于上面两种情况,首先是打印这个问题,没办法,glsl不能打印的时候你只能去猜这个变量到底是个什么值,然后给每个像素的颜色RGB设定为这个值,然后观察绘制的结果,通过颜色去验证数值正不正确,只是我目前能够用到的debug方法。。。期待有更好的方法出现。
    第二种情况,这个一方面依赖于自己对于glsl语言的学习,同时你也可以通过你的代码编辑器去检查是否有语法错误,或者,如果你在chrome调试的话,你可以去下载这些个chrome插件:

    image.png

    它们可以更好的帮助你检查程序错误已经其他问题。


    这就是我目前学习的过程中踩到的一些坑或者解决的一些小问题吧,而且学了一个月都还只在2d绘制上搞来搞去,想往3d方向走,需要的数学知识要更多,这些都只是基础而已。
    最后以一张作品图作为这篇文章的结尾吧(当然glsl的内容都是从网络上“移植”下来的。。。法线贴图生成算法移植自某位lua的dalao手笔),今后学习如果遇到新坑会继续写文章回填的。

    GIF.gif

    相关文章

      网友评论

        本文标题:坐稳咯,webgl入门小贴士!

        本文链接:https://www.haomeiwen.com/subject/xcqbextx.html