9de12b30b9bca82843367629aa426e1c
webgl实现发光线框(glow wireframe)效果

在之前这篇文章,
WebGL 单通道wireframe渲染
我们介绍了webgl如何实现单通道wireframe的效果。

本篇文章就是在此技术原理基础之上,来实现发光的wireframe效果。

要实现发光的效果

所谓的发光的效果,就是颜色的渐变。 渐变越慢,发光的效果越明显,渐变越快,发光效果越不明显。

其实wireframe本身就是在两种颜色之间进行渐变,从代码也可以看出:

gl_FragColor.rgb = mix(vec3(.0,.0,.0), vec3(1.0,1.0,1.0),edgeFactor3());

其中edgeFactor3() 就是通过重心坐标的变换计算出来的一个渐变过度的参数。
但是由于这种渐变的效果不够慢,所以 发光的效果不是很明显,因此我们可以改进如下效果,把渐变的参数通过pow函数进行处理,代码如下:

 float interopter = edgeFactor3();
 interopter = pow(interopter, uPower);
gl_FragColor.rgb = mix(vec3(1.0,.0,.0), vec3(1.0,1.0,1.0),interopter);

其中uPower表示pow函数的次方,此处取值范围0~1,通过uniform变量传递该变量的数值,最终的效果如下:

上面是既有线框部分,也有面的部分。如果想实现只有线框的效果,可以启用透明的机制,并对颜色的透明度也进行渐变插值运算,透明设置代码如下:

    // 启用混合功能
      gl.enable(gl.DEPTH_TEST);
      gl.enable(gl.BLEND);
      gl.disable(gl.DEPTH_WRITEMASK);
      // 设置混合函数
      gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

shader代码增加以下的这行代码:

gl_FragColor.a = mix(1.0, .0,interopter);

效果如下图所示:
发光的线框

发光的线框

如果模型替换成球形,效果如下:

发光的线框-球体

发光的线框-球体

加载模型的效果如下:

发光的线框-模型

发光的线框-模型

如果修改shader中的edgeFactor3函数,把计算最小值,改为计算平均值,代码如下:

float edgeFactor3(){
        vec3 d = fwidth(vBarycentric);
        vec3 a3 = smoothstep(vec3(0.0), d * 30.0 , vBarycentric);
      //return min(min(a3.x, a3.y), a3.z);
       return (a3.x + a3.y + a3.z) / 3.0;
  }

得到最终的效果如下图所示(立方体):

发光的线框-立方体

发光的线框-立方体

替换成模型,效果如下:

发光的线框-模型

发光的线框-模型

如果结合混合模式中的相加混合,加上多个模型的叠加,可以得到更明显的发光叠加的效果,此种效果经常用于智慧园区,智慧楼宇中楼宇的发光效果呈现。
首先把混合模式改成相加混合,代码如下:

     // 设置混合函数
      // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
      gl.blendFunc(gl.SRC_ALPHA, gl.ONE); //相加混合模式

然后同时绘制多个模型,代码如下:

 for (var i = 0;i < 10;i ++){
            gl.uniform1f(normalProgram.uScale, 1 - i/10)
            gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
           }

最终的效果如下所示:

叠加效果

叠加效果

叠加效果

叠加效果

叠加效果

叠加效果

全部代码:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>单通道 solid wireframe</title>
        <script type="text/javascript" src="js/utils.js"></script>
        <script type="text/javascript" src="libs/gl-matrix.js"></script>
        <!-- <script type="text/javascript" src="js/shaders.js"></script> -->
        <script type="text/javascript" src="js/shaders3.js"></script>
        <!-- <script type="text/javascript" src="js/shaders3.js"></script> -->
    <script type="text/javascript" src="js/SolidWireframe.js"></script>
    <script type="text/javascript" src="js/Sphere.js"></script>

    </head>
  <body onload= 'load();'>
     <div>
       power:<input type="range" id="power"/>
     </div>
      <canvas id = "webgl" width = "800" height = "800" />

    </body>
</html>
var utils = {};

utils.getCanvas= function (id) {
    if(id instanceof HTMLCanvasElement){
        return id;
    }
    var canvas =  document.getElementById(id);
    if(canvas instanceof HTMLCanvasElement){
        return canvas;
    }
    return null;
}
utils.getWebGLContext = function(id){
    var canvas = utils.getCanvas(id);
    var options = {
        antialias:true,
    }
    if(canvas != null){
        return canvas.getContext('webgl',options) || canvas.getContext('experimental-webgl',options);
    }
    throw 'expect canvas but find none';
}

utils.buildProgram = function(gl,vertexSource,fragmentSource){
    var vertexShader = utils.createShader(gl,gl.VERTEX_SHADER,vertexSource);
    var fragmentShader = utils.createShader(gl,gl.FRAGMENT_SHADER,fragmentSource);
    var program = gl.createProgram(); // 创建程序
    // 程序绑定着色器
      gl.attachShader(program,vertexShader);
      gl.attachShader(program,fragmentShader);
      // 链接程序
    gl.linkProgram(program)
    if ( !gl.getProgramParameter( program, gl.LINK_STATUS) ) {
          var info = gl.getProgramInfoLog(program);
          console.log("Could not initialise shader\n" + "VALIDATE_STATUS: " + gl.getProgramParameter(program, gl.VALIDATE_STATUS) + ", gl error [" + gl.getError() + "]");
          throw 'Could not compile WebGL program. \n\n' + info;
    }
    // 使用程序
    gl.useProgram(program)
    return program;
}

utils.createShader = function(gl,type,source){
    var shader = gl.createShader(type); // 创建着色器对象
       gl.shaderSource(shader,source); // 将着色器源码写入对象
       gl.compileShader(shader); // 编译着色器

      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS) && !gl.isContextLost()) {
        var info = gl.getShaderInfoLog(shader);
        console.error(info);
        throw 'Could not compile shader program. ' + info;
    }
       return shader;
}

utils.createVertexBufferObject = function(gl,vertices){
    var vertexBuffer = gl.createBuffer(); // 创建缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); //绑定缓冲区
    gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW); //给缓冲区填充数据
    return vertexBuffer;
}

utils.initVertexBufferObject = function(gl,vertices,size,location){
    var vertexBuffer = gl.createBuffer(); // 创建缓冲区
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer); //绑定缓冲区
    gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW); //给缓冲区填充数据
    if(size && location != null){
           gl.vertexAttribPointer(location,size,gl.FLOAT,false,0,0); // 把缓冲区分配给attribute变量
    }

    return vertexBuffer;
}

utils.getVariableLocation = function(gl,program,name,attribute){
    var location;
    if(attribute){ // 是attribute变量
        location = gl.getAttribLocation(program,name);
         if(location < 0){
                 throw "Get attribute variable "+name+"'s location fail";
              }
    }else{ // 表示是uniform 变量
        location = gl.getUniformLocation(program,name);
        if(location == null){
            throw "Failed get unifrom variable "+name+"'s location"
        }
    }
    return location;
}

utils.getProgramVariableLocations2 = function (gl,program,variableNames,isAttribute) {
      var len = variableNames.length;
      for (var i = 0; i < len; i++) {
        var variableName = variableNames[i];
          if (isAttribute) { // attribute 变量
            var location = program[variableName] = utils.getVariableLocation(gl, program, variableName, true);
          } else { // uniform 变量
            program[variableName] = utils.getVariableLocation(gl, program, variableName, false);
          }
      }
}

utils.getProgramVariableLocations = function(gl,program,variableNames){
       var len = variableNames.length;
       for(var i = 0; i < len;i ++){
           var variableName = variableNames[i];
           if(variableName.startsWith('a')){ // attribute 变量
               var location = program[variableName] =  utils.getVariableLocation(gl,program,variableName,true);
           }else{// uniform 变量
               program[variableName] = utils.getVariableLocation(gl,program,variableName,false);
           }   
       }
}

```
function load(){
var gl = window.gl= utils.getWebGLContext('webgl',{
antialias: true,
});
if(gl == null){
console.log("Get WebGL Context fail");
return;
}

// 
gl.getExtension("OES_standard_derivatives");

 // 创建正常场景着色器程序
 var normalProgram = utils.buildProgram(gl,normalVS,normalFS);

 utils.getProgramVariableLocations(gl, normalProgram, [
   'aPosition', 'aColor', 'aNormal', 'aBarycentric', 'uMVPMatrix', 
   'uLightColor', 'uLightPosition', 'uAmbientLightColor', 'uPower',
   "uScale",
  ]);

// var sphere = new Sphere(10,20,20);
// //创建立方体数据
var verticesColors = new Float32Array([
       // 前面四个顶点
         -5,  5,  5, 1,0,0,//v0  红色 法线向量 0,0,1
        -5, -5,  5, 1,0,0,// v1 红色
          5,  5,  5,  1,0,0,// v2 红色
          5,  -5, 5,  1,0,0,// v3 红色
       // 右面四个顶点
         5,5,5, 0,1,0, //v4 绿色 法线向量 1,0,0
         5,-5,5, 0,1,0,//v5 绿色
         5,5,-5, 0,1,0,//v6 绿色
         5,-5,-5, 0,1,0,//v7 绿色
       // 上面四个顶点
         -5,5,-5,0,1,1, //v8 青色 法线向量 0,1,0
         -5,5,5,0,1,1, //v9 青色 
         5,5,-5,0,1,1,//v10 青色 
         5,5,5,0,1,1,//v11 青色 
       // 下面四个顶点
          -5,-5,5,1,1,1,//v12 白色 法线向量 0,-1,0
         -5,-5,-5,1,1,1,//v13 白色
         5,-5,5,1,1,1,//v14 白色 
         5,-5,-5,1,1,1,//v15 白色
       // 左面的四个顶点
        -5,5,-5,1,1,0,//v16 黄色  法线向量 -1,0,0
        -5,-5,-5,1,1,0,//v17 黄色
        -5,5,5,1,1,0,//v18黄色
        -5,-5,5,1,1,0,//v19 黄色
       // 后面的四个顶点
        5,5,-5,1,0,1,//v20 红蓝 法线向量 0,0,-1
        5,-5,-5,1,0,1,//v21 红蓝
        -5,5,-5,1,0,1,//v22 红蓝
        -5,-5,-5,1,0,1,//v23 红蓝
  ]);

  // var verticesColors = sphere.getVerticesArray(); 

   var normals = new Float32Array([
     // 前面四个顶点
     0, 0, 1, //v0  红色 法线向量 0,0,1
     0, 0, 1, // v1 红色
     0, 0, 1, // v2 红色
     0, 0, 1, // v3 红色
     // 右面四个顶点
     1, 0, 0, //v4 绿色 法线向量 1,0,0
     1, 0, 0, //v5 绿色
     1, 0, 0, //v6 绿色
     1, 0, 0, //v7 绿色
     // 上面四个顶点
     0, 1, 0, //v8 青色 法线向量 0,1,0
     0, 1, 0, //v9 青色 
     0, 1, 0, //v10 青色 
     0, 1, 0, //v11 青色 
     // 下面四个顶点
     0, -1, 0, //v12 白色 法线向量 0,-1,0
     0, -1, 0, //v13 白色
     0, -1, 0, //v14 白色 
     0, -1, 0, //v15 白色
     // 左面的四个顶点
     -1, 0, 0, //v16 黄色  法线向量 -1,0,0
     -1, 0, 0, //v17 黄色
     -1, 0, 0, //v18黄色
     -1, 0, 0, //v19 黄色
     // 后面的四个顶点
     0, 0, -1, //v20 红蓝 法线向量 0,0,-1
     0, 0, -1, //v21 红蓝
     0, 0, -1, //v22 红蓝
     0, 0, -1, //v23 红蓝
   ]);

  // var normals = sphere.getNormals();

 var indices = new Uint16Array([
        0,1,2 ,2,1,3, // 前表面索引
        4,5,6,6,5,7, //右
        8,9,10,10,9,11, //上
        12,13,14,14,13,15, // 下
        16,17,18,18,17,19,//左
         20,21,22,22,21,23,//后
  ]);
  // var indices = sphere.getIndicesArray();
  // 启用混合功能
  gl.enable(gl.DEPTH_TEST);
  gl.enable(gl.BLEND);
  // gl.disable(gl.DEPTH_WRITEMASK);
  // 设置混合函数
  // gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
 var barycentrics =new Float32Array(computeBarycentric(indices));

  //  var barycentrics = sphere.getBarycentric();


  var floatSize = verticesColors.BYTES_PER_ELEMENT;
  var verticesColorsBuffer = utils.initVertexBufferObject(gl, verticesColors);
  var normalsBuffer = utils.initVertexBufferObject(gl,normals)

  var barycentricBuffer = utils.initVertexBufferObject(gl,barycentrics);


  var indexBuffer = gl.createBuffer(); // 创建缓冲区

  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indexBuffer); //绑定缓冲区
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW); //给缓冲区填充数据


  var radius = 20,angle = 1.5;
  var lightColor = [1.0,1.0,1.0],lightPosition = [0.01,20,0],ambientLightColor = [0.3,0.3,0.3];
   var viewMatrix = mat4.create(), projectMatrix = mat4.create(),
        mvpMatrix = mat4.create();
  setInterval(draw,100);
  function draw(){   
     var cameraPos = [radius * Math.sin(angle),10 ,radius * Math.cos(angle)],
        center = [0.0,0,0],up = [0,1,0],near = 1,far = 100;
        mat4.lookAt(viewMatrix,cameraPos,center,up);
        mat4.perspective(projectMatrix,Math.PI/2,1,near,far);
        mat4.mul(mvpMatrix,projectMatrix,viewMatrix);     
          gl.viewport(0,0,800,800);// 设置视口大小
          gl.clearColor(0.0,0.0,1.0,1.0);//设置背景色为蓝色
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);//清空  
          drawNormal(); // 绘制正常场景
        angle += 0.04;       
  }

  function drawNormal(){
      gl.useProgram(normalProgram);// 使用正常着色器程序
      // 设置着色器程序uniform变量
      gl.uniform3fv(normalProgram.uLightColor,lightColor);
      gl.uniform3fv(normalProgram.uLightPosition,lightPosition);
      gl.uniform3fv(normalProgram.uAmbientLightColor,ambientLightColor)
      gl.uniform1f(normalProgram.uPower, 0.05 + document.querySelector('#power').value / 500)

      gl.uniform1f(normalProgram.uScale, 1.0)
      gl.uniformMatrix4fv(normalProgram.uMVPMatrix,false,mvpMatrix);

      gl.enableVertexAttribArray(normalProgram.aPosition);
top Created with Sketch.