WebGL光照、贴图和帧缓冲

简介: WebGL光照、贴图和帧缓冲

1.光照

1)光源类型

  • 平行光
  • 点光源
  • 环境光

2)反射光颜色

  • 物体表面的颜色(基底色)和反射特性
  • 入射光的颜色与方向

3)反射类型

  • 漫反射:针对平行光和点光源而言
  • 环境反射:针对环境光而言

4)平行光下漫反射光颜色

漫反射光颜色=入射光颜色x基底色xcos(入射角)

  • 入射角:入射光与物体表面法线的夹角

平行光下漫反射光颜色=入射光颜色x基底色xcos(光线方向.法线方向)


 attribute vec3 aColor;//基底色
      attribute vec3 aNormal;//法向量
      attribute vec3 aLightColor;//平行光光线颜色
      attribute vec3 aLightDirection;//光线方向

 //对法向量进行归一化
  vec3 normal = normalize(aNormal.xyz);
 //计算光线方向和法向量的点积
float nDotL = max(dot(aLightDirection, normal), 0.0);
//计算漫反射颜色
vec3 diffuse = aLightColor * aColor.rgb * nDotL;
 //默认立方体为红色
       gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aColor'), 1.0, 0.0, 0.0);
       //光线颜色
       gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aLightColor'), 0.8, 0.8, 0.8);
       //光照方向
       gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aLightDirection'),1.0, 0.5, 2.0);

image.png

5) 环境反射光颜色

环境反射光颜色=入射光颜色x基底色

attribute vec3 aColor;//基底色
 attribute vec3 aAmbientColor;//环境光色
         //计算环境光颜色
         vec3 ambient=aAmbientColor*aColor.rgb;
 //默认立方体为红色
        gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aColor'), 1.0, 0.0, 0.0);
 //环境光线颜色
        gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aAmbientColor'), 0.3, 0.3, 0.3);

6)物体表面的反射光颜色

物体表面的反射光颜色= 漫反射光颜色+环境反射光颜色

          vColor =vec3(diffuse+ambient);

image.png

光线颜色=光线每个色值x光线强度,如vec3(1.0, 1.0, 0.0)的黄色光,强度为0.5,则结果为vec3(0.5,0.5,0.0)

7)点光源反射光颜色

点光源反射光颜色,与平行光漫反射类似,只不过需要根据点光源位置计算每个点的入射点光线的方向

 attribute vec3 aColor;//基底色
      attribute vec3 aNormal;//法向量
      attribute vec3 aLightColor;//光线颜色 
      attribute vec3 aPointLight;//点光源位置


       //计算每个点的光线方向
        vec3 lightDirection = normalize( aPointLight-gl_Position.xyz);
         //对法向量进行归一化
         vec3 normal = normalize(aNormal.xyz);
         //计算光线方向和法向量的点积
       float nDotL = max(dot(lightDirection, normal), 0.0);
         //计算漫反射颜色
         vec3 diffuse = aLightColor * aColor.rgb * nDotL;
 //默认立方体为红色
        gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aColor'), 1.0, 0.0, 0.0);      
        //光线颜色
        gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aLightColor'), 0.8, 0.8, 0.8);
        //点光源位置
        gl.vertexAttrib3f(gl.getAttribLocation(gl.program, 'aPointLight'), 2.0, 2.0, 2.0);

image.png

8)光源不跟着物体动

 uniform mat4 uNormalMatrix;//法线矩阵


  //对法向量进行归一化
         vec3 normal = normalize((uNormalMatrix*vec4(aNormal,1.0)).xyz);
let modelViewMatrix = initModelViewMatrix(gl, settings);
          //计算法线矩阵,使得光线不要跟着物体动
          let normalMatrix = mat4.create();
          mat4.invert(normalMatrix, modelViewMatrix);
          mat4.transpose(normalMatrix, normalMatrix);

          gl.uniformMatrix4fv(
            gl.getUniformLocation(gl.program, 'uNormalMatrix'),
            false,
            normalMatrix
          );

20230512\_093010.gif

2.贴图

1)简单示例

(1) 顶点着色器

precision mediump float;
      attribute vec2 aPosition;
      attribute vec2 aTexCoord;
      varying   vec2 vTexCoord;

      void main() {
        gl_Position =   vec4(aPosition,1.0,1.0);
        vTexCoord=aTexCoord;
      }

(2)片元着色器

 precision mediump float;
              varying  vec2 vTexCoord;
      uniform sampler2D uTex;
            void main() {
              gl_FragColor =  texture2D(uTex, vTexCoord) ;
            }

(3)加载贴图

function isPowerOf2(value) {
   
   
  return (value & (value - 1)) == 0;
}
//加载贴图
function initTexture(gl, url) {
   
   
  return new Promise((resolve) => {
   
   
    var image = new Image();
    image.src = url; //必须同域
    image.onload = () => {
   
   
      const texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);
      if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
   
   
        gl.generateMipmap(gl.TEXTURE_2D);
      } else {
   
   
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      }
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

      resolve({
   
    texture, image });
    };
  });
}

(4)赋值画图,图片会填充满整个webgl

const positions = [
          [-1.0, 1.0],
          [1.0, 1.0],
          [-1.0, -1.0],
          [-1.0, -1.0],
          [1.0, 1.0],
          [1.0, -1.0]
        ].reverse();
        initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 2);

        const textureCoord = [
          [0.0, 0.0],
          [1.0, 0.0],
          [0.0, 1.0],
          [0.0, 1.0],
          [1.0, 0.0],
          [1.0, 1.0]
        ].reverse();

        initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoord)), 2);
      var {
   
    texture, image } = await initTexture(gl, 'images/a.jpg');
        //开启正反面
        gl.enable(gl.CULL_FACE);
        function drawScene() {
   
   
          cleanGl(gl);

          //画三角形
          gl.drawArrays(gl.TRIANGLES, 0, positions.length);
        }
        drawScene();

gl.enable(gl.CULL_FACE):开启正反面,正面三角形的顶点顺序是逆时针方向, 反面三角形是顺时针方向。

a.jpg

2)调换rgb顺序,使得图片变色

gl_FragColor =  texture2D(uTex, vTexCoord).bgra;

image.png

gl_FragColor =  texture2D(uTex, vTexCoord).ggba;

image.png

3)维持图片比例大小

  • 顶点着色器
precision mediump float;
      attribute vec2 aPosition;
      attribute vec2 aTexCoord;
      attribute vec2 resolution;//webgl宽高
      varying   vec2 vTexCoord;

      void main() {
      //图片的坐标转换成[-1.0,1.0]范围
        vec2 pos=aPosition/resolution;
       gl_Position = vec4(pos.x*2.0-1.0,1.0-pos.y*2.0, 0.0, 1);
        vTexCoord=aTexCoord;
      }
  • 赋值
const textureCoord = [
          [0.0, 0.0],
          [1.0, 0.0],
          [0.0, 1.0],
          [0.0, 1.0],
          [1.0, 0.0],
          [1.0, 1.0]
        ].reverse();

        initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoord)), 2);
        var { texture, image } = await initTexture(gl, 'images/a.jpg');
    //获取图片各顶点位置
        function getImageRect(x, y, width, height) {
          var x1 = x;
          var x2 = x + width;
          var y1 = y;
          var y2 = y + height;
          return [
            [x1, y1],
            [x2, y1],
            [x1, y2],
            [x1, y2],
            [x2, y1],
            [x2, y2]
          ].reverse();
        }
        const positions = getImageRect(0, 0, image.width, image.height);

        initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 2);
        //webgl大小
        gl.vertexAttrib2f(
          gl.getAttribLocation(program, 'resolution'),
          gl.canvas.width,
          gl.canvas.height
        );

image.png

4)给立方体加上贴图

  • 顶点着色器
 precision mediump float;
      attribute vec3 aPosition;
      uniform mat4 uProjectionMatrix;
      uniform mat4 uModelViewMatrix;
      attribute vec2 aTexCoord;
      varying  vec2 vTexCoord;

      void main() {
        gl_Position = uProjectionMatrix*uModelViewMatrix * vec4(aPosition,1.0);
        vTexCoord=aTexCoord;
      }
  • 片元着色器
precision mediump float;
      uniform sampler2D uTex;
      varying   vec2 vTexCoord;

      void main() {
        gl_FragColor =  texture2D(uTex, vTexCoord) ;
      }
  • 参数赋值
//设定texture
var {
   
    texture, image } = await initTexture(gl, 'images/a.jpg');
//texture索引
        gl.activeTexture(gl.TEXTURE0);
        gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);
        initArrBuffer(gl, 'aPosition', new Float32Array(flatArr(positions)), 3);

        initElmtBuffer(gl, new Uint16Array(flatArr(indices)));

        initArrBuffer(gl, 'aTexCoord', new Float32Array(flatArr(textureCoordinates)), 2);

20230509\_152622.gif

3.动态canvas贴图

1)初始化canvas贴图

  function initCanvasTex(gl) {
   
   
        const canvas = document.createElement('canvas');
        canvas.width = 500;
        canvas.height = 500;
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = 'rgb(255,0,0)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

        gl.bindTexture(gl.TEXTURE_2D, texture);

        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
        return {
   
    texture, canvas };
      }

       const {
   
    texture, canvas } = initCanvasTex(gl);
        gl.activeTexture(gl.TEXTURE0);
        gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);

2)更新canvas并更新到贴图上

let count = 0;
        async function drawScene() {
   
   
          cleanGl(gl);
          const ctx = canvas.getContext('2d');
          //画canvas
          if (count >= 100) {
   
   
            count = 0;
            //清空
            ctx.clearRect(0, 0, canvas.width, canvas.height);   
            //变换底色
            // ctx.fillStyle =`rgb(${Math.random() * 255},${Math.random() * 255},${
   
   
            //   Math.random() * 255
            // })`;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
          }
          ctx.lineWidth = 3;
          ctx.strokeStyle = 'white';
          //随机画线
          ctx.beginPath();
          ctx.moveTo(Math.random() * 500, Math.random() * 500);
          ctx.lineTo(Math.random() * 500, Math.random() * 500);
          ctx.stroke();

          ctx.fillStyle = `rgb(${Math.random() * 255},${Math.random() * 255},${
     
     
            Math.random() * 255
          })`;
          count++;
          //同步更新到贴图上
          gl.bindTexture(gl.TEXTURE_2D, texture);
          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
//...
}

20230513\_202957.gif

可以尝试一下透明底色

  ctx.clearRect(0, 0, canvas.width, canvas.height); 
ctx.fillStyle = `rgba(0,0,0,0)`;

20230513\_151340.gif

4.视频贴图

1)初始化视频贴图

function initVideoTex(gl, url) {
   
   
        const video = document.createElement('video');

        let playing = false;
        let timeupdate = false;

        video.playsInline = true;
        video.muted = true;
        video.loop = true;

        video.src = url;
        video.play();

        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);

        //创建默认蓝色像素到贴图,给video贴图加载时间
        gl.texImage2D(
          gl.TEXTURE_2D,
          0,
          gl.RGBA,
          1,
          1,
          0,
          gl.RGBA,
          gl.UNSIGNED_BYTE,
          new Uint8Array([0, 0, 255, 255])
        );

        // 将包裹(wrapping)设置为边缘分割(clamp to edge)
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
        return {
   
    video, texture };
      }
      var {
   
    video, texture } = await initVideoTex(gl, 'images/test.mp4');
        gl.activeTexture(gl.TEXTURE0);
        gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);

2)更新视频贴图

     function drawScene() {
   
   
          cleanGl(gl);
          //更新视频贴图
          gl.bindTexture(gl.TEXTURE_2D, texture);
          gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video);
          //……
          }

20230513\_144418.gif

5.帧缓冲成贴图

帧缓冲用来允许渲染到纹理或渲染到渲染缓冲中的

1)初始化帧缓冲

function initFrameBuffer(gl) {
   
   
        const texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        //帧缓冲生成的贴图宽高要与截取webgl的宽高一致
        gl.texImage2D(
          gl.TEXTURE_2D,
          0,
          gl.RGBA,
          gl.canvas.width,
          gl.canvas.height,
          0,
          gl.RGBA,
          gl.UNSIGNED_BYTE,
          null
        );

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        var framebuffer = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);

        framebuffer.texture = texture;

        var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

        if (gl.FRAMEBUFFER_COMPLETE !== e) {
   
   
          console.log('Frame buffer object is incomplete: ' + e.toString());

          return;
        }
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.bindTexture(texture, null);
        return {
   
    framebuffer, texture };
      }
        const {
   
    framebuffer, texture: frameTex } = initFrameBuffer(gl);

帧缓冲生成的贴图宽高要与截取webgl的宽高一致,否则有可能物体在画面外,看不见,我这里默认了整个场景

2)画场景

先画需要帧缓冲生成的贴图,然后再画物体,将前面的贴图赋值到物体上

 function drawScene() {
   
   
          if (radius > Math.PI * 2) {
   
   
            radius = 0;
          }
          radius += 0.02;
          gl.enable(gl.CULL_FACE);
          gl.enable(gl.DEPTH_TEST);
          //画帧缓冲
          gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
          gl.bindTexture(gl.TEXTURE_2D, texture);
          gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
          gl.clearColor(1, 1, 0, 1); // 黄色底色
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

          drawCube();

          //画立方体
          gl.bindFramebuffer(gl.FRAMEBUFFER, null);
          //使用之前生成的贴图
          gl.bindTexture(gl.TEXTURE_2D, frameTex);
          gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
          gl.clearColor(0, 0, 0, 1); // 黑色底色
          gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

          drawCube();

          requestAnimationFrame(drawScene);
        }

3)立方体的贴图

 gl.activeTexture(gl.TEXTURE0);
   gl.uniform1i(gl.getUniformLocation(gl.program, 'uTex'), 0);

20230513\_210253.gif

github地址

https://github.com/xiaolidan00/my-webgl

参考

  • https://webglfundamentals.org/
  • 《WebGL编程指南》
  • 《webGL 3D开发实战详解 第2版》
相关文章
|
5月前
|
图形学
【unity小技巧】实现FPS武器的瞄准放大效果(UGUI实现反向遮罩,全屏遮挡,局部镂空效果)
【unity小技巧】实现FPS武器的瞄准放大效果(UGUI实现反向遮罩,全屏遮挡,局部镂空效果)
73 1
|
5月前
|
图形学
【unity小技巧】创建FPS击中物体URP贴花
【unity小技巧】创建FPS击中物体URP贴花
54 0
|
6月前
[光源频闪] Basler相机光源频闪设置操作说明
[光源频闪] Basler相机光源频闪设置操作说明
255 0
|
存储 数据可视化 开发者
位移贴图和法线贴图的区别
位移贴图和法线贴图在原理、使用范围、精度和复杂度、生成方式以及存储方式等方面存在差异,开发者可以根据具体需求选择适合的贴图技术。
168 0
webgl图形变换、投影与摄像机
入坑webgl,从图形变换、投影与摄像机开始,基操学得好,以后才能怎么炫酷怎么来~快快快,点进来~~
webgl图形变换、投影与摄像机
【Three.js入门】纹理及其常用属性、透明纹理、环境遮挡贴图与强度
【Three.js入门】纹理及其常用属性、透明纹理、环境遮挡贴图与强度
472 0
|
图形学
【Three.js入门】纹理加载进度、环境贴图、经纬线映射贴图与高动态范围成像HDR
【Three.js入门】纹理加载进度、环境贴图、经纬线映射贴图与高动态范围成像HDR
411 0
【Three.js入门】标准网格材质、置换贴图、粗糙度贴图、金属贴图、法线贴图
【Three.js入门】标准网格材质、置换贴图、粗糙度贴图、金属贴图、法线贴图
430 0
|
算法 定位技术
WebGL开发:加载图片配准
WebGL开发:加载图片配准
173 0
|
并行计算 iOS开发 MacOS
Metal每日分享,调节亮度滤镜效果
Metal每日分享,调节亮度滤镜效果
Metal每日分享,调节亮度滤镜效果