返回

零基础充分理解WebGL(三)距离场构图法的进一步探索

前端

在上一篇中,我们深入浅出地了解了距离场构图法的基本原理,为后续的图形绘制奠定了坚实的基础。本篇博文将继续我们的探索之旅,深入探究距离场构图法的更多奥秘,让你领略它绘制复杂图形的强大威力。

距离场构图法的进阶应用

距离场构图法不仅能绘制简单的形状,还能构建更为复杂的几何体。以下是一些进阶应用场景:

  • 绘制球体: 将球体视为由无数小球体组成的集合,通过计算每个小球体的距离场值,即可得到整个球体的距离场,进而绘制出平滑的球体形状。
  • 绘制地形: 距离场构图法可以模拟自然界的地形,例如山峰和河流。通过将地形的高程数据转换为距离场,可以创建逼真的地形模型。
  • 绘制云朵: 云朵具有柔软、蓬松的特点。利用距离场构图法,我们可以模拟云朵的密度分布,从而绘制出逼真的云朵效果。
  • 绘制其他复杂形状: 距离场构图法几乎可以绘制任何形状,例如树叶、动物和人物模型,为三维建模提供了广阔的空间。

代码实现示例

以下代码段展示了如何利用距离场构图法绘制球体:

// 定义球体的半径
const radius = 1.0;

// 创建一个距离场纹理
const distanceFieldTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, distanceFieldTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.R32F, width, height, 0, gl.RED, gl.FLOAT, null);

// 填充距离场纹理
for (let x = 0; x < width; x++) {
  for (let y = 0; y < height; y++) {
    // 计算点 (x, y) 到球心 (width / 2, height / 2) 的距离
    const distance = Math.sqrt((x - width / 2) ** 2 + (y - height / 2) **  2);

    // 根据距离计算距离场值
    let distanceField = distance - radius;
    if (distanceField < 0.0) {
      distanceField = 0.0;
    }

    // 存储距离场值到纹理中
    const pixelData = new Float32Array([distanceField]);
    gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, 1, 1, gl.RED, gl.FLOAT, pixelData);
  }
}

// 使用距离场纹理渲染球体
const vertexShaderSource = `
  attribute vec3 a_position;
  void main() {
    gl_Position = vec4(a_position, 1.0);
  }
`;

const fragmentShaderSource = `
  precision mediump float;
  uniform sampler2D u_distanceFieldTexture;
  uniform vec3 u_lightPosition;

  void main() {
    vec2 uv = gl_FragCoord.xy / vec2(width, height);
    float distance = texture2D(u_distanceFieldTexture, uv).r;

    vec3 normal = vec3(0.0);
    if (distance > 0.0) {
      normal = normalize(vec3(
        dFdx(distance),
        dFdy(distance),
        1.0
      ));
    }

    vec3 lightDirection = normalize(u_lightPosition - gl_FragCoord.xyz);
    float diffuseIntensity = max(dot(normal, lightDirection), 0.0);

    gl_FragColor = vec4(diffuseIntensity, diffuseIntensity, diffuseIntensity, 1.0);
  }
`;

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(vertexShader));
}

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
  console.error(gl.getShaderInfoLog(fragmentShader));
}

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  console.error(gl.getProgramInfoLog(program));
}

gl.useProgram(program);

// 获取顶点着色器的属性位置
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionAttributeLocation);

// 获取片段着色器的 uniform 位置
const distanceFieldTextureLocation = gl.getUniformLocation(program, "u_distanceFieldTexture");
const lightPositionLocation = gl.getUniformLocation(program, "u_lightPosition");

// 设置 uniform 变量
gl.uniform1i(distanceFieldTextureLocation, 0);
gl.uniform3f(lightPositionLocation, -1.0, 1.0, 1.0);

// 创建顶点缓冲区
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 0.0]), gl.STATIC_DRAW);

// 设置顶点属性
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);

// 清除颜色缓冲区
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

// 绑定距离场纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, distanceFieldTexture);

// 绘制球体
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

总结

距离场构图法是一种强大的技术,可以绘制各种复杂的图形。通过理解其原理并熟练运用,你可以在三维建模中如鱼得水,创作出令人惊叹的视觉效果。在下一篇博文中,我们将继续探索 WebGL 的更多奥秘,敬请期待!