阴影实现

Posted 就当笔记吧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了阴影实现相关的知识,希望对你有一定的参考价值。

相机控制

实现的功能:

1) 相机跟随, 2) 围绕Player旋转(左右和上下调整相机),3) 镜头zoom

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class ThirdPersonCamera : MonoBehaviour


    /// 摄像机和碰撞体的交点向摄像机的观察点移动的距离
    private const float Collision_Return_Dis = 0.5f;

    private Transform m_CachedCamera;

    public Transform m_Player;

    public Vector3 m_Offset = new Vector3(0.0f, 0.5f, 0.0f); //到原点的距离为旋转半径

    public float m_HorizontalAimingSpeed = 10.0f; //水平方向旋转速度
    public float m_VerticalAimingSpeed = 10.0f; //垂直方向旋转速度

    private float m_AngleH = 0.0f; //水平方向旋转角度

    public float m_MaxVerticalAngle = -5.0f;
    public float m_MinVerticalAngle = -50.0f;
    private float m_AngleV = -30.0f;

    public float m_MinDistance = 1.0f; //最小zoom距离
    public float m_MaxDistance = 6.0f; //最大zoom距离
    private float m_Distance = 0.0f; //当前zoom距离
    public float m_ZoomSpeed = 2.0f;

    void Awake()
    
        m_CachedCamera = GetComponent<Camera>().transform;
        m_Distance = (m_MinDistance + m_MaxDistance) * 0.5f;
        Debug.Log($"Awake: dist: m_Distance");
    

    void Update()
    
        float mousex = 0;
        float mousey = 0;
        if (Input.GetMouseButton(0))
        
            mousex = Input.GetAxis("Mouse X");
            mousey = Input.GetAxis("Mouse Y");
        

        m_AngleH += (Mathf.Clamp(mousex, -1.0f, 1.0f) * m_HorizontalAimingSpeed);

        m_AngleV += (Mathf.Clamp(mousey, -1.0f, 1.0f) * m_VerticalAimingSpeed);
        m_AngleV = Mathf.Clamp(m_AngleV, m_MinVerticalAngle, m_MaxVerticalAngle);

        float zoom = Input.GetAxis("Mouse ScrollWheel");
        m_Distance -= zoom * m_ZoomSpeed;
        m_Distance = Mathf.Clamp(m_Distance, m_MinDistance, m_MaxDistance);

        Quaternion animRotation = Quaternion.Euler(-m_AngleV, m_AngleH, 0.0f); //鼠标上移x角度变小, 下移变大
        m_CachedCamera.rotation = animRotation;

        Vector3 cameraBack = animRotation * Vector3.back; //用四元数计算Camera的back, 等同于mCamera.forward(z取反)
        cameraBack.Normalize();

        Quaternion camYRotation = Quaternion.Euler(0.0f, m_AngleH, 0.0f);
        Vector3 lookatpos = m_Player.position + camYRotation * m_Offset;
        m_CachedCamera.position = lookatpos + cameraBack * m_Distance;

        // 计算碰撞后的摄像机点
        RaycastHit rayhit;
        bool hit = Physics.Raycast(lookatpos, cameraBack, out rayhit, m_Distance);
        if (hit)
        
            // 屏蔽角色碰撞
            bool charcol = rayhit.collider as CharacterController;
            if (!charcol)
            
                m_CachedCamera.position = rayhit.point - cameraBack * Collision_Return_Dis;

                // 距离修正在范围内(1, 避免摄像机穿插进入角色)
                float distance = Vector3.Distance(m_CachedCamera.position, lookatpos);
                distance = Mathf.Clamp(distance, m_MinDistance, m_MaxDistance);
                m_CachedCamera.position = lookatpos + cameraBack * distance;
            
        
    

 

角色控制

基于CharacterController的行走和跳跃

using UnityEngine;

public class PlayerControl : MonoBehaviour

    private const float Dir_Speed = 10;
    private const float Gravity = -10;

    public CharacterController m_characterCtrl;

    public Animator m_Animator;
    public float m_MoveSpeed = 2;
    public float m_JumpSpeed = 4;

    private bool m_WasGrounded; //上一帧在地面上
    private bool m_IsGrounded; //当前在地面上
    private Vector3 m_CurMoveDir = Vector3.zero;

    private float m_JumpTimeStamp = 0;
    private float m_MinJumpInterval = 0.25f; //控制跳跃间隔
    private bool m_JumpInput = false; //控制一帧触发1次

    private Vector3 m_VerticalSpeed = Vector3.zero;
    private Vector3 m_DownChecker = new Vector3(0, -0.002f, 0); //在地面上时, 不断检测是否还在地面上用

    private void Awake()
    
        if (!m_characterCtrl)
            m_characterCtrl = GetComponent<CharacterController>();

        if (!m_Animator)
            m_Animator = GetComponent<Animator>();
    

    private void Update()
    
        if (!m_JumpInput && Input.GetKey(KeyCode.Space))
        
            m_JumpInput = true;
        
    

    private void FixedUpdate()
    
        m_Animator.SetBool("Grounded", m_IsGrounded);
        //if (m_characterCtrl.isGrounded) Debug.Log($"fixed: true");
        DirectUpdate();

        m_WasGrounded = m_IsGrounded;
        m_JumpInput = false;
    

    private void DirectUpdate()
    
        float v = Input.GetAxis("Vertical");
        float h = Input.GetAxis("Horizontal");
        Transform camera = Camera.main.transform;

        Vector3 inputDir = camera.forward * v + camera.right * h; //摇杆上下为相机forward方向, 左右为相机right方向
        float inputForce = inputDir.magnitude; //区分力度的情况
        inputDir.y = 0;

        if (inputDir != Vector3.zero)
        
            m_CurMoveDir = Vector3.Slerp(m_CurMoveDir, inputDir, Time.deltaTime * Dir_Speed);

            transform.rotation = Quaternion.LookRotation(m_CurMoveDir);
            var flags = m_characterCtrl.Move(m_CurMoveDir * m_MoveSpeed * Time.deltaTime);
            //Debug.Log($"flags: flags");
            m_Animator.SetFloat("MoveSpeed", inputForce); //力度
        
        else
        
            m_Animator.SetFloat("MoveSpeed", 0);
        

        JumpingAndLanding();
    

    private void JumpingAndLanding()
    
        bool jumpCooldownOver = (Time.time - m_JumpTimeStamp) >= m_MinJumpInterval;

        if (jumpCooldownOver && m_IsGrounded && m_JumpInput) //地面上按下跳, 多次跳要有一定间隔
        
            m_JumpTimeStamp = Time.time;
            m_IsGrounded = false;
            m_VerticalSpeed.y = m_JumpSpeed;
        

        if (!m_IsGrounded) //不在地面上时, 要不断下落
        
            m_characterCtrl.Move(m_VerticalSpeed * Time.deltaTime);
            m_IsGrounded = m_characterCtrl.isGrounded;
            if (m_IsGrounded)
            
                m_VerticalSpeed.y = 0;
            
            else
            
                m_VerticalSpeed.y += Gravity * Time.deltaTime;
            
            //Debug.Log($"isGround: m_characterCtrl.isGrounded");
        
        else //在地面上时, 也要不断检测是否需要下落(比如: 从一个高的平台走到低的平台)
        
            var flags = m_characterCtrl.Move(m_DownChecker); //在地面上的时候, 走不下去的, 会返回below, 并回弹
            if (flags == CollisionFlags.None)
            
                m_IsGrounded = false;
                m_VerticalSpeed.y = 0;
            
        

        if (!m_WasGrounded && m_IsGrounded) //之前不在地面上, 现在在地面上了
        
            m_Animator.SetTrigger("Land");
        

        if (!m_IsGrounded && m_WasGrounded) //之前在地面上的, 现在不在地面上了
        
            m_Animator.SetTrigger("Jump");
        
    


模型资源: Character Pack: Free Sample | 3D 人形角色 | Unity Asset Store

地面资源以及代码参考: GitHub - xieliujian/UnityDemo_PlanarShadow: UnityDemo平面阴影

 

最终效果

可以看到目前还没有阴影,后面会在这基础上展示在平面上以及平台上各种阴影的表现情况

 

其他参考 

Unity中角色着地功能_terric的博客-CSDN博客

Unity中CharacterController.IsGrounded的值在True和False中反复出现导致跳跃键失灵的原因及解决方案_unity isgrounded_H2ojunjun的博客-CSDN博客

CharacterController 角色控制器实现移动和跳跃 - 盘子脸 - 博客园 (cnblogs.com)

 

WebGL入门(四十二)-使用(FBO)实现阴影效果

1. demo效果

在这里插入图片描述

2. 相关知识点

现实生活中阴影无处不在,在三维世界中想要获得立体感,阴影必不可少,这里学习通过 阴影贴图 (shadow map) 实现阴影绘制的方法,阴影贴图又称 深度贴图(depth map)

2.1 阴影如何产生

在这里插入图片描述

如上图,坐标原点出有一光源,中间有一个三角形,右侧有一投影面,如果三角形上有一点P1,从光源发出一条光线经过P1点,在投影面的上投射的点是P2,这时P2点的位置就在阴影中,同理无数的光线穿过三角形会投射出一个区域,即上图中紫色区域,这个区域就是三角形的投影,在这个区域外则没有阴影

2.2 阴影实现原理

阴影实现原理并不难,分为两步:准备阴影贴图和阴影映射(阴影绘制)

2.2.1 准备阴影贴图

准备阴影贴图需要一对专门的着色器,即顶点着色器和片元着色器。在顶点着色器中需要接收一个模型视图投影矩阵,这个矩阵是以光源位置为视点位置计算的模型视图投影矩阵。在片元着色器中需要获取片元的深度值,即gl_FragCoord.z,存放在片元颜色的R分量中,但并不会绘图,而是写到阴影贴图中
着色器实现如下:

var SHADOW_VSHADER_SOURCE =
  'attribute vec4 a_Position;\\n' +
  'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,存放以光源位置为视点位置的模型视图投影矩阵
  'void main() {\\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\\n' +
  '}\\n';

//绘制阴影的片元着色器
var SHADOW_FSHADER_SOURCE =
  '#ifdef GL_ES\\n' +
  'precision mediump float;\\n' +
  '#endif\\n' +
  'void main() {\\n' +
  '  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\\n' + //将片元深度值z写入RGBA颜色的R分量
  '}\\n';

2.2.2 阴影映射

阴影映射其实就是绘制图形,这里需要另外一对着色器,这次在顶点着色器中接收的模型视图投影矩阵,是原来场景中的模型视图投影矩阵。即以原来的视点坐标观察物体的计算出的矩阵。在片元着色器中需要将片元坐标转换为光源坐标系中的坐标,即上一步中以光源位置为视点的坐标系,然后使用当前片元的深度值与阴影贴图中记录的深度值做比较,如果前者大,则当前片元处在阴影之中。用较深暗的颜色绘制
着色器实现如下:

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
  'attribute vec4 a_Color;\\n' + //声明attribute变量a_Color,用来存放顶点颜色
  'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
  'uniform mat4 u_MvpMatrixFromLight;\\n' +
  'varying vec4 v_PositionFromLight;\\n' +
  'varying vec4 v_Color;\\n' +
  'void main() {\\n' +
  '  gl_Position = u_MvpMatrix * a_Position;\\n' +
  '  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\\n' +
  '  v_Color = a_Color;\\n' +
  '}\\n';

var FSHADER_SOURCE =
  '#ifdef GL_ES\\n' +
  'precision mediump float;\\n' +
  '#endif\\n' +
  'uniform sampler2D u_ShadowMap;\\n' + //声明uniform变量u_ShadowMap,存放纹理单元编号
  'varying vec4 v_PositionFromLight;\\n' +
  'varying vec4 v_Color;\\n' +
  'void main() {\\n' +
  '  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\\n' + //转换为光源坐标系,坐标区间[-1.0,1.0]转换为[0.0,1.0]
  '  vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\\n' +
  '  float depth = rgbaDepth.r;\\n' + //从R分量中获取深度值
  '  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\\n' + //判断片元在平面上是否为阴影
  '  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\\n' +
  '}\\n';

2.2.3 马赫带消除

在绘制阴影的片元着色中,在做深度比较时,加了一个0.005的偏移量,之所以添加这个偏移量是为了消除马赫带,如去掉这个偏移量demo效果如下
在这里插入图片描述
之所以产生这种效果,是因为精度问题导致相同的值也会比较结果也会不同,详细说明一下:

  • 纹理图像的RGBA的每个分量都是8位,假设存储的值是0.1234567,实际上是31个1/256,即0.12109375
  • 计算的当前片元深度值是float类型,是16为,假设存储的值也是0.1234567,实际上是8090个1/65535,即0.12344360

这样即使相同深度也会得出不一样的结果,就会产生马赫带,为了避免产生马赫带,需要给纹理贴图的深度值添加一个偏移量,这个偏移量应略大于精度,这里0.005略大于1/256

3. demo代码

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

<head>
  <meta charset="UTF-8">
  <title></title>
</head>

<body>
  <!--通过canvas标签创建一个800px*800px大小的画布-->
  <canvas id="webgl" width="800" height="800"></canvas>
  <script type="text/javascript" src="./lib/cuon-matrix.js"></script>
  <script>
    var VSHADER_SOURCE =
      'attribute vec4 a_Position;\\n' + //声明attribute变量a_Position,用来存放顶点位置信息
      'attribute vec4 a_Color;\\n' + //声明attribute变量a_Color,用来存放顶点颜色
      'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,用来存放模型视图投影组合矩阵
      'uniform mat4 u_MvpMatrixFromLight;\\n' +
      'varying vec4 v_PositionFromLight;\\n' +
      'varying vec4 v_Color;\\n' +
      'void main() {\\n' +
      '  gl_Position = u_MvpMatrix * a_Position;\\n' +
      '  v_PositionFromLight = u_MvpMatrixFromLight * a_Position;\\n' +
      '  v_Color = a_Color;\\n' +
      '}\\n';

    var FSHADER_SOURCE =
      '#ifdef GL_ES\\n' +
      'precision mediump float;\\n' +
      '#endif\\n' +
      'uniform sampler2D u_ShadowMap;\\n' + //声明uniform变量u_ShadowMap,存放纹理单元编号
      'varying vec4 v_PositionFromLight;\\n' +
      'varying vec4 v_Color;\\n' +
      'void main() {\\n' +
      '  vec3 shadowCoord = (v_PositionFromLight.xyz/v_PositionFromLight.w)/2.0 + 0.5;\\n' +
      //转换为光源坐标系,坐标区间[-1.0,1.0]转换为[0.0,1.0]
      '  vec4 rgbaDepth = texture2D(u_ShadowMap, shadowCoord.xy);\\n' +
      '  float depth = rgbaDepth.r;\\n' + //从R分量中获取深度值
      '  float visibility = (shadowCoord.z > depth + 0.005) ? 0.7 : 1.0;\\n' + //判断片元在平面上是否为阴影
      '  gl_FragColor = vec4(v_Color.rgb * visibility, v_Color.a);\\n' +
      '}\\n';

    var SHADOW_VSHADER_SOURCE =
      'attribute vec4 a_Position;\\n' +
      'uniform mat4 u_MvpMatrix;\\n' + //声明uniform变量u_MvpMatrix,存放以光源位置为视点位置的模型视图投影矩阵
      'void main() {\\n' +
      '  gl_Position = u_MvpMatrix * a_Position;\\n' +
      '}\\n';

    //绘制阴影的片元着色器
    var SHADOW_FSHADER_SOURCE =
      '#ifdef GL_ES\\n' +
      'precision mediump float;\\n' +
      '#endif\\n' +
      'void main() {\\n' +
      '  gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 0.0);\\n' + //将片元深度值z写入RGBA颜色的R分量
      '}\\n';

    var OFFSCREEN_WIDTH = 2048
    var OFFSCREEN_HEIGHT = 2048

    //光源坐标
    var LIGHT_X = 0,
      LIGHT_Y = 7,
      LIGHT_Z = 2;

    //创建程序对象
    function createProgram(gl, vshader, fshader) {
      //创建顶点着色器对象
      var vertexShader = loadShader(gl, gl.VERTEX_SHADER, vshader)
      //创建片元着色器对象
      var fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fshader)

      if (!vertexShader || !fragmentShader) {
        return null
      }

      //创建程序对象program
      var program = gl.createProgram()
      if (!gl.createProgram()) {
        return null
      }
      //分配顶点着色器和片元着色器到program
      gl.attachShader(program, vertexShader)
      gl.attachShader(program, fragmentShader)
      //链接program
      gl.linkProgram(program)

      //检查程序对象是否连接成功
      var linked = gl.getProgramParameter(program, gl.LINK_STATUS)
      if (!linked) {
        var error = gl.getProgramInfoLog(program)
        console.log('程序对象连接失败: ' + error)
        gl.deleteProgram(program)
        gl.deleteShader(fragmentShader)
        gl.deleteShader(vertexShader)
        return null
      }

      gl.program = program
      //返回程序program对象
      return program
    }

    function loadShader(gl, type, source) {
      // 创建顶点着色器对象
      var shader = gl.createShader(type)
      if (shader == null) {
        console.log('创建着色器失败')
        return null
      }

      // 引入着色器源代码
      gl.shaderSource(shader, source)

      // 编译着色器
      gl.compileShader(shader)

      // 检查顶是否编译成功
      var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
      if (!compiled) {
        var error = gl.getShaderInfoLog(shader)
        console.log('编译着色器失败: ' + error)
        gl.deleteShader(shader)
        return null
      }

      return shader
    }

    function init() {
      //通过getElementById()方法获取canvas画布
      var canvas = document.getElementById('webgl')

      //通过方法getContext()获取WebGL上下文
      var gl = canvas.getContext('webgl')

      //获取绘制阴影贴图相关变量的存储地址
      var shadowProgram = createProgram(gl, SHADOW_VSHADER_SOURCE, SHADOW_FSHADER_SOURCE);
      shadowProgram.a_Position = gl.getAttribLocation(shadowProgram, 'a_Position');
      shadowProgram.u_MvpMatrix = gl.getUniformLocation(shadowProgram, 'u_MvpMatrix');
      if (shadowProgram.a_Position < 0 || !shadowProgram.u_MvpMatrix) {
        console.log('获取attribute变量或uniform变量存储地址失败');
        return;
      }

      //获取绘制阴影以外物体相关变量的存储地址
      var normalProgram = createProgram(gl, VSHADER_SOURCE, FSHADER_SOURCE);
      normalProgram.a_Position = gl.getAttribLocation(normalProgram, 'a_Position');
      normalProgram.a_Color = gl.getAttribLocation(normalProgram, 'a_Color');
      normalProgram.u_MvpMatrix = gl.getUniformLocation(normalProgram, 'u_MvpMatrix');
      normalProgram.u_MvpMatrixFromLight = gl.getUniformLocation(normalProgram, 'u_MvpMatrixFromLight');
      normalProgram.u_ShadowMap = gl.getUniformLocation(normalProgram, 'u_ShadowMap');
      if (normalProgram.a_Position < 0 || normalProgram.a_Color < 0 || !normalProgram.u_MvpMatrix ||
        !normalProgram.u_MvpMatrixFromLight || !normalProgram.u_ShadowMap) {
        console.log('获取attribute变量或uniform变量存储地址失败');
        return;
      }

      //初始化三角形顶点信息
      var triangle = initVertexBuffersForTriangle(gl)
      //初始化平面顶点信息
      var plane = initVertexBuffersForPlane(gl)
      if (!triangle || !plane) {
        console.log('初始化顶点信息失败')
        return
      }


      //初始化帧缓冲区对象(FBO)
      var fbo = initFramebufferObject(gl)
      if (!fbo) {
        console.log('初始化帧缓冲区对象失败')
        return
      }

      //激活0号纹理单元并绑定到纹理对象
      gl.activeTexture(gl.TEXTURE0)
      gl.bindTexture(gl.TEXTURE_2D, fbo.texture) //将帧缓冲区的颜色关联对象关联的纹理对象绑定到纹理单元


      // 设置canvas的背景色
      gl.clearColor(0, 0, 0, 1)
      //开启隐藏面消除
      gl.enable(gl.DEPTH_TEST)

      //创建、设置视图投影矩阵
      var viewProjMatrix = new Matrix4()
      viewProjMatrix.setPerspective(45, 1, 1, 100)
      viewProjMatrix.lookAt(0.0, 7.0, 9.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

      var viewProjMatrixFromLight = new Matrix4() //为阴影贴图创建视图投影矩阵
      viewProjMatrixFromLight.setPerspective(70.0, OFFSCREEN_WIDTH / OFFSCREEN_HEIGHT, 1.0, 100.0)
      viewProjMatrixFromLight.lookAt(LIGHT_X, LIGHT_Y, LIGHT_Z, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)

      var mvpMatrixFromLight_t = new Matrix4(); //三角形的模型视图投影矩阵
      var mvpMatrixFromLight_p = new Matrix4(); //底面的模型视图投影矩阵
      var currentAngle = 0.0
      var tick = function () {
        currentAngle = getCurrentAngle(currentAngle) //获取当前要旋转的角度

        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo) //绑定帧缓冲区对象,后续绘制在绑定帧缓冲区中进行
        gl.viewport(0, 0, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置在帧缓冲区绘制时窗口大小
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空帧缓冲区

        //启用绘制阴影的着色器程序对象
        gl.useProgram(shadowProgram)

        //准备阴影纹理,即将三角形和平面片元写入帧缓冲区的深度关联对象
        drawTriangle(gl, shadowProgram, triangle, currentAngle, viewProjMatrixFromLight); //以光源位置为视点计算投影
        mvpMatrixFromLight_t.set(g_mvpMatrix); //恢复绘制三角形的模型视图投影矩阵
        drawPlane(gl, shadowProgram, plane, viewProjMatrixFromLight);
        mvpMatrixFromLight_p.set(g_mvpMatrix); //恢复绘制平面的模型视图投影矩阵

        gl.bindFramebuffer(gl.FRAMEBUFFER, null) //帧缓冲区对象解绑,解绑后绘制在默认的颜色缓冲区中进行
        gl.viewport(0, 0, canvas.width, canvas.height) //设置在默认的颜色缓冲区绘制时窗口大小
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) //清空颜色和深度缓冲区

        //启用绘制三角形和平面的着色器程序对象
        gl.useProgram(normalProgram);
        gl.uniform1i(normalProgram.u_ShadowMap, 0); //传值纹理编号

        //绘制三角形
        gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_t.elements);
        drawTriangle(gl, normalProgram, triangle, currentAngle, viewProjMatrix);
        //绘制平面及平面上三角形的阴影
        gl.uniformMatrix4fv(normalProgram.u_MvpMatrixFromLight, false, mvpMatrixFromLight_p.elements);
        drawPlane(gl, normalProgram, plane, viewProjMatrix)

        requestAnimationFrame(tick)
      }

      tick() // 调用tick

    }

    var g_LastTime = Date.now() // 上次绘制的时间
    var ANGLE_SET = 30.0 // 旋转速度(度/秒)
    function getCurrentAngle(angle) {
      var now = Date.now()
      var elapsed = now - g_LastTime //上次调用与当前时间差
      g_LastTime = now
      var newAngle = angle + (ANGLE_SET * elapsed) / 1000
      return newAngle %= 360
    }

    //初始化帧缓冲区(FBO)
    function initFramebufferObject(gl) {
      var framebuffer, texture, depthBuffer

      //处理错误
      var error = function () {
        if (framebuffer) gl.deleteFramebuffer(framebuffer)
        if (texture) gl.deleteTexture(texture)
        if (depthBuffer) gl.deleteRenderbuffer(depthBuffer)
        return null
      }

      //创建帧缓冲区
      framebuffer = gl.createFramebuffer()
      if (!framebuffer) {
        console.log('创建帧缓冲区对象失败')
        return error()
      }

      //创建纹理对象并设置参数
      texture = gl.createTexture() //创建纹理对象
      if (!texture) {
        console.log('创建纹理对象失败')
        return error()
      }
      gl.bindTexture(gl.TEXTURE_2D, texture) //绑定纹理对象
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT, 0, gl.RGBA, gl.UNSIGNED_BYTE,
        null) //纹理图像分配给纹理对象
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) //配置纹理对象参数
      framebuffer.texture = texture //将纹理对象关联到帧缓冲区的颜色关联对象

      //创建渲染缓冲区对象并设置参数
      depthBuffer = gl.createRenderbuffer() //创建渲染缓冲区对象
      if (!depthBuffer) {
        console.log('创建渲染缓冲区对象失败')
        return error()
      }
      gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer) //绑定渲染缓冲区
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, OFFSCREEN_WIDTH, OFFSCREEN_HEIGHT) //设置渲染缓冲区尺寸

      //将纹理对象和渲染缓冲区对象关联到帧缓冲区对象
      gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer) //绑定帧缓冲区对象
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer)

      //检查帧缓冲区的配置状态
      var e = gl.checkFramebufferStatus(glCSS实现表格阴影

3D光照阴影 平面阴影矩阵推导及代码实现

3D光照阴影 平面阴影矩阵推导及代码实现

WebGL入门(四十二)-使用(FBO)实现阴影效果

WebGL入门(四十二)-使用(FBO)实现阴影效果

用GTK实现模糊阴影技术