一. 水的焦散

焦散(Caustic),是一种光学现象,是由于光线在曲面经过反射或折射后形成的聚光效果,类似于凸透镜效果。

对于不透明物体来说,当光线照射到具有强反射属性的曲面对象时,会形成反射焦散现象;而当光线照射到透明物体时,如流动的水或玻璃杯,通过折射后形成聚光的折射焦散现象。

同理,对于水体来说,焦散效果不是在水面产生。一部分光线在水表面发生折射,穿透水面在水底形成折射焦散。另一部分光线被水面反射,可以在墙壁等平面形成反射焦散。

本章内容主要介绍了从美学的角度出发,来实时渲染水下的折射焦散的方法。

二. 原理与实现

光线入水后发生一次折射后继续前进,随着入水深度的增减,光强度逐渐减弱。最后,一部分光子经过不同路径,碰到了海底相同的区域并将其照亮,形成明亮的光斑,类似于凸透镜的聚光现象。无论是基于光线追踪还是逆向光线追踪的方法来实现,都非常的费时,因为只有极小部分的计算对最终结果有实际意义。

为了方便计算焦散,大胆假设:
       1. 我们是在赤道上计算正午的水面焦散,这意味着太阳处于头顶正上方;
       2. 对焦散效果做出贡献的光线与海底垂直。
          (光线从入水到碰到海底,传播距离最短的光线,才容易形成焦散,其余的被介质吸收化成热量发散)

具体实现步骤分为两步:
      1. 绘制海底底面:
            a. 假设有一道垂直于该顶点的折射光线;
            b. 找到该折射光线与水面的交点;
            c. 计算交点的法向量
            d. 逆向使用斯涅尔定律(Shell)计算入射光线;
               (书中代码并没有计算入射光线,而是使用了水面顶点法线方向和水面顶点与水底的距离做为采样坐标)
            d. 使用入射光线作为纹理坐标对"太阳"贴图采样,采样结果为焦散的强度;
      2. 渲染水表面。

代码如下,首先是申明两个Pass,分别绘制了海底和水面:

SubShader
{Tags {"Queue" = "Geometry" "RenderType" = "Geometry" } pass{CGPROGRAM#pragma vertex vert#pragma fragment fragENDCG} Tags {"Queue" = "Transparent" "RenderType" = "Transparent" }pass{Blend SrcAlpha OneMinusSrcAlpha CGPROGRAM#pragma vertex vertWater#pragma fragment fragWaterENDCG}
}
v2f vertWater(a2v v){v2f o;float3 worldPos = mul(unity_ObjectToWorld, v.v.vertex.xyz = GetWaterWavePos(o.worldNormal = GetWaterWaveNormal(o.pos = UnityObjectToClipPos(v.o.uv = v.texcoord.return o;
}       fixed4 fragWater(v2f i) : SV_Target{ fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);fixed3 lambert = 0.5 * max(0, dot(worldNormal, worldLightDir)) + 0.5;fixed3 diffuse = lambert * _WaterColor.rgb * _LightColor0.xyz;return fixed4(diffuse * _WaterColor.rgb, _WaterColor.a);
}   v2f vert(a2v v){v2f o;float3 worldPos = mul(unity_ObjectToWorld, v.vertex);  v.vertex.xyz = v.vertex.xyz - fixed3(0,0,_PlaneDistance);o.worldNormal = fixed3(0,1,0);o.worldPos = worldPos;o.pos = UnityObjectToClipPos(v.vertex);    o.uv = v.texcoord.xy;   return o;
}       fixed4 frag(v2f i) : SV_Target{ fixed3 intercept = CaculateIntercept(i.worldPos);fixed3 caustic = tex2D(_SunLight, intercept.xy * _Caustic).r;fixed3 worldNormal = normalize(i.worldNormal);fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);fixed3 lambert = 0.5 * max(0, dot(worldNormal, worldLightDir)) + 0.5;fixed3 diffuse = lambert * _PlaneColor.rgb * _LightColor0.xyz + caustic * _CausticColor.rgb;return fixed4(diffuse, 1);
}   

计算水面波,这里我叠加了4个正弦波:

float3 GetWaterWavePos(float3 worldPos)
{float3 disPos = float3(0,0,0);disPos += CaculatePos(150, 0.002, 1, _Time.x * 10, fixed2(0.5,-1), worldPos,2); disPos += CaculatePos(200,0.001, 1, _Time.x * 60, fixed2(4,-0.5), worldPos,4);  disPos += CaculatePos(300, 0.001, 1, _Time.x * 20, fixed2(1,-1), worldPos,5);disPos += CaculatePos(90, 0.001, 1, _Time.x * 100, fixed2(1.5,1), worldPos,1);return mul(unity_WorldToObject, float4(worldPos + disPos, 1));
}float3 CaculatePos(fixed A, fixed omiga, fixed fai, fixed t, fixed2 dir, float3 p, int k)
{dir = normalize(dir);fixed height =  sin( omiga * (p.x * dir.x + p.z * dir.y) + t * fai);float3 pos = float3(0, A * pow((height+1)/2, k) * 2 ,0);return pos;
}

计算法线:

fixed3 GetWaterWaveNormal(float3 worldPos)
{fixed3 n = fixed3(0,0,0);n += CaculateNormal(150, 0.002, 1, _Time.x * 10, fixed2(0.5,-1), worldPos,2); n += CaculateNormal(200, 0.001, 1, _Time.x * 60, fixed2(4,-0.5), worldPos,4);n += CaculateNormal(300, 0.001, 1, _Time.x * 20, fixed2(1,-1), worldPos,5);n += CaculateNormal(90, 0.001, 1, _Time.x * 100, fixed2(1.5,1), worldPos,1);return fixed3(n.x,1,n.z);
}fixed3 CaculateNormal(fixed A, fixed omiga, fixed fai, fixed t, fixed2 dir, float3 p, int k)
{dir = normalize(dir);fixed S = sin(omiga * (p.x*dir.x + p.z*dir.y) + t * fai);fixed C = cos(omiga * (p.x*dir.x + p.z*dir.y) + t * fai);fixed WA = k * omiga * A;fixed x = dir.x * WA * pow((S+1)/2, k - 1) * C;fixed y = 1;fixed z = dir.y * WA * pow((S+1)/2, k - 1) * C;fixed3 normal = fixed3(x, y, z);return normal;
}

计算"太阳贴图"的采样坐标:

fixed3 CaculateIntercept(float3 worldPos)
{fixed3 pos = GetWaterWavePos(worldPos);fixed3 worldNormal = GetWaterWaveNormal(worldPos);return pos + worldNormal * (pos.z + _PlaneDistance);
}

Unity实现效果:

三. 一些计算细节

a. 斯涅尔定律(Snell)

斯涅尔定律:当光波从一种介质传播到另一种具有不同折射率的介质时,会发生折射现象,入射角与折射角之间满足关系:

 和  分别是两个介质的折射率, 和  分别是入射角和折射角,是入射光和折射光与平面法线的夹角。水的折射率为1.33,空气的折射率近似等于1.00001。

不涉及角度计算的话,Foley等人在1996年提出,假如入射线,折射线,表面法线是共面的,那么有:

 : 折射线        : 表面法线        : 入射线          : 两个介质的折射率


b. 正弦波及法线计算

水面的波函数如下(书里的那个没看懂,所以这里替换为第一章中正弦波表现水波的方法):


顶点法线:
                                      


c. 纹理的采样坐标计算

使用水面顶点法线方向水面顶点与水底的距离做为采样坐标:

【GPU Gems 学习笔记】Rendering Water Caustics相关推荐

  1. 【GPU Gems 学习笔记】Implementing Improved Perlin Noise

    柏林噪声 柏林噪声算法(Perlin Noise)是Ken Perlin在1983年提出的一种渐变噪声.和完全随机的白噪声相比,柏林噪声做到了杂乱而有序.能够更好地模拟自然界中的随机现象的复杂性和相互 ...

  2. PYRIT 强大的密码分析工具(可使用GPU分析)-学习笔记

    优势: 可运用GPU的运算加速生成PMK 本身支持抓包获取四步五首过程,无需用Airodump抓包 也支持传统的读取airodump抓包获取四步握手的方式 只抓取WAP四次握手过程包 pyrit -r ...

  3. 《Real-Time Rendering》第四版学习笔记——Chapter 9 Physically Based Shading(三)

    前文: <Real-Time Rendering>第四版学习笔记--Chapter 9 Physically Based Shading(一) <Real-Time Renderin ...

  4. Tensorflow学习笔记6:解决tensorflow训练过程中GPU未调用问题

    Tensorflow学习笔记6:解决tensorflow训练过程中GPU未调用问题 参考文章: (1)Tensorflow学习笔记6:解决tensorflow训练过程中GPU未调用问题 (2)http ...

  5. Caffe学习笔记2--Ubuntu 14.04 64bit 安装Caffe(GPU版本)

    0.检查配置 1. VMWare上运行的Ubuntu,并不能支持真实的GPU(除了特定版本的VMWare和特定的GPU,要求条件严格,所以我在VMWare上搭建好了Caffe环境后,又重新在Windo ...

  6. 《Real-Time Rendering》第四版学习笔记——Chapter 9 Physically Based Shading(一)

    一.光的物理特性 光与物体的交互形成了基于物理着色的基础. 在物理层面上,光的模型为电磁横波.每个波都有一个单一的波长λ\lambdaλ.具有单一波长的光叫单色光(monochromatic ligh ...

  7. 《基于GPU加速的计算机视觉编程》学习笔记

    <基于GPU加速的计算机视觉编程>学习笔记(1) 最近打算 准备工作 CUDA开发环境(主要是查看N卡的信息) 在WIN10下安装CUDA工具包 最近打算 在训练模型的时候,感觉电脑非常吃 ...

  8. Unity3D学习笔记6——GPU实例化(1)

    文章目录 1. 概述 2. 详论 3. 参考 1. 概述 在之前的文章中说到,一种材质对应一次绘制调用的指令.即使是这种情况,两个三维物体使用同一种材质,但它们使用的材质参数不一样,那么最终仍然会造成 ...

  9. Unity3D学习笔记8——GPU实例化(3)

    文章目录 1. 概述 2. 详论 2.1. 自动实例化 2.2. MaterialPropertyBlock 3. 参考 1. 概述 在前两篇文章<Unity3D学习笔记6--GPU实例化(1) ...

最新文章

  1. PHP QQ 登录接口应用
  2. Java对象的生命周期与作用域的讨论(转)
  3. OpenStack云计算口袋书-第一章-开始OpenStack之旅(一)
  4. Hadoop 同步集群时间ntp
  5. ES6减少魔法操作之Reflect
  6. HDU 3966-Aragorn's Story 树链剖分+树状数组
  7. 主程序分析法MATLAB编程,专题五  概率统计问题的Matlab求解
  8. 交换二叉树的每个节点的左右子树
  9. bzoj1179 Atm
  10. ProgressDialog弹出时的底色变暗(转)
  11. Android开发的四大组件
  12. UNIX网络编程——常用服务器模型总结
  13. 标准机器学习数据集的最佳结果
  14. 详解哈夫曼树和哈夫曼编码
  15. 一刀工具箱 - URL链接编码解码工具
  16. Spring Initializr文档翻译及源码解读
  17. tom 猫 android 版本 设计,一种Tom猫的简单实现
  18. php画五角星,H5怎样用绘制五角星
  19. vux安装时报vux-loader配置问题
  20. Xcode免证书导出ipa文件

热门文章

  1. arr和arr的区别以及数组首元素地址和整个数组地址的区别
  2. linux中目录前 表示什么意思,linux的命令行前面的root@linux~ #中的每个部分代表什么意思?...
  3. java poi 水印_JAVA不使用POI给Word文档添加水印
  4. 入网许可证_入网许可证怎么查询方法 入网许可证查询方法【图文演示】
  5. Linux查看文件行数
  6. iOS 教你如何修改微信运动步数
  7. x_train, x_test, y_train, y_test到底是什么?
  8. qtable sorting enable中文是按照什么顺序_漫威电影:22部电影观影顺序(附ZY)
  9. 飞塔防火墙×××之隧道分离 (Split Tunneling)
  10. Spring Boot 无侵入式 实现API接口统一JSON格式返回