threejs shader 入门(三)

154 阅读2分钟

没看过的朋友,记得先去看下入门一。

基础代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        canvas {
            width: 100%;
            height: 100%
        }
    </style>
</head>

<body>

</body>
<script src="./three.js">
</script>
<script> 
    const vshader = `
        void main(){
         gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0    );
        }
       `;
    const fshader = ` 
        void main(){
            gl_FragColor=vec4(1.0,0.0,0.0,1.0);
         }
        `;
    const scene = new THREE.Scene();
    const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10);
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    const geometry = new THREE.PlaneGeometry(2, 2);
    const material = new THREE.ShaderMaterial({
        vertexShader: vshader,
        fragmentShader: fshader,
    });

    const plane = new THREE.Mesh(geometry, material);
    scene.add(plane);

    camera.position.z = 3;
    
    onWindowResize();
   
    animate();
    window.addEventListener("resize", onWindowResize, false);
    function onWindowResize(event) {
        const aspectRatio = window.innerWidth / window.innerHeight;
        let width, height;
        if (aspectRatio >= 1) {
            width = 1;
            height = (window.innerHeight / window.innerWidth) * width;
        } else {
            width = aspectRatio;
            height = 1;
        }
        camera.left = -width;
        camera.right = width;
        camera.top = height;
        camera.bottom = -height;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
    }
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
    }
</script>

</html>

1.png

把屏幕切分。

const vshader = `
        varying vec2 v_uv; 
        void main(){
            v_uv=uv;
         gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0    );
        }
       `;
    const fshader = ` 
        varying vec2 v_uv;
        float rect(vec2 pt,vec2 wh,vec2 center){
            vec2 rangeMin=center  - (wh/2.0);
            vec2 rangeMax=center  + (wh/2.0);
            return  step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y);
        }
        void main(){
            vec2 xy= fract(v_uv*5.0);
            vec3 color=vec3(1.0,0.0,0.0)*rect(xy,vec2(0.5,0.5),vec2(0.5,0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

2.png fract(a) 返回小数部分,原理 fract(x)=x-floor(x)

接下来画一个圆 外加一条线

const fshader = ` 
        #define PI 3.14159265359
        #define PI2 6.28318530718
        varying vec2 v_uv;
        float circle(vec2 pt,vec2 rxry,float r ,float strokeWidth, float gradient){
            float len=length(rxry-pt); 
            float distance=abs(r-len);
            return smoothstep(strokeWidth/2.0,strokeWidth/2.0-gradient/2.0,distance);
        }
        float longLine(vec2 pt,vec2 center,float strokeWidth){
            vec2 d=pt -center;
            vec2 p=vec2(cos(PI/2.0),-sin(PI/2.0));
             float l = length(cross(vec3(d,0.0),vec3(p,0.0)))/length(p);
            //  两段代码等价
            // float l =abs(d.x*p.y-d.y*p.x)/length(p);
            return step(l,strokeWidth);
        }
        
        void main(){
            vec3 color=vec3(1.0,0.0,0.0)*(
                circle(v_uv,vec2(0.5),0.3,0.004,0.002)+
                longLine(v_uv,vec2(0.5),0.001)
            );
            gl_FragColor=vec4(color,1.0);
         }
        `;

3.png 函数说明

abs为返回绝对值

cross为叉乘 参数和返回值都说vec3 解释一下 float l =abs(d.x*p.y-d.y*p.x)/length(p);这段代码的含义。 d.x*p.y-d.y*p.x 其实是|d|*|p|*sinθ 在除以一个P的模长,就等于点d到直线P的长度。

接下来画一个长度等于圆半径的直线,线的起始点从圆心开始。

const fshader = ` 
        #define PI 3.14159265359
        #define PI2 6.28318530718
        varying vec2 v_uv;
        float circle(vec2 pt,vec2 rxry,float r ,float strokeWidth, float gradient){
            float len=length(rxry-pt); 
            float distance=abs(r-len);
            return smoothstep(strokeWidth/2.0,strokeWidth/2.0-gradient/2.0,distance);
        }
        float longLine(vec2 pt,vec2 center,float strokeWidth){
            vec2 d=pt -center;
            vec2 p=vec2(cos(PI/2.0),-sin(PI/2.0));
            // float l = length(cross(vec3(d,0.0),vec3(p,0.0)))/length(p);
            //  两段代码等价
            float l =abs(d.x*p.y-d.y*p.x)/length(p);
            return step(l,strokeWidth);
        }
        float line(vec2 pt,vec2 center,float r,float width,float bianyuan){
            vec2 d=pt-center;
            vec2 p=vec2(cos(PI/4.0),-sin(PI/4.0))*r;
            float d_p=clamp(dot(d,p)/dot(p,p),0.0,1.0);
            float h = length(d-p*d_p);
            return smoothstep(width,width-bianyuan,h);
        }
        void main(){
            vec3 color=vec3(1.0,0.0,0.0)*(
                circle(v_uv,vec2(0.5),0.3,0.004,0.002)+
                longLine(v_uv,vec2(0.5),0.001)+
                line(v_uv,vec2(0.5),0.3,0.002,0.001)
            );
            gl_FragColor=vec4(color,1.0);
         }
        `;

1.png

dot点乘 参数是vec2 ,返回值时float;

clamp(a x y) 返回中间大小的值 例如 clamp(5 1 4) 返回的是4 。 -3 1 2返回1 返回三个数的中间数,传参时x必须比y小。

解释一下这段话 float d_p=clamp(dot(d,p)/dot(p,p),0.0,1.0);

dot(d,p)=|d|*|p|*cosθ

dot(d,p)/dot(p,p)=|d|*cosθ/|p| 数学含义就是 d在p上的投影,占p长度的几分之几。

pd_p 就是d投影到p上对应的那个点, length(d-pd_p) 就等于离直线多远.

涉及到一些数学,如果实在看不懂,记住各个函数的用法即可。