做一个独一无二的重力版个人博客(二)

在上一篇基础上将画面改成WebGL 3D效果。WebGL是OpenGLES的封装,使用方法跟OpenGL基本相同。在WebGL基础上还有Three.js等更高级的框架,能实现更好的效果。这里为了简便易于学习,我直接使用WebGL。

1 WebGL

Github上找了一下,有mdn的官方demo,一步步实现WebGL的基本功能,很不错。
https://github.com/mdn/webgl-examples
demo的sample7实现了一个方块的展示和光照,我们在它的基础上稍微修改一下。

1.1 修改模型大小

博客图片的大小不一样,因此创建VBO时需要修改模型数据大小。将模型里的数据改成x,y,z,这样就能根据图片大小动态配置了。

如果不修改模型大小,也可以通过后面变换矩阵实现,但会导致图片非常模糊。

function createPos(x,y,z) {
  return positions = [
    -x, -y,  z,
     x, -y,  z,
     x,  y,  z,
    -x,  y,  z,
    -x, -y, -z,
    -x,  y, -z,
     x,  y, -z,
     x, -y, -z,
    -x,  y, -z,
    -x,  y,  z,
     x,  y,  z,
     x,  y, -z,
    -x, -y, -z,
     x, -y, -z,
     x, -y,  z,
    -x, -y,  z,
     x, -y, -z,
     x,  y, -z,
     x,  y,  z,
     x, -y,  z,
    -x, -y, -z,
    -x, -y,  z,
    -x,  y,  z,
    -x,  y, -z,
  ];
}

1.2 绘制模型

这是最重要的一步。

跟上一篇一样,遍历world,将body找出来,根据id找到图片,根据body的位置和旋转方向对模型的矩阵进行变换。

模型的位置跟3D位置和视角有关,几何运算会很复杂。这里我给矩阵的位移和缩放乘以一个简单的系数mul,修改这个系数观察实际位置,就能得到跟实际很接近的结果。

      // body
      var id = body.GetUserData();
      if (id > 0) {
          var c = body.GetWorldCenter();
          var w = myBlogImages.get(id).width;
          var h = myBlogImages.get(id).height;
          var a = body.GetAngle();


          var modelViewMatrix = mat4.create();
          var mul = 0.214
          mat4.translate(modelViewMatrix,     // destination matrix
            modelViewMatrix,     // matrix to translate
            [c.x * mul, c.y * mul, -6.0]);  // amount to translate

          mat4.rotate(modelViewMatrix,  // destination matrix
                    modelViewMatrix,  // matrix to rotate
                    a,     // amount to rotate in radians
                    [0, 0, 1]);       // axis to rotate around (Z)

          mul = myBlogScale * 0.106
          var myCache = myCacheMap.get(id);
          if (myCache == null) {
            myCache = new MyCache()
            myCache.tex = loadTexture(gl, myBlogImages.get(id), w, h)
            myCache.buffers = initBuffers(gl, w * mul, h * mul)
            myCacheMap.set(id, myCache)
          }
          drawOne(gl, programInfo, myCache.buffers, myCache.tex, deltaTime, projectionMatrix, modelViewMatrix)

      }
      body = body.GetNext();

1.3 性能优化

如果每次绘制时都重新创建GPU数组会非常卡顿,将它们缓存到一个Map里面,根据id来取,只在第一次读取时创建,这样就比较流畅了。


      var myCacheMap = new Map();

      var myCache = myCacheMap.get(id);
      if (myCache == null) {
        myCache = new MyCache()
        myCache.tex = loadTexture(gl, myBlogImages.get(id), w, h)
        myCache.buffers = initBuffers(gl, w * mul, h * mul)
        myCacheMap.set(id, myCache)
      }

1.4 调试技巧

前面说过,要让WebGL绘制的位置和大小准确需要多次调整缩放比较参数mul,那么如何判断结果是否准确呢?

我在index.html里原来的canvas下面增加了一个canvas,它们一个用来绘制原来的2d图形,另一个绘制WebGL 3d图形。默认它们是上下放置的,在webgl.css里给下面的加上位移后,就可以让它们完全重叠,再给上层的加上透明度,就可以实时对照两个canvas里面的物体位置了。


            <div class='parent' style="margin:auto;width:1280px;padding:2px;border:0px solid #888;text-align:left"> 
                <canvas id="glcanvas" width="1280" height="720" tabindex='1'></canvas>  
            </div>
            <div class='child' style="margin:auto;width:1280px;padding:2px;border:0px solid #888;text-align:left"> 
                <canvas id="canvas" width="1280" height="720" tabindex='1'></canvas>  
            </div>

.child {
	position: relative;
	top: -724px;
	opacity: 0.2;
}


(位置大小没重叠时如图所示)

webgl-demo.js用来绘制3d图形,embox2d-html5canvas-testbed.js用来绘制2d图形并接收鼠标事件。调试完成后将2d的canvas remove掉,对象换成2d的canvas。这样3d图形就可以接收原来的鼠标事件了。


function init() {
    document.getElementById("debugDiv").hidden = hideDebugDiv
    
    canvas = document.getElementById("canvas");
    context = canvas.getContext( '2d' );
    if (hideCanvas) {
        canvas.remove()
        canvas = document.getElementById("glcanvas");
    }
    ...

2 其他优化

2.1 添加镜面光

mdn的demo里只有环境光和漫反射光,没有镜面光,我原来在OpenGLES上写过,在着色器里添加一下。

这里有一点语法差异,OpenGLES3.0里的in和out要改成varying highp。

const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec3 aVertexNormal;
    attribute vec2 aTextureCoord;

    uniform mat4 uNormalMatrix;
    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying highp vec3 aFragPos;
    varying highp vec3 aNormal;
    varying highp vec2 aTexCoord;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      aFragPos = vec3(uNormalMatrix * aVertexPosition);
      aNormal = vec3(uNormalMatrix * vec4(aVertexNormal, 1.0));
      aTexCoord = aTextureCoord;
    }
  `;

  // Fragment shader program

  const fsSource = `
    precision mediump float;

    varying highp vec3 aFragPos;
    varying highp vec3 aNormal;
    varying highp vec2 aTexCoord;
    
    // out vec4 fragColor;
    
    uniform sampler2D uSampler;
    vec3 lightColor = vec3(1.0, 1.0, 1.0);
    vec3 lightPos= vec3(0.0, 0.5, 0.8);
    vec3 viewPos= vec3(0.0, 0.0, 1.0);
    
    void main() {
        // 纹理颜色
        vec4 texColor = texture2D(uSampler, aTexCoord);
        if (texColor.a == 0.0) {
            texColor = vec4(1,1,1,1);
        }
        // 环境光
        float ambientStrength = 0.2;
        vec3 ambient = ambientStrength * lightColor;
        // 漫反射光
        vec3 norm = normalize(aNormal);
        vec3 lightDir = normalize(lightPos - aFragPos);
    
        float diff = max(dot(norm, lightDir), 0.0);
        vec3 diffuse = diff * lightColor;
    
        // 镜面光
        float specularStrength = 0.2;
        vec3 viewDir = normalize(viewPos - aFragPos);
        vec3 reflectDir = reflect(-lightDir, norm);
    
        float spec = pow(max(dot(viewDir, reflectDir), 0.0), 2.0);
        vec3 specular = specularStrength * spec * lightColor;
    
        gl_FragColor = vec4(ambient + diffuse + specular, 1.0) * texColor;
    }
  `;

2.2 添加招牌

博客标题“Rome753's Blog”做成招牌的样式,放在页面上方,它可以跟别的物体碰撞移动,又有一定的弹性能恢复原位。可以用Box2D里面的DistanceJoint实现。DistanceJoint可以让两个Body之间有固定距离,这个距离可以是0,也可以有弹性。

在tests/fallingImages.js里创建body时加上DistanceJoint用来固定招牌,创建3个防止它旋转。


        if (id == 1) {
            var def = new b2DistanceJointDef();
            def.collideConnected = false;
            def.frequencyHz = 2;
            def.dampingRatio = 1;
            def.length = 0;
            def.bodyA = groundBody;
            def.bodyB = body;
            def.localAnchorA.x = -1;
            def.localAnchorA.y = 10;
            def.localAnchorB.x = -1;
            def.localAnchorB.y = 0;
            world.CreateJoint(def);

            def.localAnchorA.x = 1;
            def.localAnchorB.x = 1;
            world.CreateJoint(def);

            def.localAnchorA.x = 0;
            def.localAnchorB.y = 1;
            world.CreateJoint(def);
        }

2.3 添加重力传感器

获取设备的加速度数据,将x,y方向的加速度设置成world的重力即可。

注意方向要取反,并且devicemotion只有在https网站下才会生效,并且不同设备还有兼容性问题。


    if (window.DeviceMotionEvent) {
        alert('Support DeviceMotionEvent')
        window.addEventListener('devicemotion', function(event) {
            if (world == null) {
                return;
            }
            var ax = event.accelerationIncludingGravity.x;
            var ay = event.accelerationIncludingGravity.y;
            var g = world.GetGravity();
            g.x = -ax * 2;
            g.y = -ay * 2;
            world.SetGravity(g);
        });
    } else {
        alert('Not Support DeviceMotionEvent')
    }
posted @ 2022-07-22 15:03  rome753  阅读(328)  评论(0编辑  收藏  举报