源码:
作者: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
Alfons Christian, Real-time Procedural Planets,http://www.student.itn.liu.se/%7Echral647/tnm084/tnm084-2011-real-time_procedural_planets-chral647.pdf, Accessed: 2015-09-11. Ian McEwan, Ashima Arts,https://github.com/ashima/webgl-noise/blob/master/src/noise3D.glsl, Accessed: 2015-09-11. NVIDIA, Chapter 1. Effective Water Simulation from Physical Models,http://http.developer.nvidia.com/GPUGems/gpugems_ch01.html, Accessed: 2015-09-11. OpenGL, Per Fragment Lighting,https://www.opengl.org/sdk/docs/tutorials/ClockworkCoders/lighting.php, Accessed: 2015-09-12. Water normal texture:https://github.com/jbouny/ocean/tree/master/assets/img, Accessed: 2015-09-12.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。