webgl通过Shadow Mapping生成阴影

阴影生成的原理

阴影贴图是一种基于深度缓冲区(Depth Buffer)的阴影生成技术,其基本思想是通过从光源视角生成一个深度图,然后在场景渲染时利用该深度图判断像素是否在阴影中。具体步骤如下:

  1. 深度图生成:首先,从光源视角渲染场景,并将每个片段的深度值存储在深度贴图中。
  2. 场景渲染:然后,从摄像机视角渲染场景。对于每个片段,将其转换到光源视角,并与深度贴图中的值进行比较。如果片段的深度值大于深度贴图中的值,则该片段在阴影中。

代码实现

基础代码

// 定义 Light 类,包含光照的强度、颜色、位置、目标和上向量
class Light {
  lightIntensity: number;
  lightColor: Vector3;
  pos: Vector3;
  target: Vector3;
  up: Vector3;
  
  constructor(lightIntensity: number, lightColor: Vector3, pos: Vector3, target: Vector3, up: Vector3) {
    this.lightIntensity = lightIntensity;
    this.lightColor = lightColor;
    this.pos = pos;
    this.target = target;
    this.up = up;
  }

  // 计算光源的MVP矩阵
  calMVP(translate: Vector3, scale: Vector3, rotate: Vector3): Matrix4 {
    const lightMvp = new Matrix4();
    const modelMatrix = getModeMatrix(scale, translate, rotate);
    const viewMatrix = getViewMatrix(this.pos, this.target, this.up);

    // 定义投影矩阵的参数
    const r = 450, l = -r, t = 300, b = -t, n = 0.01, f = 500;
    const projMatrix = getOrhMatrix(l, r, t, b, n, f);

    lightMvp.multiply(projMatrix)
            .multiply(viewMatrix)
            .multiply(modelMatrix);
    return lightMvp;
  }
}

// 定义 Obj 类,表示一个3D对象
class Obj {
  modelMatrix: Matrix4;
  programInfo: { program: WebGLProgram; uniforms: any; attributes: any; };
  vertices: WebGLBuffer | null = null;
  normals: WebGLBuffer | null = null;
  indices: any[] = [];
  uniforms: any;
  gl: WebGLRenderingContext;
  light!: Light;
  scale: Vector3 = new Vector3(1, 1, 1);
  translate: Vector3 = new Vector3(0, 0, 0);
  rotate: Vector3 = new Vector3(0, 0, 0);
  
  constructor(modelMatrix: Matrix4, gl: WebGLRenderingContext, v: string, f: string, uniforms: any) {
    this.modelMatrix = modelMatrix;
    this.gl = gl;
    this.uniforms = uniforms;
    this.programInfo = glUtil.creatProgramInfo(gl, v, f);
  }

  // 绑定纹理
  bindTxt(texture: WebGLTexture) {
    glUtil.setTextureAndLoc(this.programInfo.program, this.gl, texture, "uShadowMap");
  }

  // 设置对象的变换矩阵
  setTransform(scale: Vector3, translate?: Vector3, rotate?: Vector3) {
    this.scale = scale;
    if (translate) this.translate = translate;
    if (rotate) this.rotate = rotate;
  }

  // 设置对象的材质
  setMaterial(diffuse: Vector3) {
    this.uniforms.diffuseColor = diffuse.toArray();
  }

  // 设置对象的网格数据
  setMesh(geometry: BoxGeometry) {
    const { attributes, index } = geometry;
    const { position, normal } = attributes;
    this.vertices = glUtil.createBuffer(this.gl, position.array as unknown as number[]);
    this.normals = glUtil.createBuffer(this.gl, normal.array as unknown as number[]);
    this.indices = index?.array as unknown as number[];
  }

  // 设置光照
  setLight(light: Light) {
    this.light = light;
  }

  // 创建对象
  create() {
    const mvp = this.light.calMVP(this.translate, this.scale, this.rotate);
    this.uniforms.uLightMvp = mvp.clone().elements;
    glUtil.setUniforms(this.gl, this.programInfo.program, this.uniforms);
    glUtil.setAttr(this.gl, this.programInfo.program, "aPosition", this.vertices!, 3);
    glUtil.createIndexBuffer(this.gl, this.indices);
    this.gl.drawElements(this.gl.TRIANGLES, this.indices.length, this.gl.UNSIGNED_SHORT, 0);
  }

  // 绘制对象
  draw(camera?: PerspectiveCamera, texture?: WebGLTexture) {
    this.gl.useProgram(this.programInfo.program);
    if (camera) {
      this.gl.viewport(0, 0, config.width, config.height);
      const viewMatrix = camera.matrixWorld.clone().invert();
      const projectMatrix = camera.projectionMatrix.clone();
      const modelMatrix = getModeMatrix(this.scale, this.translate, this.rotate);
      this.uniforms.uViewMatrix = viewMatrix.elements;
      this.uniforms.uProjectionMatrix = projectMatrix.elements;
      this.uniforms.uModelMatrix = modelMatrix.elements;
      this.uniforms.uLightPos = this.light.pos.toArray();
      this.bindTxt(texture!);
      glUtil.setAttr(this.gl, this.programInfo.program, "aNormalPosition", this.normals!, 3);
    } else {
      this.gl.viewport(0, 0, 1024, 1024);
    }
    this.create();
  }
}
 基础变量定义
let cameraControls: any = null;
const shadowMeshes: Obj[] = [];
const meshes: Obj[] = [];
let prevTime = 0;
const config = {
  width: 900,
  height: 600,
};
// 设置网格对象并添加到渲染队列中
function setMesh(box: any, light: Light, gl: WebGLRenderingContext, transform = {
  translate: new Vector3(),
  scale: new Vector3(1, 1, 1),
  rotate: new Vector3()
}, diffuseColor: Vector3) {
  const obj = new Obj(new Matrix4(), gl, phongVertex, phongFrag, {});
  obj.setMesh(box);
  obj.setLight(light);
  obj.setTransform(transform.scale.clone(), transform.translate.clone(), transform.rotate.clone());
  obj.setMaterial(diffuseColor);
  meshes.push(obj);
}

// 设置阴影网格对象并添加到渲染队列中
function setShadowMesh(box: any, light: Light, gl: WebGLRenderingContext, transform = {
  translate: new Vector3(),
  scale: new Vector3(1, 1, 1),
  rotate: new Vector3()
}) {
  const obj = new Obj(new Matrix4(), gl, shadowVertex, shadowFrag, {});
  obj.setMesh(box);
  obj.setLight(light);
  obj.setTransform(transform.scale.clone(), transform.translate.clone(), transform.rotate.clone());
  shadowMeshes.push(obj);
}
阴影着色器 
const shadowVertex = `#ifdef GL_ES
precision highp float;
#endif

attribute vec3 aPosition;
uniform mat4 uLightMvp;

void main() {
    gl_Position = uLightMvp * vec4(aPosition, 1.0);
}`

const shadowFrag = `#ifdef GL_ES
precision highp float;
#endif

void main() {
    gl_FragColor = vec4(gl_FragCoord.z, 0.0, 0.0, 1.0);
}


`
渲染着色器
const phongVertex = `#ifdef GL_ES
precision highp float;
#endif
attribute vec3 aPosition;
attribute vec3 aNormalPosition;
attribute vec2 aTextureCoord;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
uniform mat4 uLightMvp;
varying vec4 vPositionFromLight;
varying vec3 vNormal;
varying vec3 vFragPos;

void main() {
    vPositionFromLight = uLightMvp * vec4(aPosition, 1.0);
    vFragPos = (uModelMatrix * vec4(aPosition, 1.0)).xyz;
    vNormal = (uModelMatrix * vec4(aNormalPosition, 0.0)).xyz;
    gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);
}
`

const phongFrag = `#ifdef GL_ES
precision highp float;
#endif

#define EPS 1e-3
#define SHADOW_MAP_SIZE 2048.
#define FRUSTUM_SIZE 400.

uniform sampler2D uShadowMap;
uniform vec3 uLightPos;
uniform vec3 diffuseColor;
varying vec4 vPositionFromLight;
varying vec3 vNormal;
varying vec3 vFragPos;

float getShadowBias(float c, float filterRadiusUV){
  vec3 normal = normalize(vNormal);
  vec3 lightDir = normalize(uLightPos - vFragPos);
  float fragSize = (1.) * (FRUSTUM_SIZE / SHADOW_MAP_SIZE / 2.);
  return max(fragSize, fragSize * (1.0 - dot(normal, lightDir))) * c;
}


void main() {
    vec3 shadowCoord = vPositionFromLight.xyz / vPositionFromLight.w;
    shadowCoord.xyz = (shadowCoord.xyz + 1.0) / 2.0;
    float cur_depth = shadowCoord.z;
    float depth = texture2D(uShadowMap, shadowCoord.xy).r;
    float visibility = 1.0;
    if(cur_depth-getShadowBias(0.4,0.0) >= depth + EPS ) {
        visibility = 0.0;
    }
    gl_FragColor = vec4(diffuseColor * visibility, 1.0);
}

`
 基础数据的设置
  const gl = canvas.getContext("webgl")!;
  gl.enable(gl.DEPTH_TEST);

  // 设置相机
  const camera = new PerspectiveCamera(75, config.width / config.height, 0.01, 1000);
  cameraControls = new OrbitControls(camera, canvas);
  cameraControls.enableZoom = true;
  cameraControls.enableRotate = true;
  cameraControls.enablePan = true;
  cameraControls.rotateSpeed = 0.3;
  cameraControls.zoomSpeed = 1.0;
  cameraControls.panSpeed = 2.0;
  cameraControls.target.set(0, 0, 0);
  camera.position.set(100, 100, 100);

  // 设置对象的变换
  const planeTransform = { scale: new Vector3(1, 1, 1), translate: new Vector3(0, -30, 0), rotate: new Vector3(degrees2Radians(90), 0, 0) };
  const sphereTransform = { scale: new Vector3(10, 10, 10), translate: new Vector3(), rotate: new Vector3() };
  const light = new Light(500, new Vector3(0, 100, 0), new Vector3(0, 30, 30), new Vector3(), new Vector3(0, 1, 0));

  // 添加阴影网格对象和普通网格对象
  setShadowMesh(new SphereGeometry(), light, gl, sphereTransform);
  setShadowMesh(new PlaneGeometry(100, 100, 1, 1), light, gl, planeTransform);
  setMesh(new SphereGeometry(), light, gl, sphereTransform, new Vector3(0.5, 0.5, 0.5));
  setMesh(new PlaneGeometry(100, 100, 1, 1), light, gl, planeTransform, new Vector3(0.6, 0.6, 0));

  // 创建帧缓冲区和纹理
  const texture = glUtil.createTexture(gl, null, [1024, 1024, 0], 0);
  const framebuffer = glUtil.createFramebuffer(gl, texture)!;
  glUtil.creatDepthFramebuufer(gl, framebuffer, 1024, 1024);
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.bindTexture(gl.TEXTURE_2D, null);
  gl.bindRenderbuffer(gl.RENDERBUFFER, null);
循环函数
function mainLoop(now: number) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clearDepth(1.0);
    gl.enable(gl.DEPTH_TEST);
    gl.depthFunc(gl.LEQUAL);

    // 渲染阴影到帧缓冲区
    gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
    gl.clearColor(1.0, 1.0, 1.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    let deltaTime = (now - prevTime) / 1000;
    let lightPos = light.pos.clone();
    const matrix = new Matrix4().makeRotationY(degrees2Radians(10 * deltaTime));
    lightPos.applyMatrix4(matrix);
    light.pos = lightPos.clone();

    cameraControls.update();
    camera.updateMatrixWorld();

    // 绘制阴影网格对象
    shadowMeshes.forEach(item => {
      item.draw();
    });

    // 渲染场景到默认帧缓冲区
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    meshes.forEach(item => {
      item.draw(camera, texture);
    });

    gl.disable(gl.BLEND);
    prevTime = now;

    requestAnimationFrame(mainLoop);
  }

产生效果

总结

从上面的图片中,我们可以看到阴影边缘出现锯齿现象, 并且阴影也并不像现实生活中的阴影那样,边缘有点模糊的感觉,我们通过这种方式产生的阴影为硬阴影,现实生活中产生的阴影为软阴影,如下图所示软硬阴影的对比。

图片来自于网络

说明

项目使用的threejs产生的相机,点击下载glUtil工具

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: WebGL 是一种基于 JavaScript 和 OpenGL 的 3D 图形库,它可以在 Web 浏览器中渲染 3D 图形,从而实现浏览器中的 3D 视觉效果。实现阴影效果可以使用 WebGL 中的阴影映射技术,简单来说,就是在场景中添加光源,然后从光源的角度渲染场景,生成一张深度图,再将深度图应用到场景中的物体上,就可以实现阴影效果了。同时,还可以使用其他技术,例如环境映射和法线映射,来增强场景的真实感和深度感。总之,WebGL 提供了丰富的工具和技术,使得在 Web 浏览器中实现 3D 阴影效果成为可能。 ### 回答2: WebGL是一种基于Web的图形渲染技术,可以实现高性能的3D图形渲染。要实现阴影效果,可以借助WebGL的功能来完成。 首先,要实现阴影效果,需要一个光源。在WebGL中,可以通过设置光源的位置和方向来控制光的照射效果。 其次,还需要一个或多个需要投射阴影的物体。通过在渲染过程中为这些物体添加一个额外的"shadow"属性,可以标识它们需要投射阴影。 然后,在渲染阴影之前,需要创建一个用于渲染阴影的额外场景。在该场景中,只包含需要投射阴影的物体。这个场景可以使用一个特殊的着色器来渲染,这个着色器和其他场景使用的着色器可能稍有不同,因为它只关注渲染阴影而不需要考虑光照。 接下来,在渲染主场景之前,需要对每个需要投射阴影的物体进行阴影计算。这可以通过使用光源的位置和方向来确定每个像素是否在阴影之中。可以使用阴影映射技术(Shadow Mapping)来完成这个计算过程,这个过程涉及渲染一个特殊的深度贴图来表示场景中的深度信息。 最后,在主场景渲染过程中,可以使用计算得到的阴影信息来对物体进行阴影投射。可以使用对阴影贴图进行采样的方式,来判断像素是否位于阴影之中。 综上所述,WebGL通过设置光源、创建阴影场景、进行阴影计算和使用阴影信息投射,可以实现3D场景中的阴影效果。 ### 回答3: WebGL是一种在网页浏览器上运行3D图形的技术,它使用JavaScript和OpenGL的子集来实现。要实现阴影效果,我们可以借助WebGL的着色器程序和纹理功能。 首先,为了产生阴影,我们需要一个光源。可以使用WebGL的光照功能来定义光源的位置和类型,例如平行光或点光源。 接下来,我们需要为阴影接收器(即产生阴影的物体)和阴影投射器(即光源)创建阴影贴图。阴影贴图是一个与场景大小相同的二维纹理,它记录了物体与光源之间的相对位置关系。 然后,我们需要通过在阴影投射器和阴影接收器之间进行渲染来生成阴影贴图。我们可以使用投射阴影的算法,如阴影映射或体积光,来计算每个像素的阴影强度。 在渲染阶段,我们可以在顶点着色器和片段着色器中应用阴影贴图。顶点着色器将物体的顶点位置转换为世界坐标和光源坐标,并将它们传递给片段着色器。片段着色器使用阴影贴图来计算每个像素的阴影强度,并将其与物体的颜色进行混合。 最后,我们可以通过在片段着色器中使用深度测试来控制阴影的渲染顺序,并使用透明度来调整阴影的强度和透明度。 总体来说,WebGL通过使用光照、阴影贴图和着色器程序来实现阴影效果。这样可以使3D场景更加逼真和生动,并增强用户对物体之间关系的认知。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值