![79cad27bce95542891047163bbb58ea3.png](https://i-blog.csdnimg.cn/blog_migrate/2174ef3164f185d739fa89d41f7d63c6.jpeg)
![54f86e794e9080fbf2ff798cd072ebdb.png](https://i-blog.csdnimg.cn/blog_migrate/dc3de2c1ca0f43ae0d92214bea250931.jpeg)
焦散原因:光线从曲面反射或折射从而在接受面上聚焦于某一区域
计算焦散
![ae15901bd31f62d478cef275fe3c54f7.png](https://i-blog.csdnimg.cn/blog_migrate/a54930d8eb0566ae32dc41e0b37f87c7.png)
斯涅耳定律:(编程不友好)
![3cf7ada95b217ab331115cde2c1d3d0b.png](https://i-blog.csdnimg.cn/blog_migrate/58c638c5773f3d4a17f2e76dff3fa03d.png)
Foley公式:(编程友好,假设入射光线,传输光线和表面法线共面)
![02841ddca70121343c96deace979be40.png](https://i-blog.csdnimg.cn/blog_migrate/4c628efb35a4a7fbe910b78fc66ebbc2.png)
光线追踪方法:
- 前向光线追踪:大部分光线未到达水底造成计算浪费
- 逆向光线追踪:从水底出发按时间反向计算给定点的入射光总和,通过蒙特卡洛采样计算,从聚焦采样点的半球发射各个方向的光线,击中物体的光线丢弃,到达水面的光线使用逆向斯涅尔公式,计算光线是否为光源发出
实时方法:
- Jos Stam (Stam 1996):使用波理论计算动画焦散纹理
- Lasse Staff Jensen and Robert Golias (Jensen and Golias 2001):使用FFT来造型水体,从太阳向水面网格的每个顶点追踪光线,根据斯涅尔定律折射生成新的光线
本文方法
简化版的逆向蒙特卡洛光线追踪。
假设:
- 假设太阳在正上方,计算太阳覆盖的天空对应的角度为0.53度
- 假设照亮海面的光线发射于采样点的垂直上方,从水面到水底经过最短距离的光线最容易形成焦散,因此垂直光线最被考虑
![7d8a45088f5357dfa7cd27e229962ed4.png](https://i-blog.csdnimg.cn/blog_migrate/f562fc8706e8cf7cecd5895a4b38e617.png)
算法:
- 在绘制完地面之后从海底开始计算,使用addictive混合
- 创建和波浪网格相同粒度的网格,根据焦散值逐顶点着色
- 将焦散网格的顶点垂直向上投影到波浪网格,通过有限差分计算投影点的法线
- 通过斯涅耳定律计算入射光并通过比较角度测试其发射自太阳的密度
![48ee7726693892a1745d53550b9ab2c7.png](https://i-blog.csdnimg.cn/blog_migrate/021ed9be12653166311a26ddcdf35a07.png)
OpenGL实现
多Pass实现:
- 第一个pass:渲染海底河床
- 第二个pass:使用焦散生成器逐顶点照亮河床的细分网格
- Send a vertical ray.
- Collide the ray with the ocean's mesh.
- Compute the refracted ray using Snell's Law in reverse.
- Use the refracted ray to compute texture coordinates for the "Sun" map.
- Apply texture coordinates to vertices in the finer mesh.
- 第三个pass:使用波浪渲染器渲染波浪
高级着色语言实现
逐像素计算焦散,使用偏导数计算发现而不是有限差分
方法:
- 在屏幕空间渲染程序化纹理,部分像素可见时节省开销,但是大量像素可见时会增加大量工作
- 在纹理空间渲染固定分辨率的render target,每帧的开销固定,但是难以估计适合当前场景的rt分辨率,依赖纹理过滤时若纹理像素比过低则可能引入双线性过滤交叉图案
优化:
观察到使用斯涅尔定律折射与根据沿波法线从水表面到地板表面的距离映射环境贴图存在很小的视觉差异,所以进行纹理采样,垂直光采中心区域,角度光进行衰减。该方法允许不平坦的河床,将深度写入顶点数据
![3494e78addb9755fc766555aea9b79ee.png](https://i-blog.csdnimg.cn/blog_migrate/2034be274f5101335e6a934ad987e7b0.png)
波函数:
![a5850ad2026c802056a8ee9e18ec4a79.png](https://i-blog.csdnimg.cn/blog_migrate/38d88db77c19898b9d7856be59ae9992.png)
法线计算:
![e40d2ae1d92cb7c5e7e652b76f520b70.png](https://i-blog.csdnimg.cn/blog_migrate/345385166c0d1744114a1db2b3593abc.png)
![47fed8db79bd227e58e3a913a496b6fc.png](https://i-blog.csdnimg.cn/blog_migrate/6ae1fde7ce9aeb394748fe5e41fa83ce.png)
![2c61e4842aff65e3bba4138cdcec7b1b.png](https://i-blog.csdnimg.cn/blog_migrate/65ebf1a16834d1dfd029cb92533ca874.png)
距离计算:
![1a527f5cac277b3973a2a7295b613309.png](https://i-blog.csdnimg.cn/blog_migrate/3245187a33fc7c7ba2ac70e2fe1b01df.png)
![fe20bf7433039aed6a6a195ac3189f0e.png](https://i-blog.csdnimg.cn/blog_migrate/3f707b1c040bf4b0821378f1c408c686.png)
// Caustics
// Copyright (c) NVIDIA Corporation. All rights reserved.
//
// NOTE:
// This shader is based on the original work by Daniel Sanchez-Crespo
// of the Universitat Pompeu Fabra, Barcelona, Spain.
#define VTXSIZE 0.01f // Amplitude
#define WAVESIZE 10.0f // Frequency
#define FACTOR 1.0f
#define SPEED 2.0f
#define OCTAVES 5
// Example of the same wave function used in the vertex engine
float wave(float x, float y, float timer)
{
float z = 0.0f;
float octaves = OCTAVES;
float factor = FACTOR;
float d = sqrt(x * x + y * y);
do {
z -= factor * cos(timer * SPEED + (1/factor) * x * y * WAVESIZE);
factor = factor/2;
octaves--;
} while (octaves > 0);
return 2 * VTXSIZE * d * z;
}
// This is a derivative of the above wave function.
// It returns the d(wave)/dx and d(wave)/dy partial derivatives.
float2 gradwave(float x, float y, float timer)
{
float dZx = 0.0f;
float dZy = 0.0f;
float octaves = OCTAVES;
float factor = FACTOR;
float d = sqrt(x * x + y * y);
do {
dZx += d * sin(timer * SPEED + (1/factor) * x * y * WAVESIZE) * y * WAVESIZE
- factor * cos(timer * SPEED + (1/factor) * x * y * WAVESIZE) * x/d;
dZy += d * sin(timer * SPEED + (1/factor) * x * y * WAVESIZE) * x * WAVESIZE
- factor * cos(timer * SPEED + (1/factor) * x * y * WAVESIZE) * y/d;
factor = factor/2;
octaves--;
} while (octaves > 0);
return float2(2 * VTXSIZE * dZx, 2 * VTXSIZE * dZy);
}
float3 line_plane_intercept(float3 lineP, float3 lineN, float3 planeN, float planeD)
{
// Unoptimized
// float distance = (planeD - dot(planeN, lineP)) /
// dot(lineN, planeN);
// Optimized (assumes planeN always points up)
float distance = (planeD - lineP.z) / lineN.z;
return lineP + lineN * distance;
}//使用结果进行lightmap采样
float4 main(VS_OUTPUT vert, uniform sampler2D LightMap : register(s0),
uniform sampler2D GroundMap : register(s1), uniform float Timer) : COLOR
{
// Generate a normal (line direction) from the gradient
// of the wave function and intercept with the water plane.
// We use screen-space z to attenuate the effect to avoid aliasing.
float2 dxdy = gradwave(vert.Position.x, vert.Position.y, Timer);
float3 intercept = line_plane_intercept( vert.Position.xyz, float3(dxdy, saturate(vert.Position.w)), float3(0, 0, 1), -0.8);
// OUTPUT
float4 colour;
colour.rgb = (float3)tex2D(LightMap, intercept.xy * 0.8);
colour.rgb += (float3)tex2D(GroundMap, vert.uv);
colour.a = 1;
return colour;
}
![bc08008b3201a32e530f732dc9455cdc.png](https://i-blog.csdnimg.cn/blog_migrate/f1a07ede0b2fc8f9454a11e9d4132ef4.png)
足够大的分辨率纹理具有与屏幕空间渲染一样好的质量,而在显示大量像素时不会影响性能。此外,在使用渲染目标纹理时,我们可以利用自动mipmap生成和各向异性过滤来减少焦散的混叠,这在渲染到屏幕空间时无法实现。