利用贝塞尔曲线和着色器来写一个飞线

为了加深自己对着色器语言的认识,于是就着手写了一个简版的"飞线"。

做3D的或者做可视化的应该对这个词不陌生,一般会用在地理方面的3D需求上,废话不多说,先上今天的demo的gif图示:

看完效果,让我们继续一步一步地看是怎么实现的

一、写在着色器之前

三部分:

1⃣️地球

    // 添加地球
    var globeMesh;
    function addglobe() {
        var globeTextureLoader = new THREE.TextureLoader();
        globeTextureLoader.load('../texture/earth.jpeg', function (texture1) {
            console.log(texture1)
            var globeGgeometry = new THREE.SphereGeometry(60, 100, 100);
            var globeMaterial = new THREE.MeshStandardMaterial({map: texture1});
            globeMesh = new THREE.Mesh(globeGgeometry, globeMaterial);
            scene.add(globeMesh);
        });
    }

地球的旋转,直接在每次渲染的时候改rotation就好了,这里不啰嗦。

2⃣️路径线

    var flyline;
    function addline(){
        var curve = new THREE.CubicBezierCurve3(
            new THREE.Vector3( -70, 0, 0 ),
            new THREE.Vector3( -35, 100, 0 ),
            new THREE.Vector3( 35, 100, 0 ),
            new THREE.Vector3( 70, 0, 0 )
        );
        var points = curve.getPoints( 50 );

        var geometry = new THREE.BufferGeometry().setFromPoints( points );
        var material = new THREE.LineBasicMaterial( { color : 0xff0000 } );
        flyline = new THREE.Line( geometry, material );

        scene.add(flyline);
    }

利用three.js提供的贝塞尔曲线类结合材质生成了上面你所看到的贝塞尔曲线。

3⃣️夜空背景

    var scene;
    function initScene() {
        scene = new THREE.Scene();
        var bgTexture = new THREE.TextureLoader().load("../texture/starfiled.jpeg");
        scene.background = bgTexture;
    }

不难看出是给场景添加了一个背景贴图。

二、线条的材质替换成shader材质

我们可以看到,目前线条的材质是带有特定颜色的 THREE.LineBasicMaterial 材质。现在我们要将其换成shader材质。

    //创建ShaderMaterial纹理的函数
    function createMaterial(vertexShader, fragmentShader) {
        var vertShader = document.getElementById(vertexShader).innerHTML; //获取顶点着色器的代码
        var fragShader = document.getElementById(fragmentShader).innerHTML; //获取片元着色器的代码

        //配置着色器里面的attribute变量的值
        var attributes = {};
        //配置着色器里面的uniform变量的值
        var uniforms = {
            time: {type: 'f', value: 1.0}
        };

        var meshMaterial = new THREE.ShaderMaterial({
            uniforms: uniforms,
            defaultAttributeValues : attributes,
            vertexShader: vertShader,
            fragmentShader: fragShader,
            transparent: true
        });
        return meshMaterial;
    }
    

这段生成shader材质的代码其实很简单,注释都有,就不啰嗦了,因为这个简要的demo所用到的动画只需要一个参数,那么我们就只传一个参数吧(time)。

顶点着色器代码:

<!-- 顶点着色器 -->
<script id="vertex-shader" type="x-shader/x-vertex">

    void main(){
        vec3 posChanged = position;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(posChanged,1.0);
    }

</script>

代码只做了一件事,将物体顶点进行了矩阵变换,转化为屏幕上的点(上一篇博客也有)。

片元着色器代码:

<script id="fragment-shader-7" type="x-shader/x-fragment">
    uniform float time;
void main( void ) { float start = time; float end = start + 20.0; float opacity = 0.0;
if(gl_FragCoord.x > start && gl_FragCoord.x < end){ opacity = 1.0; } gl_FragColor = vec4(1.0,1.0,1.0,opacity); } </script>

因为片元着色器逐点绘制,所以对于gl_FragCoord这个内置变量,表示的就是屏幕上的点(一个window下,iframe下也是一个单独window);

gl_FragCoord 坐标是以左下角为(0,0),右上角为(屏宽,屏高),拿上述iframe来说,右上角就是(966,772);其实更准确的说法请参照下面这段话:

但今天不是来研究这些的,我们继续看着色器代码。

因为视图是每一帧绘制一次,假如现在是第199帧(time+=1.0 了199次,此时是200.0)

void main( void ) {
        float start = time;  //200
        float end = start + 20.0;  //220

        float opacity = 0.0;

        if(gl_FragCoord.x > start && gl_FragCoord.x < end){
            opacity = 1.0;
        }
        gl_FragColor = vec4(1.0,1.0,1.0,opacity);

    }

那么,上述曲线显示的部分,左端点的x轴就是200像素点,右端点的x轴就是220像素点。针对着色器传过来的顶点(逐个遍历),顶点x不在该范围内的顶点的颜色透明度都是0,也就是看不到,

到了下一帧(time值变化),这个可视区域的范围就又就变了,于是一帧一帧合起来就重现了我们刚开始看到的那张gif图:

原文地址:https://www.cnblogs.com/eco-just/p/11186440.html