threejs shader (一)

591 阅读4分钟

学习threejs shader的应该是会用threejs的,所以threejs代码不做讲解。首先我们使用threejs代码构建基础场景

<!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>

1.png

shader代码分为片源着色器和顶点着色器,片元和顶点简单点来说就是颜色和位置的区别,我们先从片元着色器入手,后期讲顶点着色器,而这其中每个顶点都会执行着色器代码,比如有1000个顶点,下面的每个main函数都会执行1000次。

const vshader = `
        void main(){
         gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }
       `;
    const fshader = ` 
        void main(){
            gl_FragColor=vec4(0.0,1.0,0.0,1.0);
         }
        `;
   //把 ShaderMaterial 加上参数
   
   const material = new THREE.ShaderMaterial({
        vertexShader: vshader,
        fragmentShader: fshader,
    });     

gl_FragColor内置变量主要用来设置片元像素的颜色,vec4(r,g,b,a),前三个参数表示片元像素颜色值RGB,第四个参数是片元像素透明度A,1.0表示不透明,0.0表示完全透明。 注意 shader代码 是强类型语言,1是整数,1.0是浮点数,类型不同不能做加减乘除运算。 vec4是向量类型,例如 vec4 a=vec4(0.1,0.2,0.3,0.4);取值可以a.x=0.1;a.r=0.1;vec4 b=a*2.0=vec4(0.2,0.4,0.6,0.8);

XNX4E7)LB@%R`KQ4BW64WUW.png

接下来我们使用内置变量position来改变颜色。

    const vshader = `
        varying vec3 v_position; 
        void main(){
        v_position=position;
         gl_Position=projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }
       `;
    const fshader = ` 
        varying vec3 v_position;
        void main(){
            gl_FragColor=vec4(v_position,1.0);
         }
        `;

varying关键字,主要用于变量在两个着色器之间使用,顶点着色器的变量是不能在片元着色器中使用的,要使用顶点着色器中的变量需要加varying关键字,同时position是写好的内置变量,只存在于顶点着色器。因为z方向为0(threejs平面模型的z为0),在二维平面中,中间点是(0,0),左下角是(-1,-1) ,右上角(1,1)。position的值完全等于顶点在空间中的位置。

可以修改 const geometry = new THREE.PlaneGeometry(2, 2); 把(2,2)改成(1,1)或(0.5,0.5)看看效果。 3.png

接下来根据position的值画一个正方形。

const fshader = ` 
        varying vec3 v_position;
        float rect(vec2 pt,vec2 wh){
            vec2 rangeMax=wh/2.0;
            vec2 rangeMin=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(v_position.xy,vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

4.png

step(a, b);当b > a时, 返回1;当b < a时,返回0。

v_position如果等于vec3(0.2,0.2,0);那么 v_position.xy = vec2(0.2,0.2)。

step(rangeMin.x,pt.x)*step(pt.x,rangeMax.x)*step(rangeMin.y,pt.y)*step(pt.y,rangeMax.y); 这段代码含义其实就是判断传入的x,y 是否在-0.25~0.25之间(传入的宽高是0.5)

vec2(0.5) 是vec2(0.5,0.5)的简写方式。

接下来加一点点旋转。

const fshader = ` 
        varying vec3 v_position;
        float rect(vec2 pt,vec2 wh){
            // 0.7853 45°
            mat2 rotate = mat2( 
                cos(0.7853), -sin(0.7853), 
                sin(0.7853), cos(0.7853)
            );
            pt  = rotate*pt;
            vec2 rangeMax= wh/2.0;
            vec2 rangeMin= 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(v_position.xy,vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

7.png

旋转这里涉及到一些数学,讲我肯定是讲不清楚的,建议多百度多百度,有threejs基础,应该接触过矩阵的旋转平移缩放之类的吧。可以看看threejs官网Matrix4的旋转平移缩放。

接下来我们改变正方形的位置。

 const fshader = ` 
        varying vec3 v_position;
        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(v_position.xy,vec2(0.5),vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

5.png

接下来,来一点点旋转

const fshader = ` 
        varying vec3 v_position;
        float rect(vec2 pt,vec2 wh,vec2 center){
            // 0.7853 45°
            mat2 rotate = mat2( 
                cos(0.7853), -sin(0.7853), 
                sin(0.7853), cos(0.7853)
            );
            pt  =  rotate*(pt-center)+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(v_position.xy,vec2(0.5),vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

6.png

接下来让矩形根据时间旋转,也就是把0.7853替换为时间变量。

const fshader = ` 
        varying vec3 v_position;
        uniform float u_time;
        float rect(vec2 pt,vec2 wh,vec2 center){
            // 0.7853 45°
            mat2 rotate = mat2( 
                cos(u_time), -sin(u_time), 
                sin(u_time), cos(u_time)
            );
            pt  = rotate*(pt-center)+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(v_position.xy,vec2(0.5),vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;
     const clock = new THREE.Clock()
   
    ...
     const uniforms = {
        u_time: { value: 0 },
    }
    const material = new THREE.ShaderMaterial({
        vertexShader: vshader,
        fragmentShader: fshader,
        uniforms,
    });
    ...
    function animate() {
        requestAnimationFrame(animate);
        renderer.render(scene, camera);
        uniforms.u_time.value = clock.getElapsedTime()
    }

8.gif

代码说明 uniform float u_time;这段代码相当于声明全局变量u_time,而它的值来自于 const uniforms = { u_time: { value: 0 }, } uniform 可以在顶点和片元两个着色器中使用。

这是threejs 帮我们做的封装,我们要在shader代码使用js变量,只需要在THREE.ShaderMaterial({})传入即可,注意u_time:{value:0}是固定写法,value一定不能省略。 除了float 也可以穿其他类型的变量,在shader代码中声明对应的类型即可。

const uniforms = {
        u_time: { value: 0 },
        u_color: { value: new THREE.Color(0xffff00) },  //  uniform vec3 u_color;
        u_mouse: { value: { x: 0, y: 0 } },    //  uniform vec2 u_mouse;
        u_a: { value:new THREE.Vector3(0,1,0) }, //  uniform vec3 u_a;
    }

接下来我们换一种内置变量 uv 来实现画正方形; uv是vec2的类型,uv的值和position完全不同,uv的左下角为(0,0) 右上角为(1,1)且跟物体的空间几何无关。

 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 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(v_uv,vec2(0.5),vec2(0.5));
            gl_FragColor=vec4(v_uv,0.0,1.0);
         }
        `;

9.png

下面来一点点缩放。

const fshader = ` 
        varying vec2 v_uv;
        float rect(vec2 pt,vec2 wh,vec2 center){
            pt=(pt-center)*2.0+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(v_uv,vec2(0.5),vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

10.png

最后一个效果正方形改变正方形的旋转的中心点,就不上图了。

const fshader = ` 
        varying vec2 v_uv;
        uniform float u_time;
        float rect(vec2 pt,vec2 wh,vec2 center){
            mat2 rotate = mat2( 
                cos(u_time), -sin(u_time), 
                sin(u_time), cos(u_time)
            );
            pt=rotate*(pt-center-wh/2.0)+center+wh/2.0;
            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(v_uv,vec2(0.25),vec2(0.5));
            gl_FragColor=vec4(color,1.0);
         }
        `;

其中旋转涉及到一些数学知识,如果不懂也没关系,记住uv,position,varying,uniform step的用法即可。

但是最好还是推荐去学一下平移旋转缩放,对之后学习shader有很大的帮助。