图形学进阶技巧(十六)threejs实现海面模拟

源码:

作者:john hollen

源码:https://github.com/johnhollen/Procedural-Ocean

最终效果不是特别漂亮,但作者给出了详细的笔记,这就是收录这项源码的原因。

记笔记:TNM084,是Linköping University 的计算机图形学课程,github上有20有关多个项目,容易学习。

以下是对原作者笔记的翻译

项目介绍

该项目由JohnHollén(johho982)在LinköpingUniversity的TNM084-过程图像课程中完成。 最初的计划是创建一个程序生成的行星,但是事实证明,这对我来说太具有挑战性,因为我对OpenGL / WebGL并不特别熟练。 该计划已更改为创建海洋,而不是空间海洋。

我之所以选择在WebGL中进行该项目,是因为Web编程是我最喜欢的编程类型,并且在浏览器中运行3D图形的想法非常酷。

此应用程序已经过测试,并且可以在2011年MacBook Pro的Safari,Chrome和Firefox中正常运行。 从[2]中获取了此应用中使用的3D噪声的实现。

框架和库

该应用程序是使用以下库编写的:

  • Three.js
  • JQuery 
  • GLSL 

基本场景设置

场景仅包含两个对象。 一个大立方体用作天空盒,而一个由100x50三角形组成的平面用作水面。 这两个对象都是通过使用它们各自的THREE.js方法THREE.CubeGeometry和THREE.PlaneGeometry创建的。 场景中只有一个光源,即THREE.PointLight。 点光源是没有方向的简单光源,仅具有距离和强度。

场景中还有两个摄像头。 我使用的是THREE.PerspectiveCamera,还有一个用于反射水中天空的THREE.CubeCamera。 稍后将描述如何完成天空反射。

天空实现

第一个想法是创建一个行星,因此首先创建了空间环境。 首先,创建了大立方体并将其缩放为10000x10000x10000的大小,并配置为仅显示其内侧。 为了将着色器程序附加到天空盒,使用了THREE.ShaderMaterial。 着色器材质对象将着色器程序作为输入,并在后台进行所有着色器绑定和编译。 天空盒的顶点着色器并不是很有趣,因为它只为片段着色器提供顶点位置。

但是,片段着色器更有趣。 使用 eight-octave multifractal [[1]渲染天空纹理,该分形也使用时间来移动天空。 天空纹理的完整代码如下所示。


varying vec3 v_position;
uniform float time;

vec4 spaceClouds(vec3 cubePos){
  //Normalize the cube position to map it on to a sphere.
  vec3 spherePos = normalize(cubePos);
  
  const int octaves = 8;
  float sum = 0.0;
  float frequency = 0.7;
  float weight = 1.3;

  for(int i = 0; i < octaves; ++i){
    sum = sum + weight*snoise(frequency*spherePos+time*0.1);
    frequency = 2.0*frequency;
    weight = 0.5*weight;
  }
  sum = sum/float(octaves);
  float val = 0.5+0.5*sum;
  val = pow(1.8*val, 12.0);

  //Return the resulting color vector.
  return vec4(0.5*val*val*val, 0.5*val*val, 0.5*val, 1.0);
}

void main(){
  gl_FragColor = spaceClouds(v_position);
}

通过反复试验找到频率和重量的值,直到找到满意的结果。 当发现令人满意的结果时结束。

海洋的实现

如前所述,水面由具有100x50三角形的平面组成。 首先是将水表面配置为渲染为线框。 完成后,开始创建波形。 我想创造一个海洋,因此我想要相当大的波形。 为了获得类似海洋的波形,在水面的顶点着色器中实现了Gerstner波。

Gerstner Waves

规则的正弦波顶部为圆形,而水波顶部则更尖。 这就是Gerstner波的出现。Gerstner波是不同幅度和频率的正弦波之和。 Gerstner波函数如下[3]:

Equation 1 - The Gerstner wave function.

其中A是振幅,ω是频率,φ是相位常数。 Q是确定波的陡度的参数[3]。 此功能不仅会移动波浪的高度,还会移动顶点的x和y坐标。

我没有在波动函数中使用预定义的波动,而是使用顶点位置的单纯形噪声作为参数Q和A,以实现具有随机行为的波动。 事实证明这看起来不错,毕竟这是我追求的视觉效果。

顶点着色器

生成的水表面顶点着色器如下所示:


varying vec2 st;
varying vec3 v_position;
varying vec3 viewDirection;
uniform float time;

varying vec3 N;
varying vec3 v;

void main(){

  float height = 0.0;

  float newX = position.x;
  float newY = position.y;

  //Total number of waves
  const int nrWaves = 50;

  //Create the noise variable here in order to gain performance
  float noiseVar = snoise(position*0.004+time*0.2);

  //Sum the waves
  for(int i = 0; i < nrWaves; i++){
    height += (noiseVar*sin(dot(vec2(newX, newY), vec2(0.01, 0.01))+time*1.2));

    newX += dot(1.0*1.0*noiseVar, cos(dot(vec2(newX, newY), vec2(0.01, 0.01))+time*1.2));
    newY += dot(1.0*1.0*noiseVar, cos(dot(vec2(newX, newY), vec2(0.01, 0.01))+time*1.2));
  }

  //Set the new position of the vertex
  vec3 newPosition = position;
  newPosition.x = newX;
  newPosition.y = newY;
  newPosition.z = height;
  gl_Position = projectionMatrix*modelViewMatrix*vec4(newPosition, 1.0);

  st = uv;
  v_position = position;

  //View direction, used for reflecting the sky
  viewDirection = vec3(modelMatrix*vec4(position, 1.0) - vec4(cameraPosition, 1.0));

  //Other variables for specular lighting 
  v = vec3(modelViewMatrix * vec4(position, 1.0));
  N = (normalMatrix * normal);
}

实施此着色器时,实际上是这样的

Figure 1 - 作为线框渲染的波浪的水。 天空盒已被禁用。

现在我有了水面的波浪,但是水面本身仍然是完全平坦的,因此我继续创建波纹状的表面。 这是在水面的片段着色器中完成的。

在片段着色器中完成的第一件事是使用[4]中的技术实现Phong Shading。 现在我有了一个漂亮的照明模型,但是水仍然没有任何涟漪。 为了给水面增加波纹,我首先尝试使用从[5]中借用的法线贴图,但是我无法通过直接采样法线来获得良好的结果。 相反,我自己想出了一个解决方案。 当我采样法线贴图时,我只是开始随噪声和纹理坐标一起摆弄。 确实没有科学依据,数学可能是错误的。 但是结果很不错。

正如我在开始时提到的,天空也反射在水中。 天空反射是使用前面提到的THREE.CubeCamera进行的。 多维数据集相机会将场景映射到多维数据集,并且其渲染目标可用作纹理,以作为统一的textureCube发送到着色器程序。 通过首先在着色器中计算viewDirection,可以使用viewDirection从textureCube中提取颜色。 然后,水表面的总颜色将变为反射颜色+环境光+散射光+镜面光。

最终的片段着色器

生成的片段着色器如下所示。


varying vec2 st;
varying vec3 viewDirection;
varying vec3 v_position;

uniform sampler2D normalMap;
uniform samplerCube skyTexture;

uniform vec3 pointLightColor;
uniform vec3 pointLightPosition;
uniform float pointLightDecay;

uniform float time;


varying vec3 N;
varying vec3 v;

void main(){
  //Create normal from normalMap combined with noise. 
  vec3 sampledNormal = (normalize(texture2D(normalMap, st*0.05*tan(snoise(0.11*v_position+time*2.0))).rgb*2.0-1.0) + N*0.2*sin(snoise(0.05*v_position+time)));

  vec3 L = normalize(pointLightPosition - v);
  vec3 E = normalize(-v); // we are in Eye Coordinates, so EyePos is (0,0,0)
  vec3 R = normalize(-reflect(L, sampledNormal));

  //calculate Ambient Term:
  vec4 Iamb = vec4(pointLightColor, 1.0)*pointLightDecay;

  //calculate Diffuse Term:
  vec4 Idiff = vec4(pointLightColor, 1.0)* max(dot(sampledNormal, L), 0.0);
  Idiff = clamp(Idiff, 0.0, 1.0)*pointLightDecay;

  // calculate Specular Term:
  vec4 Ispec = vec4(pointLightColor, 1.0) * pow(max(dot(R,E), 0.0), 4.0);
  Ispec = clamp(Ispec, 0.0, 1.0);

  //Calculate the reflected direction from the skybox
  vec3 reflectedDirection = normalize(reflect(viewDirection, sampledNormal));

  vec4 reflectionColor = normalize(textureCube(skyTexture, reflectedDirection));
  gl_FragColor = reflectionColor + (Iamb + Idiff + Ispec);
}

结果

结果在页面顶部。 在我看来,令人信服的海洋令人愉悦,尽管场景本身可能是不现实的。 它也相当轻巧,在我的2011年MacBook Pro上以30 FPS的速度运行,并在Safari中集成了Intel HD Graphics 3000 512 MB。

References

  1. Alfons Christian, Real-time Procedural Planetshttp://www.student.itn.liu.se/%7Echral647/tnm084/tnm084-2011-real-time_procedural_planets-chral647.pdf, Accessed: 2015-09-11.
  2. Ian McEwan, Ashima Arts, https://github.com/ashima/webgl-noise/blob/master/src/noise3D.glsl, Accessed: 2015-09-11.
  3. NVIDIA, Chapter 1. Effective Water Simulation from Physical Modelshttp://http.developer.nvidia.com/GPUGems/gpugems_ch01.html, Accessed: 2015-09-11.
  4. OpenGL, Per Fragment Lightinghttps://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php,
    Accessed: 2015-09-12.
  5. Water normal texture: https://github.com/jbouny/ocean/tree/master/assets/img, Accessed: 2015-09-12.
发布了194 篇原创文章 · 获赞 8 · 访问量 9844

猜你喜欢

转载自blog.csdn.net/qq_43439240/article/details/104559961