总要有人来改变世界的,为什么不能是你呢

GPU编程shader之正余弦波和幂/指数函数

先上一个demo代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        html, body {
            margin: 0;
            height: 100%;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
<script src="../lib/three.min.js"></script>
<script src="../lib/stats.min.js"></script>
<script src="../lib/OrbitControl.js"></script>

<!-- 顶点着色器 -->
<script id="vertex-shader" type="x-shader/x-vertex">
    void main(){
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
    }
</script>

<script id="fragment-shader-8" type="x-shader/x-fragment">
    uniform float time;
    uniform vec2 resolution;

    void main( void ) {
        float x = gl_FragCoord.x;
        float y = gl_FragCoord.y;
        float fy = sin(x / 100. + time * 5.) * 100. + 250.;
        float opacity = 0.0;
        if(y > fy){
            opacity = 1.0;
        }
        gl_FragColor = vec4(1.,1.,1.,opacity);
    }
</script>




    <script type="module">
    var renderer;
    var clock ;
    function initRender() {
        clock = new THREE.Clock();
        renderer = new THREE.WebGLRenderer({antialias: true,alpha:true});
        renderer.setSize(window.innerWidth, window.innerHeight);
        //告诉渲染器需要阴影效果
        //renderer.shadowMap.enabled = true;
        //renderer.shadowMap.type = THREE.PCFSoftShadowMap; // 默认的是,没有设置的这个清晰 THREE.PCFShadowMap
        renderer.setClearColor(0xffffff);
        document.body.appendChild(renderer.domElement);
    }

    var camera;
    function initCamera() {
        camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(0, 0, 150);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
    }

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

    function initLight() {
        var hemisphereLight1 = new THREE.HemisphereLight(0xffffff, 0x444444, 2);
        hemisphereLight1.position.set(0, 200, 0);
        scene.add(hemisphereLight1);
    }



    var plane;
    function addplane(){
        var planeGeometry = new THREE.PlaneGeometry(450,250)
        var meshMaterial = createMaterial("vertex-shader", "fragment-shader-8");

        plane = new THREE.Mesh(planeGeometry,meshMaterial);
        scene.add(plane);
    }

    //创建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},
            scale: {type: 'f', value: 0.2},
            alpha: {type: 'f', value: 0.6},
            resolution: {type: "v2", value: new THREE.Vector2(window.innerWidth, window.innerHeight)}
        };

        var meshMaterial = new THREE.ShaderMaterial({
            uniforms: uniforms,
            defaultAttributeValues : attributes,
            vertexShader: vertShader,
            fragmentShader: fragShader,
            transparent: true
        });
        return meshMaterial;
    }
    
    //初始化性能插件
    var stats;
    function initStats() {
        stats = new Stats();
        document.body.appendChild(stats.dom);
    }

    //用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
    var controls;
    function initControls() {
        controls = new THREE.OrbitControls(camera, renderer.domElement);
        // 如果使用animate方法时,将此函数删除
        //controls.addEventListener( 'change', render );
        // 使动画循环使用时阻尼或自转 意思是否有惯性
        controls.enableDamping = true;
        //动态阻尼系数 就是鼠标拖拽旋转灵敏度
        //controls.dampingFactor = 0.25;
        //是否可以缩放
        controls.enableZoom = true;
        //是否自动旋转
        controls.autoRotate = false;
        controls.autoRotateSpeed = 3;
        //设置相机距离原点的最远距离
        controls.minDistance = 1;
        //设置相机距离原点的最远距离
        controls.maxDistance = 200;
        //是否开启右键拖拽
        controls.enablePan = true;
    }

    function render() {
        var delta = clock.getDelta();
        renderer.render(scene, camera);
        if(plane){
        plane.material.uniforms.time.value += 0.01;
        }
    }
    //窗口变动触发的函数
    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        render();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }
    function animate() {
        //更新控制器
        render();
        //更新性能插件
        stats.update();
        controls.update();
        requestAnimationFrame(animate);
    }
    function draw() {
        initScene();
        initCamera();
        initLight();
        initRender();


        addplane();

        initControls();
        initStats();
        animate();
        window.onresize = onWindowResize;
    }
    draw();

    </script>
    
</body>
</html>
View Code

一、页面结构介绍

后续的shader代码效果,也基本上会基于上述html页面代码;

我们看到,我们是用three的一个PlaneGeometry类画了一张面板(你可以把它看作我们今后写shader的画板),然后在这个面板上应用shader材质,这个材质呈现什么样的效果全靠我们去写这个shader啦。比如说,上述代码就是写了一个正余弦波,并且利用一个递增的变量实现波的移动效果。

虽然这个shader很简单,但是还是有必要解析一下,顺便温习一下高中学的三角函数。

二、demo中着色器代码解析

<!-- 顶点着色器 -->
<script id="vertex-shader" type="x-shader/x-vertex">
    void main(){
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
    }
</script>

<!-- 片元着色器 -->
<script id="fragment-shader-8" type="x-shader/x-fragment">
    uniform float time;
    uniform vec2 resolution;

    void main( void ) {
        float x = gl_FragCoord.x;
        float y = gl_FragCoord.y;
        float fy = sin(x / 100. + time * 5.) * 100. + 250.;
        float opacity = 0.0;
        if(y > fy){
            opacity = 1.0;
        }
        gl_FragColor = vec4(1.,1.,1.,opacity);
    }
</script>

顶点着色器就是把plane的顶点通过矩阵变换转成屏幕上的像素点;

然后在片元着色器中对这些顶点进行逐个着色(如果有1w个顶点,那么这个片元着色器代码就会跑1w次,因为是在GPU中并行地跑,所以很快),gl_FragCoord就是当前操作的顶点,而gl_FragColor算出来的颜色就是用来在该顶点输出到屏幕的颜色值;

所以片元着色器就是给你一个顶点gl_FragCoord(屏幕上的像素点),你来决定输出什么颜色gl_FragColor!

我们看这段main函数里面的代码:

float fy = sin(x / 100. + time * 5.) * 100. + 250.;
是不是很熟悉?

ok,到这里,正弦波就算出来了,但是颜色呢?因为根据每个x,我们算出对应正弦波上的y值,对应屏幕上每个x来说,都有n个y与之对应,那么上面的做法是,在正弦波上面的这些点的颜色的透明通道值为1.0,在下面的这些点的颜色的透明通道值为0,然后再加上横轴的偏移量θ,这里是变量time,由于time是变化的,所以就出现了上图所示的效果。

以此类推,我们还能写出指数函数、幂函数等的图像

二、幂函数

顶点着色器共用的,就不贴了

<!-- 片元着色器 -->
<script id="fragment-shader-8" type="x-shader/x-fragment">
    uniform float time;
    uniform vec2 resolution;

    void main( void ) {
        float x = gl_FragCoord.x;
        float y = gl_FragCoord.y;
        float fy = pow((x - time * 50.)/10.,2.0) + 200.;
        float opacity = 0.0;
        if(y > fy){
            opacity = 1.0;
        }
        gl_FragColor = vec4(1.,1.,1.,opacity);
    }
</script>

 

其余的曲线方程如果你有兴趣可以自己试着写写看

posted @ 2019-07-17 00:53  桔子桑  阅读(1061)  评论(0编辑  收藏  举报