【问题标题】:billboarding vertices in the vertex shader顶点着色器中的广告牌顶点
【发布时间】:2015-09-16 03:37:18
【问题描述】:

代码演示问题 (注释/取消注释顶点着色器中的gl_Position 行)

    var scene;
    var book;
    var shaderMaterial;

    var renderer = new THREE.WebGLRenderer({
        antialias: true
    });
    renderer.setClearColor(0x000000);
    document.body.appendChild(renderer.domElement);

    var camera = new THREE.PerspectiveCamera(55, 1, 0.1, 40000);


    window.onresize = function () {
        renderer.setSize(window.innerWidth, window.innerHeight);
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
    };
    window.onresize();

    scene = new THREE.Scene();

    camera.position.z = 25;
    camera.position.y = 15;
    scene.add(camera);


    var grid = new THREE.GridHelper(100, 10);
    scene.add(grid);


    var controls = new THREE.OrbitControls(camera);
    controls.damping = 0.2;
    var lettersPerSide = 16;

    function createGlpyhSheet() {

        var fontSize = 64;

        var c = document.createElement('canvas');
        c.width = c.height = fontSize * lettersPerSide;
        var ctx = c.getContext('2d');
        ctx.font = fontSize + 'px Monospace';
        var i = 0;

        for (var y = 0; y < lettersPerSide; y++) {
            for (var x = 0; x < lettersPerSide; x++, i++) {
                var ch = String.fromCharCode(i);
                ctx.fillText(ch, x * fontSize, -(8 / 32) * fontSize + (y + 1) * fontSize);
            }
        }

        var tex = new THREE.Texture(c);
        tex.flipY = false;
        tex.needsUpdate = true;

        return tex;
    }


    function createLabels(textArrays, positions) {
        //console.log(textArrays, positions);

        var master_geometry = new THREE.Geometry();


        for (var k = 0; k < textArrays.length; k++) {

            var geo = new THREE.Geometry();
            geo.dynamic = true;

            var str = textArrays[k];
            var vec = positions[k];
            //console.log(shaderMaterial);

            //console.log('str is', str, 'vec is', vec);


            var j = 0,
                ln = 0;

            for (i = 0; i < str.length; i++) {

                //console.log('creating glyph', str[i]);

                var code = str.charCodeAt(i);
                var cx = code % lettersPerSide;
                var cy = Math.floor(code / lettersPerSide);
                var oneDotOne = .55;

                geo.vertices.push(
                new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 0.05, 0).add(vec),
                new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 0.05, 0).add(vec),
                new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 1.05, 0).add(vec),
                new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 1.05, 0).add(vec));
                shaderMaterial.attributes.labelpos.value.push(vec);
                shaderMaterial.attributes.labelpos.value.push(vec);
                shaderMaterial.attributes.labelpos.value.push(vec);
                shaderMaterial.attributes.labelpos.value.push(vec);

                var face = new THREE.Face3(i * 4 + 0, i * 4 + 1, i * 4 + 2);
                geo.faces.push(face);
                face = new THREE.Face3(i * 4 + 0, i * 4 + 2, i * 4 + 3);
                geo.faces.push(face);

                var ox = (cx + 0.05) / lettersPerSide;
                var oy = (cy + 0.05) / lettersPerSide;
                var off = 0.9 / lettersPerSide;

                geo.faceVertexUvs[0].push([
                new THREE.Vector2(ox, oy + off),
                new THREE.Vector2(ox + off, oy + off),
                new THREE.Vector2(ox + off, oy)]);
                geo.faceVertexUvs[0].push([
                new THREE.Vector2(ox, oy + off),
                new THREE.Vector2(ox + off, oy),
                new THREE.Vector2(ox, oy)]);
                if (code == 10) {
                    ln--;
                    j = 0;
                } else {
                    j++;
                }
            }

            // i can only get this working with merge.
            // Building one giant geometry doesn't work for some reason
            master_geometry.merge(geo);

        }

        console.log(shaderMaterial);
        shaderMaterial.attributes.labelpos.needsUpdate = true;

        book = new THREE.Mesh(
        master_geometry,
        shaderMaterial);

        //book.doubleSided = true;
        scene.add(book);

    }


    var uniforms = {
        map: {
            type: "t",
            value: createGlpyhSheet()
        }
    };

    var attributes = {
        labelpos: {
            type: 'v3',
            value: []
        }
    };

    shaderMaterial = new THREE.ShaderMaterial({
        attributes: attributes,
        uniforms: uniforms,
        vertexShader: document.querySelector('#vertex').textContent,
        fragmentShader: document.querySelector('#fragment').textContent
    });
    shaderMaterial.transparent = true;
    shaderMaterial.depthTest = false;


    strings = [];
    vectors = [];
    var sizeOfWorld = 100;
    var halfSize = sizeOfWorld * 0.5;

    for (var i = 0; i < 500; i++) {

        strings.push('test' + i);
        var vector = new THREE.Vector3();
        vector.x = Math.random() * sizeOfWorld - halfSize;
        vector.y = Math.random() * sizeOfWorld - halfSize;
        vector.z = Math.random() * sizeOfWorld - halfSize;
        vectors.push(vector);

    }

    console.log('creating labels');
    createLabels(strings, vectors);

    function animate() {
        controls.update();
        renderer.render(scene, camera);
        requestAnimationFrame(animate, renderer.domElement);
    }

    animate();
        html {
            background-color: #ffffff;
        }
        * {
            margin: 0;
            padding: 0;
        }
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script id="vertex" type="text/x-glsl-vert">
    varying vec2 vUv;
    attribute vec3 labelpos;

    void main() {
        vUv = uv;


        // standard gl_Position.  Labels stay in the correct place, but do not billboard.
        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

        // this is the billboarding position as described by:
        // http://stackoverflow.com/questions/22053932/three-js-billboard-vertex-shader
    //gl_Position = projectionMatrix * (modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0) + vec4(position.x, position.y, 0.0, 0.0));

        // this gets a little closer
        //gl_Position = projectionMatrix * (modelViewMatrix * vec4(0.0, 0.0, position.z, 1.0) + vec4(position.x, position.y, 0.0, 0.0));

    }
</script>
<script id="fragment" type="text/x-glsl-frag">
    varying vec2 vUv;
    uniform sampler2D map;
    void main() {
        vec4 diffuse = texture2D(map, vUv);
        vec4 letters = mix(diffuse, vec4(1.0, 1.0, 1.0, diffuse.a), 1.0);
        gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * letters;
    }
</script>

我需要帮助我的场景中的广告牌标签。我的最终场景将有数百个标签,我想面对镜头。我无法通过单个网格几何体找到一种方法。我尝试了几种不同的gl_Position 方法来获得广告牌外观:

// standard gl_Position.  Labels stay in the correct place, but do not billboard.
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );

// this is the billboarding position as described by:
// http://stackoverflow.com/questions/22053932/three-js-billboard-vertex-shader
gl_Position = projectionMatrix * (modelViewMatrix * vec4(0.0, 0.0, 0.0, 1.0) + vec4(position.x, position.y, 0.0, 0.0));

// this gets a little closer
gl_Position = projectionMatrix * (modelViewMatrix * vec4(0.0, 0.0, position.z, 1.0) + vec4(position.x, position.y, 0.0, 0.0));

我的想法是向每个顶点发送一个着色器属性以帮助计算广告牌,这就是为什么我在顶点着色器中有一个label_pos 属性。

如果每个标签(由字符组成)分别添加到场景中,我可以获得我想要的确切外观。不幸的是,这导致每个渲染循环的绘制调用过多,因此将它们全部添加到单个几何体的原因。

非常感谢您对此提供任何帮助,谢谢。

【问题讨论】:

    标签: three.js glsl webgl shader vertex-shader


    【解决方案1】:

    我想你想要

    gl_Position = projectionMatrix * 
                  (modelViewMatrix * vec4(labelpos, 1) +
                   vec4(position.xy, 0, 0));
    

    并且您不需要将位置添加到顶点

    geo.vertices.push(
      new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 0.05, 0),
      new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 0.05, 0),
      new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 1.05, 0),
      new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 1.05, 0));
    

    否则你会在这个位置上两次。

    因为您的所有标签都在同一个网格中,所以只有 1 个绘制调用,这意味着您不会为每个标签获得不同的位置,除非您将其传入(您在 labelpos 中但您没有使用它)

    在这种情况下modelViewMatrix * vec4(0,0,0,1)modelViewMatrix[3] 相同,您所做的只是获取包含所有标签的模型的翻译。如果每个标签都是一个单独的网格并且有自己的矩阵,这将起作用,但由于您将它们全部放在一个网格中,它不会起作用。

    您的解决方法是在您已经包含的单独属性中传递每个标签的位置,您只需要使用它。

    modelViewMatrix * vec4(labelpos, 1)
    

    获取标签的根

    vec4(position.x, position.y, 0.0, 0.0)
    

    在视图空间的角落添加

    var scene;
        var book;
        var shaderMaterial;
    
        var renderer = new THREE.WebGLRenderer({
            antialias: true
        });
        renderer.setClearColor(0x000000);
        document.body.appendChild(renderer.domElement);
    
        var camera = new THREE.PerspectiveCamera(55, 1, 0.1, 40000);
    
    
        window.onresize = function () {
            renderer.setSize(window.innerWidth, window.innerHeight);
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
        };
        window.onresize();
    
        scene = new THREE.Scene();
    
        camera.position.z = 25;
        camera.position.y = 15;
        scene.add(camera);
    
    
        var grid = new THREE.GridHelper(100, 10);
        scene.add(grid);
    
    
        var controls = new THREE.OrbitControls(camera);
        controls.damping = 0.2;
        var lettersPerSide = 16;
    
        function createGlpyhSheet() {
    
            var fontSize = 64;
    
            var c = document.createElement('canvas');
            c.width = c.height = fontSize * lettersPerSide;
            var ctx = c.getContext('2d');
            ctx.font = fontSize + 'px Monospace';
            var i = 0;
    
            for (var y = 0; y < lettersPerSide; y++) {
                for (var x = 0; x < lettersPerSide; x++, i++) {
                    var ch = String.fromCharCode(i);
                    ctx.fillText(ch, x * fontSize, -(8 / 32) * fontSize + (y + 1) * fontSize);
                }
            }
    
            var tex = new THREE.Texture(c);
            tex.flipY = false;
            tex.needsUpdate = true;
    
            return tex;
        }
    
    
        function createLabels(textArrays, positions) {
            //console.log(textArrays, positions);
    
            var master_geometry = new THREE.Geometry();
    
    
            for (var k = 0; k < textArrays.length; k++) {
    
                var geo = new THREE.Geometry();
                geo.dynamic = true;
    
                var str = textArrays[k];
                var vec = positions[k];
                //console.log(shaderMaterial);
    
                //console.log('str is', str, 'vec is', vec);
    
    
                var j = 0,
                    ln = 0;
    
                for (i = 0; i < str.length; i++) {
    
                    //console.log('creating glyph', str[i]);
    
                    var code = str.charCodeAt(i);
                    var cx = code % lettersPerSide;
                    var cy = Math.floor(code / lettersPerSide);
                    var oneDotOne = .55;
    
                    geo.vertices.push(
                    new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 0.05, 0),
                    new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 0.05, 0),
                    new THREE.Vector3(j * oneDotOne + 1.05, ln * oneDotOne + 1.05, 0),
                    new THREE.Vector3(j * oneDotOne + 0.05, ln * oneDotOne + 1.05, 0));
                    shaderMaterial.attributes.labelpos.value.push(vec);
                    shaderMaterial.attributes.labelpos.value.push(vec);
                    shaderMaterial.attributes.labelpos.value.push(vec);
                    shaderMaterial.attributes.labelpos.value.push(vec);
    
                    var face = new THREE.Face3(i * 4 + 0, i * 4 + 1, i * 4 + 2);
                    geo.faces.push(face);
                    face = new THREE.Face3(i * 4 + 0, i * 4 + 2, i * 4 + 3);
                    geo.faces.push(face);
    
                    var ox = (cx + 0.05) / lettersPerSide;
                    var oy = (cy + 0.05) / lettersPerSide;
                    var off = 0.9 / lettersPerSide;
    
                    geo.faceVertexUvs[0].push([
                    new THREE.Vector2(ox, oy + off),
                    new THREE.Vector2(ox + off, oy + off),
                    new THREE.Vector2(ox + off, oy)]);
                    geo.faceVertexUvs[0].push([
                    new THREE.Vector2(ox, oy + off),
                    new THREE.Vector2(ox + off, oy),
                    new THREE.Vector2(ox, oy)]);
                    if (code == 10) {
                        ln--;
                        j = 0;
                    } else {
                        j++;
                    }
                }
    
                // i can only get this working with merge.
                // Building one giant geometry doesn't work for some reason
                master_geometry.merge(geo);
    
            }
    
            console.log(shaderMaterial);
            shaderMaterial.attributes.labelpos.needsUpdate = true;
    
            book = new THREE.Mesh(
            master_geometry,
            shaderMaterial);
    
            //book.doubleSided = true;
            scene.add(book);
    
        }
    
    
        var uniforms = {
            map: {
                type: "t",
                value: createGlpyhSheet()
            }
        };
    
        var attributes = {
            labelpos: {
                type: 'v3',
                value: []
            }
        };
    
        shaderMaterial = new THREE.ShaderMaterial({
            attributes: attributes,
            uniforms: uniforms,
            vertexShader: document.querySelector('#vertex').textContent,
            fragmentShader: document.querySelector('#fragment').textContent
        });
        shaderMaterial.transparent = true;
        shaderMaterial.depthTest = false;
    
    
        strings = [];
        vectors = [];
        var sizeOfWorld = 100;
        var halfSize = sizeOfWorld * 0.5;
    
        for (var i = 0; i < 500; i++) {
    
            strings.push('test' + i);
            var vector = new THREE.Vector3();
            vector.x = Math.random() * sizeOfWorld - halfSize;
            vector.y = Math.random() * sizeOfWorld - halfSize;
            vector.z = Math.random() * sizeOfWorld - halfSize;
            vectors.push(vector);
    
        }
    
        console.log('creating labels');
        createLabels(strings, vectors);
    
        function animate() {
            controls.update();
            renderer.render(scene, camera);
            requestAnimationFrame(animate, renderer.domElement);
        }
    
        animate();
    html {
                background-color: #ffffff;
            }
            * {
                margin: 0;
                padding: 0;
            }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/69/three.min.js"></script>
    <script src="https://cdn.rawgit.com/mrdoob/three.js/4862f5f1111346a957ac3e0cb0858be1568d0e03/examples/js/controls/OrbitControls.js"></script>
    <script id="vertex" type="text/x-glsl-vert">
        varying vec2 vUv;
        attribute vec3 labelpos;
    
        void main() {
            vUv = uv;
    
    
            gl_Position = projectionMatrix * 
                          (modelViewMatrix * vec4(labelpos, 1) +
                           vec4(position.xy, 0, 0));
    
        }
    </script>
    <script id="fragment" type="text/x-glsl-frag">
        varying vec2 vUv;
        uniform sampler2D map;
        void main() {
            vec4 diffuse = texture2D(map, vUv);
            vec4 letters = mix(diffuse, vec4(1.0, 1.0, 1.0, diffuse.a), 1.0);
            gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0) * letters;
        }
    </script>

    【讨论】:

    • 干得好!在一次绘制调用中呈现所有广告牌标签的 three.js 类会很不错。不过,它应该使用 BufferGeometry。
    • @gman 我非常感谢你的代码 sn-p 和清晰的解释。您为我节省了无数小时的试错时间。感谢您改进方法!一个类会很棒 - 我看到很多项目可以从这样一个易于使用的功能中受益。
    【解决方案2】:

    还值得看看 Three.js 和他们的 SpriteMaterial 是如何完成的:sprite_vert.glsl

    这是一个带注释的sn-p:

    // optional: pass 2D rotation angle as an uniform
    uniform float rotation;
    // optional: pass 2D center point as an uniform
    uniform vec2 center;
    
    // optional: use this define to scale the model according to distance from the camera
    #define USE_SIZEATTENUATION
    
    // [skipped includes]
    
    void main() {
        // discard rotation and scale
        vec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );
    
        // extract model's scale
        vec2 scale;
        scale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );
        scale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );
    
        // if not defined, keep model the same size regardless of distance from the camera
        #ifndef USE_SIZEATTENUATION
            bool isPerspective = isPerspectiveMatrix( projectionMatrix );
            if ( isPerspective ) scale *= - mvPosition.z;
        #endif
    
        // if center is not passed as uniform, create vec2 center = vec2(0.0);
    
        // aligned with the camera [and scaled]
        vec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;
    
        // if rotation is not passed as uniform, skip the next block
    
        // rotate 2D
        vec2 rotatedPosition;
        rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;
        rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;
    
        // billboard
        mvPosition.xy += rotatedPosition;
    
        gl_Position = projectionMatrix * mvPosition;
    
        // [skipped includes]
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多