Threejs 渲染阴影流程

效果展示

首先我们通过创建场景,为场景添加1个平行光,1个平面,1 个球体;并渲染带阴影的场景。

ini 复制代码
const settings = {
  cameraX: 6,
  cameraY: 12,
  posX: -3.5,
  posY: 7.5,
  posZ: 5.0,
  targetX: 3.5,
  targetY: 0,
  targetZ: 3.5,
  projWidth: 10,
  projHeight: 10,
  perspective: false,
  fieldOfView: 120,
  bias: -0.006
};

export function DirectionLightShadowInThree(canvas: HTMLCanvasElement) {

  const { width, height } = canvas.getBoundingClientRect();

  const aspect = width / height;

  const scene = new Scene();

  const renderer = new WebGL1Renderer({
    canvas
  });
  renderer.shadowMap.enabled = true;
  renderer.outputColorSpace = LinearSRGBColorSpace;
  // renderer.shadowMap.type = PCFSoftShadowMap;
  // renderer.outputEncoding = THREE.sRGBEncoding;
  // 这个开启才会渲染阴影贴图
    // 创建一个数字纹理
    const data = new Uint8Array([  // data
      0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
      0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
      0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
      0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
      0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
      0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
      0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC,
      0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF, 0xCC, 0xFF,
    ]);
    
    // const dataTexture = new DataTexture(data, 8, 8, LuminanceFormat, UnsignedByteType)
  
    // dataTexture.needsUpdate = true;
    // create a buffer with color data
  
  const _width = 8;
  const _height = 8;
  
  const camera = new PerspectiveCamera(60, aspect, 1, 2000);
  camera.position.set(
    settings.cameraX,
    settings.cameraY,
    15
  );
  camera.lookAt(new Vector3())
  // // used the buffer to create a DataTexture
  
  const texture = new DataTexture( data, _width, _height, LuminanceFormat, UnsignedByteType);
  // texture.mipmaps = [texture]
  texture.needsUpdate = true;


  const dirLight = new DirectionalLight();
  dirLight.castShadow = true;
  dirLight.target.position.set( 0, 0, 0 );

  // const lighthelper = new DirectionalLightHelper(dirLight);
  const planeMat = new MeshPhongMaterial({
    map: texture,
    color: new Color(0.5, 0.5, 1)
  });
  const sphereMat = new MeshPhongMaterial({
    map: texture,
    color: new Color(1, 0.5, 0.5)
  });

  const sphereGeometry = new SphereGeometry(1, 32, 24);
  
  const planeGeo = new PlaneGeometry(20, 20);

  dirLight.position.set(settings.posX, settings.posY, settings.posZ);

  dirLight.lookAt(new Vector3())

  const plane = new Mesh(planeGeo, planeMat);

  plane.receiveShadow = true;
  
  plane.rotation.x = - 0.5 * Math.PI;
  
  const sphere = new Mesh(sphereGeometry, sphereMat);

  sphere.castShadow = true;
  sphere.position.set(2, 3, 4);
  // sphere.updateMatrixWorld();
  scene.add(plane)

  scene.add(dirLight)
  scene.add(sphere)

  renderer.render(scene, camera);



}

threejs 渲染阴影的前置条件是

  1. 开启渲染器的阴影贴图 就是 renderer.shadowMap.enabled = true;
  2. 对需要投身阴影的模型设置 castShadow 属性为 true
  3. 对需要能产生阴影的光设置 castShadow 属性为 true
  4. 需要接收阴影的模型设置 receiveShadow 属性为 true

最终效果如下图所示

Threejs 是如何产生模型阴影的

  1. 根据灯光信息,渲染阴影贴图

1.1 首先记录能产生阴影的灯光

csharp 复制代码
// projectObject 函数内会有这样一段,处理灯光

if ( object.isLight ) {

        currentRenderState.pushLight( object );

        if ( object.castShadow ) {

                currentRenderState.pushShadow( object );

        }

}

1.2 根据灯光 执行WebGLShadowMap 的render 渲染阴影贴图 // 渲染灯光下的深度信息 // 渲染了尝试贴图后 shadowMap.render( shadowsArray, scene, camera );

以示例代码说明,此处用的是平行光。会在DirectilnalLightShadow 中保存渲染后的阴影深度信息(就是通过 WebGLRenderTarget 记录纹理,结合 MeshDepthMaterial)。

1.3 将贴图信息写入着色器中,供后面生成程序时用

在 setupLights 方法中会将相应光的贴图信息写入WebGLUniforms 中,后面传入着色器内。

ini 复制代码
if ( light.isDirectionalLight ) {
// 平行光
const uniforms = cache.get( light );

uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor );

if ( light.castShadow ) {

        const shadow = light.shadow;

        const shadowUniforms = shadowCache.get( light );

        shadowUniforms.shadowBias = shadow.bias;
        shadowUniforms.shadowNormalBias = shadow.normalBias;
        shadowUniforms.shadowRadius = shadow.radius;
        shadowUniforms.shadowMapSize = shadow.mapSize;

        state.directionalShadow[ directionalLength ] = shadowUniforms;
        state.directionalShadowMap[ directionalLength ] = shadowMap;
        state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;

        numDirectionalShadows ++;

}

state.directional[ directionalLength ] = uniforms;

directionalLength ++;

}

src/renderers/shaders/shaderChunk/shadowmap_pars_vertext.glsl

定义

ini 复制代码
    // 灯光的模型矩阵
    uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];
    varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];

src/renderers/shaders/shaderChunk/shadowmap_vertext.glsl 计算阴影纹理坐标

ini 复制代码
#if NUM_DIR_LIGHT_SHADOWS > 0

#pragma unroll_loop_start
for ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {
   // worldPosition 模型的 position
   shadowWorldPosition = worldPosition + 
   vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );
   
   vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;

}
       

src/renderers/shaders/shaderChunk/shadowmap_pars_fragment.glsl 定义变量

ini 复制代码
   
    // 获取深度贴图
    uniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];
    // 贴图纹理坐标
    varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];
    

src/renderers/shaders/shaderChunk/lights_fragment_begin.glsl 中调用 getShadow 获取阴影信息

ini 复制代码
#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )

 DirectionalLight directionalLight;
 #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
 DirectionalLightShadow directionalLightShadow;
 #endif

 #pragma unroll_loop_start
 for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {

 	directionalLight = directionalLights[ i ];

 	getDirectionalLightInfo( directionalLight, geometry, directLight );

 	#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
 	directionalLightShadow = directionalLightShadows[ i ];
 	directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) 
 		? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) 
 		: 1.0;
 	#endif

 	RE_Direct( directLight, geometry, material, reflectedLight );

 }
相关推荐
清幽竹客24 分钟前
vue-37(模拟依赖项进行隔离测试)
前端·vue.js
vvilkim24 分钟前
Nuxt.js 页面与布局系统深度解析:构建高效 Vue 应用的关键
前端·javascript·vue.js
滿28 分钟前
Vue3 父子组件表单滚动到校验错误的位置实现方法
前端·javascript·vue.js
夏梦春蝉1 小时前
ES6从入门到精通:模块化
前端·ecmascript·es6
拓端研究室2 小时前
视频讲解:门槛效应模型Threshold Effect分析数字金融指数与消费结构数据
前端·算法
工一木子3 小时前
URL时间戳参数深度解析:缓存破坏与前端优化的前世今生
前端·缓存
半点寒12W5 小时前
微信小程序实现路由拦截的方法
前端
某公司摸鱼前端6 小时前
uniapp socket 封装 (可拿去直接用)
前端·javascript·websocket·uni-app
要加油哦~6 小时前
vue | 插件 | 移动文件的插件 —— move-file-cli 插件 的安装与使用
前端·javascript·vue.js
小林学习编程6 小时前
Springboot + vue + uni-app小程序web端全套家具商场
前端·vue.js·spring boot