在之前这篇文章,
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);
在之前这篇文章,
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);
在之前这篇文章,
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);