WebGL之ThreeJS的shader整理

906 阅读3分钟

什么是shader着色器

维基百科: shader一词被广泛应用于计算机图形学领域,是一种计算机程序,用于进行图像的浓淡处理(计算图像中的光照、亮度、颜色等)。开发人员可以使用着色器语言对GPU编程。构成最终图像的像素、顶点、纹理,图像的位置、色相、饱和度、亮度、对比度也都可以利用着色器中定义的算法进行动态调整。调用着色器的外部程序,也可以利用它向着色器提供的外部变量、纹理来修改这些着色器中的参数。

  • 固定渲染管线: —---—标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和采制参数。如果有了固定渲染管线,编写程序就比较容易了,因为所有的变换都是由固定渲染管线来完成的,但是缺点就是自由度低。固定渲染管线只能完成一些最基本的操作,如果想要做一些特殊的处理,就比较麻烦了,ThreeJS基本上就是固定渲染管线,当然也可以自己写shader到material中。

  • 可编辑渲染管线:—---—WebGL中不存在固定渲染管线,坐标变换必须全部由自己来做,这个记述了坐标变换的机制就叫做着色器(Shader),这样可以由程序员控制的机制叫做可编辑渲染管线。而着色器又有 处理几何图形顶点的顶点着色器和处理像素的片元着色器两种类型。由于WebGL中没有固定管线,所以必须准备好顶点着色器和片元着色器。

着色器简介

顶点着色器(Vertex shader) —— 顶点着色器用来描述顶点特性(如位置、颜色等)的程序。顶点是指二维或三维空间中的一个点,比如二维或三维图形的端点和交点。
片元着色器(Fragment shader) —— 进行逐片元处理的程序。片元是一个WebGL术语,可以将其理解为像素(图像的单元)。

着色器的处理方法

  • Uniforms是所有顶点都具有相同的值的变量。 比如灯光,雾,和阴影贴图就是被储存在uniforms中的数据。 用uniform修饰的变量既可以传入顶点着色器,也可以传入片元着色器,它们包含了哪些在整个渲染过程中保持不变的变量,比如,一个点光源的位置。
  • Attributes是与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据,比如每个顶点都具有一个颜色。Attributes变量和顶点的关系是一一对应的。attributes只可以在顶点着色器中访问。
  • Varyings是从顶点着色器传递到片元着色器的变量,为了确保这点,我们需要确保在两个着色器中变量的类型和命名完全一致。对于每一个片元,每一个varying的值将是相邻顶点值的平滑插值,一个经典的应用是法线向量,因为在计算光照的时候需要用到法线。 顶点着色器首先运行; 它接收attributes, 计算/操纵每个单独顶点的位置,并将其他数据(varyings)传递给片元着色器。片元(或像素)着色器后运行; 它设置渲染到屏幕的每个单独的“片元”(像素)的颜色。

着色器模拟光照

  • vertexShader
<script id="vertexShader" type="x-shader/x-vertex">
        //normal three.js内置法线, position three.js内置位置
	varying vec3 vNormal;
	void main() {
	  // 将vNormal设置为normal,normal是Three.js创建并传递给着色器的attribute变量
	  vNormal = normal;
	  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
	}
</script>

  • fragmentShader
<script id="fragmentShader" type="x-shader/x-vertex">
	varying vec3 vNormal;
	void main() {
	  // 定义光线向量
	  vec3 light = vec3(0.5,0.2,1.0);
	  // 归一化向量
	  light = normalize(light);
	  // 计算光线向量和法线向量的点积,如果点积小于0(即光线无法照到),就设为0
	  float dProd = max(0.0, dot(vNormal, light));
	  // 填充片元颜色
	  gl_FragColor = vec4(dProd, // R
	                      dProd, // G
	                      dProd, // B
	                      1.0);  // A
	} 
</script>

运行效果

QQ录屏20220517095750_.gif

完整代码

<!DOCTYPE html>
<html lang="en">
   <head>
      <title>threejs webgl - materials - hdr environment mapping</title>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
      <link type="text/css" rel="stylesheet" href="main.css">
   </head>
   <body>

      <div id="container"></div>


      <!--顶点着色器-->
      <script id="vertexShader1" type="x-shader/x-vertex">
         void main(){
            //最终顶点位置信息=投影矩阵*模型视图矩阵*每个顶点坐标
            gl_Position = projectionMatrix * modelViewMatrix * vec4( position , 1.0 );
         }
      </script>
      <!--片元着色器-->
      <script id="fragmentShader1" type="x-shader/x-fragment">
         void main(){
            //将任意一个像素色设置为该颜色RGBA(红色)
            gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 ); //r,g,b,a
         }
      </script>

      <script id="vertexShader" type="x-shader/x-vertex">
         //normal three.js内置法线, position three.js内置位置

         varying vec3 vNormal;
         void main() {
           // 将vNormal设置为normal,normal是Three.js创建并传递给着色器的attribute变量
           vNormal = normal;
           gl_Position = projectionMatrix *modelViewMatrix *vec4(position, 1.0);
         }
      </script>

      <script id="fragmentShader" type="x-shader/x-vertex">
         varying vec3 vNormal;
         void main() {
           // 定义光线向量
           vec3 light = vec3(0.5,0.2,1.0);
           // 归一化向量
           light = normalize(light);
           // 计算光线向量和法线向量的点积,如果点积小于0(即光线无法照到),就设为0
           float dProd = max(0.0, dot(vNormal, light));
           // 填充片元颜色
           gl_FragColor = vec4(dProd, // R
                          dProd, // G
                          dProd, // B
                          1.0);  // A
         }
      </script>




      <script type="importmap">
         {
            "imports": {
               "three": "../build/three.module.js"
            }
         }
      </script>

      <script type="module">

         import * as THREE from 'three';


         import { OrbitControls } from './jsm/controls/OrbitControls.js';


         let container, geometry, material;
         let camera, scene, renderer, controls;

         init();
         animate();

         function init() {

            container = document.createElement( 'div' );
            document.body.appendChild( container );

            camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 1000 );
            camera.position.set( 0, 100, 400 );

            scene = new THREE.Scene();
            scene.background = new THREE.Color( 0xffffff );
            let axesHelper = new THREE.AxesHelper(100);
            scene.add(axesHelper);

            renderer = new THREE.WebGLRenderer();
            renderer.physicallyCorrectLights = true;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;

            let position = new THREE.Vector3(50, 0, 0);       // 平移
            let quaternion = new THREE.Quaternion();    // 旋转
            quaternion.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), Math.PI / 6 );  /// 绕着哪一根法线旋转多少度
            let scale = new THREE.Vector3(0.2, 0.2, 0.8);       // 缩放

            let geometry = new THREE.BoxGeometry(30,30,30);
            // let material = new THREE.MeshLambertMaterial({ color: "green" })

            let material=new THREE.ShaderMaterial({   /// shader
               vertexShader:document.getElementById("vertexShader").textContent,
               fragmentShader:document.getElementById("fragmentShader").textContent
            });


            let box = new THREE.Mesh(geometry, material);
            scene.add(box)

            // window.addEventListener("click", function () {
            //     // box.matrix.makeRotationFromQuaternion( quaternion );  /// 要求顺序
            //     // box.matrix.setPosition( position );
            //     // box.matrix.setFromMatrixScale( scale )
            //
            //     box.matrix.compose(position, quaternion, scale)
            //     box.matrixAutoUpdate = false;
            //     // box.updateMatrix();
            // })

            let ambientLight = new THREE.AmbientLight("#ffffff", 1);
            scene.add(ambientLight);


            renderer.setPixelRatio( window.devicePixelRatio );
            renderer.setSize( window.innerWidth, window.innerHeight );
            container.appendChild( renderer.domElement );

            renderer.outputEncoding = THREE.sRGBEncoding;

            controls = new OrbitControls( camera, renderer.domElement );
            controls.minDistance = 50;
            controls.maxDistance = 800;

            window.addEventListener( 'resize', onWindowResize );

         }

         function onWindowResize() {

            const width = window.innerWidth;
            const height = window.innerHeight;

            camera.aspect = width / height;
            camera.updateProjectionMatrix();

            renderer.setSize( width, height );

         }

         function animate() {

            // setTimeout(function () {
               requestAnimationFrame( animate );
               render();
            // }, 2000)

         }

         function render() {

            scene.rotation.y += 0.005;

            renderer.render( scene, camera );
         }

      </script>

   </body>
</html>