threejs shader 入门(二)

107 阅读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 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 uniforms = {
    }

    const material = new THREE.ShaderMaterial({
    });

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

    camera.position.z = 1;

    onWindowResize();
    window.addEventListener("resize", onWindowResize, false);
    animate();

    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>

加入shader:

 const vshader = `
        void main(){
         gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }
       `;
       const fshader = ` 
        uniform vec2 u_resolution;
        uniform vec2 u_mouse;
        void main(){
            vec2 color= gl_FragCoord.xy/u_resolution;
            gl_FragColor=vec4(color,0.0,1.0);
         }
        `;
   ...
   //u_mouse还没用到,等会会使用
   const uniforms = { 
        u_mouse: { value: { x: 0, y: 0 } },
        u_resolution: { value: { x: 0, y: 0 } }
   }
   ...
   const material = new THREE.ShaderMaterial({
        vertexShader: vshader,
        fragmentShader: fshader,
         uniforms,
    });     
   ...
   function onWindowResize(event) {
        const aspectRatio = window.innerWidth / window.innerHeight;
       
        uniforms.u_resolution.value.x=window.innerWidth;
        uniforms.u_resolution.value.y=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);
    }
    ...

// gl_FragCoord:表示当前片元着色器处理的候选片元窗口相对坐标信息,是一个 vec4 类型的变量 (x, y, z, 1/w),其中 x, y 是当前片元的窗口坐标。例如gl_FragCoord.xy左下角为(0,0) 右上角 如果屏幕是1000,1000 则为(1000,1000)

1.png

接下来我们让画一个矩形,让矩形跟随鼠标运动。

const fshader = ` 
        uniform vec2 u_resolution;
        uniform vec2 u_mouse;
        float rect(vec2 pt,vec2 wh,vec2 center){
            vec2 rangeMax=center + wh/2.0;
            vec2 rangeMin=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(){
            vec3 color=vec3(1.0,0.0,0.0)*rect(gl_FragCoord.xy,vec2(30),u_mouse);
            gl_FragColor=vec4(color,1.0);
         }
        `;
       ...
    window.addEventListener('mousemove', move)
    function move(e) {
        uniforms.u_mouse.value.x = e.clientX
        uniforms.u_mouse.value.y = window.innerHeight-e.clientY
    }

e07ea0de-09d8-4726-8642-fd05a985ea2d.gif 接下来画一个圆

    const fshader = ` 
       float circle(vec2 xy,float r,vec2 center){
            return step(length(xy-center),r);
        }
        void main(){
            vec3 color=vec3(1.0,0.0,0.0)*circle(gl_FragCoord.xy,30.0,vec2(500,500));
            gl_FragColor=vec4(color,1.0);
         }
        `;

// length(a); 返回向量a的长度;比如 vec2 a=vec2(2,2); length(a)= 根号下(22 +22) 1.png

接下来在画一个模糊圆。

const fshader = ` 
        float circle(vec2 xy,float r,vec2 center){
            return step(length(xy-center),r);
        }
        float smoothCircle(vec2 xy,float r,vec2 center){
            return smoothstep(r,r*0.75,length(xy-center));
        }
        void main(){
            vec3 color=vec3(1.0,0.0,0.0)*(
                circle(gl_FragCoord.xy,30.0,vec2(100))
                +smoothCircle(gl_FragCoord.xy,30.0,vec2(100,200))
            );
            gl_FragColor=vec4(color,1.0);
         }
        `;

smoothstep(edg0, edg1, x); edg0<edg1时,edg0左边缘,edg1右边缘,使x在edg0和edg1区间内进行平滑处理。返回值在[0, 1]区间内,当x > edg1时,返回1,当x < edg0时,返回0,当x在edg0和edg1之间时,返回x; edg0>edg1时情况相反.

说人话就是 smoothstep(0,1,x)x为-1,返回0,x为2返回1. smoothstep(1,0,x为-1,返回1,x为2返回0. 2.png

接下来画一个空心圆

const fshader = ` 
        float circle(vec2 xy,float r,vec2 center){
            return step(length(xy-center),r);
        }
        float smoothCircle(vec2 xy,float r,vec2 center){
            return smoothstep(r,r*0.75,length(xy-center));
        }
        float hollowCircle(vec2 xy,float r,float strokeWidth,vec2 center){
            return step(r,length(xy-center))*step(length(xy-center),r+strokeWidth);
        }
        void main(){
            vec3 color=vec3(1.0,0.0,0.0)*(
                circle(gl_FragCoord.xy,30.0,vec2(100))
                +smoothCircle(gl_FragCoord.xy,30.0,vec2(100,200))
                +hollowCircle(gl_FragCoord.xy,60.0,60.0,vec2(500,500))
            );
            gl_FragColor=vec4(color,1.0);
         }
        `;

3.png

第二节课就到此结束了。记住gl_FragCoord, smoothstep, length的用法即可。