如何使用WebGL渲染一簇水晶

2,212 阅读2分钟

水晶体效果

本文示例用随机几何体、光照、纹理贴图、着色器整合起来绘制一个水晶簇。通过调整着色器,可以产生岩浆、蓝宝石等各种效果。

示例效果图:

27个单晶45度随机分布的蓝色水晶簇。

canvas-preview
uv

77个单晶180度随机分布的红色水晶簇。

canvas-preview
uv

制作随机水晶体生成器

很多三维库的几何体基本都是规则几何体,没有随机的美感,因此捣鼓了一个随机水晶生成器,源码地址:github.com/guoweish/ve…

实现思路如下:

  • 生成单个水晶顶点

canvas-preview
uv

  • 生成水晶簇顶点

canvas-preview
uv

实现步骤如下:

  • 一个圆周上取多边形点,使用随机角度,形成不同宽度面
  ...
  angle = i / faceNumber * PI * 2 + r * angleDithringFactor * angleDithringUnit;
  px = Math.sin(angle) * polygonCircleRadius;
  py = Math.cos(angle) * polygonCircleRadius;
  ...
  • 随机缩放圆半径,圆周上同角度取多边形点,形成不同柱体半径
  ...
  pxd = Math.sin(angle) * polygonCircleRadius* (circleRadiusDithringFactor + radiusDithringDistance);
  pyd = Math.cos(angle) * polygonCircleRadius* (circleRadiusDithringFactor + radiusDithringDistance);
  ...
  • 形成多边形棱柱的上下两层顶点
  ...
  vertexs.topPositions.push(px);
  vertexs.topPositions.push(py);
  ...
  • 增加正对圆心的顶点,形成水晶柱顶点
  ...
  vertexsCon = [0, cylinderHeight+conHeight, 0];
  ...
  • 生成一个水晶体几何体
  ...
  vertexs = [...vertexsCylinderDown, ...vertexsCylinderTop, ...vertexsCon];
  ...
  • 矩阵变换缩放、旋转单个水晶体的顶点,并复制顶点
  ...
  let scaleMatrix = new Matrix4();
  scaleMatrix.setScale(scaleFactor, scaleFactor, scaleFactor);
  let rotateMatrix = new Matrix4();
  rotateMatrix.setRotate(rotateAngle, rotateCenter.x, rotateCenter.y, rotateCenter.z);
  ...
  let vScaledRotated = rotateMatrix.multiplyVector4(vScaled);
  ...
  • 生成水晶簇几何体
  ...
  let extendedIndices = extendIndices(cristalTransformed.indices, currentIndicesLength);
  cluster.indices = cluster.indices.concat(extendedIndices);
  cluster.positions = cluster.positions.concat(cristalTransformed.positions);
  cluster.uvs = cluster.uvs.concat(cristal.uvs);
  ...

渲染几何体

用纯色shader简单渲染一下是否顶点和索引正确;

  • 片元着色器
  fragColor = vec4(0.6, 0.6, 0.6, 1.0);

canvas-preview
uv

添加光照

增加光照,使用Blinn-Phong模型;

  • 片元着色器
  vec3 diffuse = max(dot(normal, ec_lightDirection), 0.0) * lightColor * lightIntensity * baseColor;

  vec3 viewDirection = -normalize(ec_position);
  vec3 halfAngle = normalize(viewDirection + ec_lightDirection);
  float specularFactor = clamp(dot(normal, halfAngle), 0.0, 1.0);
  float spec = pow(specularFactor, specularIntensity);
  vec3 specular = clamp(spec * specularColor, 0.0, 1.0);

  fragColor = vec4(diffuse + specular + ambient, 1.0);

canvas-preview

着色器添加渐变的祖母绿效果

为了看上去像祖母绿宝石效果,用纹理做一个渐变;用pow函数使得渐变非线性,用mix函数融合得颜色,看上去效果更自然;

  • 片元着色器
  ...
  float colorMixFactor = pow(v_uv.y, 3.0);
  ...
  vec3 baseColor = mix(CRISTAL_COLOR, GEM_COLOR_GREEN,  colorMixFactor);
  ...

canvas-preview
uv

使用贴图增强表面细节

为了表面有石头纹样效果,找一张大理石图片做贴图过滤一下颜色;魔改一下光照模型,贴图过滤diffuse而不过滤specular,这样使得表面反色不受影响而产生表面光滑的效果;

  • 片元着色器
  ...
  fragColor = vec4(diffuse + diffuse * textureFilter + specular + AMB_COLOR, 1.0);

canvas-preview
uv

结语

可以使用perlin噪音和fbm让随机几何体的视觉效果自然,比如大小个体比例和空间位置的分布;光照可以改用pbr模型,比Blinn-Phong会更好,计划下一篇更新尝试(希望有空填坑-_-!!)。

关于作者

郭不耐 github.com/guoweish

参考文献

Webgl Programing Guide

OpenGl Shading Language

The Book of Shaders

ShaderToy