分享一下我的程序中水面的具体实现,水面渲染一般包括三个部分:反射、透射、波动模拟,以下将依次介绍这三种效果的实现。


反射

实时渲染中实现反射最常用的方案是ssr(screen space reflections),也就是屏幕空间的反射,只需要额外的一个后处理pass 特别适合延迟渲染,它将屏幕内可见的内容渲染到反射表面作为反射贴图使用。实现思路类似于光线追踪的做法,如下图所示

求取反射和场景的相交点

首先通过入射向量(眼睛所看的方向)和反射表面的法线求取反射向量,然后顺着反射向量的方向逐次增加一定的步长,检测反射向量是否和场景中的物体相交(如图中蓝色箭头所示),如果相交则返回相交点的颜色,如果没有则反射向量继续前进,直到最终和场景相交或者长度超出范围。此时返回的每个交点颜色即为场景反射到水面的颜色,把它输出作为反射贴图进行后续的水面颜色计算。实现代码如下

uniform mat4 viewMatrix;
uniform mat4 projectMatrix, invProjMatrix;
layout(bindless_sampler) uniform sampler2D lightBuffer, matBuffer, normalBuffer, depthBuffer;
uniform vec2 screenSize, pixelSize;in vec2 vTexcoord;
out vec4 ReflectColor;vec2 BinarySearch(float start, float end, vec2 projUV, vec3 refDir, vec3 refPos, vec4 projRef) {float startLen = start, endLen = end;float curLen = start + (end - start) * 0.5;vec2 uvCoord = projUV;for(int i = 0; i < 3; i++) {vec3 curPos = refPos + refDir * curLen;vec4 projPos = projectMatrix * vec4(curPos, 1.0);projPos /= projPos.w;vec3 projCoord = projPos.xyz * 0.5 + 0.5;float storedDepth = texture2D(depthBuffer, projCoord.xy).r;if(storedDepth <= projCoord.z) {endLen = curLen;uvCoord = projCoord.xy;} else startLen = curLen;curLen = startLen + (endLen - startLen) * 0.5;}return uvCoord;
}vec4 RayCast(vec3 refDir, vec3 refPos) {float stepLen = 200.0;float lenStart = 0.0;float lenBefore = lenStart;float lenCurrent = lenStart;float border = 0.1;float oneMinBorder = 0.9;float invBorder = 10.0;vec4 projRef = projectMatrix * vec4(refPos, 1.0);projRef /= projRef.w;for(float i = 0.0; i < 10.0; i += 1.0) {vec3 curPos = refPos + refDir * lenCurrent;vec4 projPos = projectMatrix * vec4(curPos, 1.0);projPos /= projPos.w;vec3 projCoord = projPos.xyz * 0.5 + 0.5;float storedDepth = texture2D(depthBuffer, projCoord.xy).r;vec4 storedView = invProjMatrix * vec4(vec3(projCoord.xy, storedDepth) * 2.0 - 1.0, 1.0);storedView /= storedView.w;float refFlag = texture2D(matBuffer, projCoord.xy).a;if(storedDepth >= 1.0)return FAIL_COLOR;else if(curPos.z <= storedView.z + 0.01 && refFlag > 0.9 && refPos.z >= storedView.z - 20.0) {vec2 searchData = BinarySearch(lenBefore, lenCurrent, projCoord.xy, refDir, refPos, projRef);vec4 storedData = texture2D(lightBuffer, searchData);if(searchData.y >= oneMinBorder)storedData = mix(storedData, FAIL_COLOR, (searchData.y - oneMinBorder) * invBorder);if(searchData.x >= oneMinBorder)storedData = mix(storedData, FAIL_COLOR, (searchData.x - oneMinBorder) * invBorder);else if(searchData.x <= border)storedData = mix(storedData, FAIL_COLOR, (border - searchData.x) * invBorder);return storedData; } else {lenBefore = lenCurrent;            lenCurrent = lenStart + stepLen * (i + random(gl_FragCoord.xyz, i));}}return FAIL_COLOR;
}void main() {float refFlag = texture2D(matBuffer, vTexcoord).a;if(refFlag > 0.9)ReflectColor = texture2D(lightBuffer, vTexcoord);else {float depth = texture2D(depthBuffer, vTexcoord).r;vec3 ndcPos = vec3(vTexcoord, depth) * 2.0 - 1.0;vec4 viewPos = invProjMatrix * vec4(ndcPos, 1.0);viewPos /= viewPos.w;vec3 normal = texture2D(normalBuffer, vTexcoord).xyz * 2.0 - 1.0;vec3 viewNormal = mat3(viewMatrix) * normal;vec3 reflectDir = normalize(reflect(viewPos.xyz, viewNormal));ReflectColor = RayCast(reflectDir, viewPos.xyz);}ReflectColor.rgb = pow(ReflectColor.rgb, GAMMA);
}

此处做了二分查找优化,事实上如果步长过长则交点未必是反射向量和场景首次相交所产生的,用二分查找对相交到的步长进行细化能够比较准确的求取交点。

另外屏幕空间反射还有一个问题,它只能反射屏幕空间中存在的内容,纹理边缘会出现反射不到内容的情况,此时可以对边缘像素和其他像素进行淡出使反射纹理的边缘看上去没有那么生硬。


透射

水是透明的,也就是说能够看到水面下的场景。由于我采用了延迟渲染 导致alpha blend没法直接使用,那么就需要把场景和水面分开来渲染,最后再加入一步后处理把场景和水面混合起来,水下场景可以根据水下位置到水面的距离进行混合。实现步骤如下

render->setFrameBuffer(screen);
renderMgr->renderScene(render, scene);
renderMgr->drawDeferred(render, scene, screen, sceneFilter);render->setFrameBuffer(waterFrame);
waterFrame->getDepthBuffer()->copyDataFrom(screen->getDepthBuffer());
renderMgr->renderWater(render, scene);renderMgr->drawCombined(render, scene, combinedChain->input, combinedChain->output);

该过程分为三部分,首先将场景渲染到一个fbo 在渲染水面前拷贝场景(不含水面)的深度作为之后水面渲染的初始深度,随后渲染水面到另一个fbo(此时被场景遮挡的水面不会通过深度测试,类似于将场景作为一个mask),最后从之前渲染的fbo中取出深度和颜色纹理送到一个pass 将场景和水面进行混合,混合shader如下

layout(bindless_sampler) uniform sampler2D sceneBuffer, sceneDepthBuffer, waterBuffer, waterDepthBuffer, matBuffer, waterNormalBuffer, bloomBuffer;
uniform vec2 pixelSize;
uniform mat4 invViewProjMatrix;
uniform vec3 light;
uniform vec3 eyePos;
uniform float quality;
uniform float useBloom;in vec2 vTexcoord;
out vec4 FragColor;#define START_H float(200.0)
#define END_H float(1600.0)
#define FOG_COLOR vec3(0.95)vec3 GenFogColor(vec4 worldPos, float depthView, vec3 sceneColor) {float worldH = worldPos.y / worldPos.w;float heightFactor = smoothstep(START_H, END_H, worldH);float fogFactor = exp2(-0.0000005 * depthView * depthView * LOG2);fogFactor = mix(fogFactor, 1.0, heightFactor);fogFactor = clamp(fogFactor, 0.0, 1.0);return mix(FOG_COLOR, sceneColor, fogFactor);
}void main() {vec4 waterRefColor = texture2D(waterBuffer, vTexcoord);  vec4 sceneColor = texture2D(sceneBuffer, vTexcoord);if(useBloom > 0.5) {vec4 bloomColor = texture2D(bloomBuffer, vTexcoord);sceneColor.rgb += bloomColor.rgb;}float sDepth = texture2D(sceneDepthBuffer, vTexcoord).r;float wDepth = texture2D(waterDepthBuffer, vTexcoord).r;vec3 ndcScene = vec3(vTexcoord, sDepth) * 2.0 - 1.0;vec3 ndcWater = vec3(vTexcoord, wDepth) * 2.0 - 1.0;vec4 scenePos = invViewProjMatrix * vec4(ndcScene, 1.0); scenePos /= scenePos.w; vec4 waterPos = invViewProjMatrix * vec4(ndcWater, 1.0);waterPos /= waterPos.w;vec3 eye2Water = waterPos.xyz - eyePos;float depthView = length(eye2Water);vec4 waterMatColor = texture2D(matBuffer, vTexcoord);float waterFactor = 1.0 - step(0.2, waterMatColor.w);float depthFactor = clamp((waterPos.y - scenePos.y - 10.0) * 0.01, 0.0, 1.0) * waterFactor;vec3 waterNormal = texture2D(waterNormalBuffer, vTexcoord).rgb * 2.0 - 1.0;float ndote = -dot(waterNormal, normalize(eye2Water));vec3 sceneRefract = sceneColor.rgb;vec3 waterReflect = waterRefColor.rgb;vec3 waterRefract = waterMatColor.rgb;waterRefract = mix(sceneRefract, waterRefract, depthFactor).rgb;vec3 waterColor = mix(waterReflect, waterRefract, ndote);vec3 finalColor = mix(sceneColor.rgb, waterColor, waterFactor);FragColor = vec4(GenFogColor(waterPos, depthView, finalColor), wDepth);if(quality > 3.0) FragColor.rgb = vec3(1.0) - exp(-FragColor.rgb * 2.5);FragColor.rgb = pow(FragColor.rgb, INV_GAMMA);
}

由于是渲染的最后一个pass,此处将雾化和bloom以及gamma矫正一并完成。


波动模拟

水波的模拟在GPU Gems Effective Water Simulation from Physical Models 章节中有详细描述,此方法通过生成多个正弦波进行叠加可以模拟水面的波动,我直接将伪代码进行魔改作为水面mesh的vertex shader

uniform mat4 viewProjectMatrix;
uniform mat4 viewMatrix;
uniform float time;
uniform vec3 eyePos;layout (location = 0) in vec3 vertex;out vec3 vNormal;
out vec3 vViewNormal;
out vec3 vEye2Water;
out vec4 vProjPos;vec3 CalculateWavePosition(float q, float a, float w, vec3 dir, vec3 meshVert, float ph, float t) {float qa = q * a;float theta = dot(w * dir.xz, meshVert.xz) + ph * t;float cosTh = cos(theta);float sinTh = sin(theta);float x = qa * dir.x * cosTh;float z = qa * dir.z * cosTh;float y = a * sinTh;return vec3(x, y, z);
}vec3 CalculateWaveNormal(float q, float a, float w, vec3 dir, vec3 waveVert, float ph, float t) {float wa = w * a;float theta = dot(w * dir, waveVert) + ph * t;float cosTh = cos(theta);float sinTh = sin(theta);float x = dir.x * wa * cosTh;float z = dir.z * wa * cosTh;float y = q * wa * sinTh;return vec3(x, y, z);
}void main() {float l0 = 31.25, a0 = 0.16, s0 = 2.56;vec3 dir0 = vec3(0.58, 0.0, 0.42);float w0 = 2.0 / l0, ph0 = s0 * w0, q0 = 1.28;float l1 = 25.0, a1 = 0.22, s1 = 5.12;vec3 dir1 = vec3(0.31, 0.0, 0.69);float w1 = 2.0 / l1, ph1 = s1 * w1, q1 = 2.56;float l2 = 25.6, a2 = 0.22, s2 = 1.28;vec3 dir2 = vec3(0.33, 0.0, 0.67);float w2 = 2.0 / l2, ph2 = s2 * w2, q2 = 2.56;float l3 = 52.5, a3 = 0.34, s3 = 0.64;vec3 dir3 = vec3(0.26, 0.0, 0.74);float w3 = 2.0 / l3, ph3 = s3 * w3, q3 = 5.12;float l4 = 25.6, a4 = 0.34, s4 = 2.56;vec3 dir4 = vec3(0.3, 0.0, -0.7);float w4 = 2.0 / l4, ph4 = s4 * w4, q4 = 5.12;float l5 = 42.5, a5 = 0.46, s5 = 5.12;vec3 dir5 = vec3(0.4, 0.0, -0.6);float w5 = 2.0 / l5, ph5 = s5 * w5, q5 = 2.56;float l6 = 25.0, a6 = 0.22, s6 = 1.28;vec3 dir6 = vec3(0.1, 0.0, -0.9);float w6 = 2.0 / l6, ph6 = s6 * w6, q6 = 2.56;float l7 = 31.25, a7 = 0.16, s7 = 0.64;vec3 dir7 = vec3(0.43, 0.0, -0.57);float w7 = 2.0 / l7, ph7 = s7 * w7, q7 = 1.28;vec3 worldVertex = vertex + vec3(eyePos.x, 0.0, eyePos.z);vec3 pos0 = CalculateWavePosition(q0, a0, w0, dir0, worldVertex, ph0, time);vec3 pos1 = CalculateWavePosition(q1, a1, w1, dir1, worldVertex, ph1, time);vec3 pos2 = CalculateWavePosition(q2, a2, w2, dir2, worldVertex, ph2, time); vec3 pos3 = CalculateWavePosition(q3, a3, w3, dir3, worldVertex, ph3, time); vec3 pos4 = CalculateWavePosition(q4, a4, w4, dir4, worldVertex, ph4, time); vec3 pos5 = CalculateWavePosition(q5, a5, w5, dir5, worldVertex, ph5, time);vec3 pos6 = CalculateWavePosition(q6, a6, w6, dir6, worldVertex, ph6, time);vec3 pos7 = CalculateWavePosition(q7, a7, w7, dir7, worldVertex, ph7, time);vec3 position = pos0 + pos1 + pos2 + pos3 + pos4 + pos5 + pos6 + pos7;position.xz += worldVertex.xz;vec3 nor0 = CalculateWaveNormal(q0, a0, w0, dir0, position, ph0, time);vec3 nor1 = CalculateWaveNormal(q1, a1, w1, dir1, position, ph1, time);vec3 nor2 = CalculateWaveNormal(q2, a2, w2, dir2, position, ph2, time);vec3 nor3 = CalculateWaveNormal(q3, a3, w3, dir3, position, ph3, time);vec3 nor4 = CalculateWaveNormal(q4, a4, w4, dir4, position, ph4, time);vec3 nor5 = CalculateWaveNormal(q5, a5, w5, dir5, position, ph5, time);vec3 nor6 = CalculateWaveNormal(q6, a6, w6, dir6, position, ph6, time);vec3 nor7 = CalculateWaveNormal(q7, a7, w7, dir7, position, ph7, time);vec3 normal = nor0 + nor1 + nor2 + nor3 + nor4 + nor5 + nor6 + nor7;normal = vec3(0.0, 1.0, 0.0) - normal;vNormal = normal;vViewNormal = mat3(viewMatrix) * normal;vEye2Water = position - eyePos;vProjPos = viewProjectMatrix * vec4(position, 1.0);gl_Position = vProjPos;
}

最终得到如下效果

完整工程

https://github.com/zxx43/Tiny3D​github.com


改进方案

透明可以采用 OpenGL 4.6 中的原子计数器生成一张片元链表 在片元着色器中进行透明排序,此方法可以回避掉深度纹理拷贝操作

波动可以采用噪声纹理生成(此方法效率高一些),亦或是由fft生成(此方法真实程度高,适合模拟海浪)

shader 反射 水面_大规模水面渲染相关推荐

  1. shader 反射 水面_【Unity Shader】模拟水面包含折射与反射与波浪动画

    最近研究了一下Unity官方的BoatAttack案例,他们模拟的海浪效果很厉害,用的是Gerstner波来模拟水面起伏和波峰的白浪还有浮力系统,还做加入了焦散效果(Caustics)和平面反射(Pl ...

  2. Unity Shader - 实现类似镜面反射、水面扰动效果

    前几天,家里出了一些问题,搞得心情很不好,面试我也取消了. 唉,反正那个伤心啊,不过,昨天处理好了. 所以说啊,家和万事兴. 加油加油!!! 所以心情好了,我又写博客了. 另外说一下:图形我今年201 ...

  3. unity shader入门精要_Unity Shader 入门(一):渲染流水线

    一.参考与说明(需要写在开始东西): 1.1 Unity Shader 入门紧要学习 candycat1992/Unity_Shaders_Book​github.com 1.2 还有一些图形学的历史 ...

  4. Unity Shader 学习笔记(27)渲染轮廓线(描边)方法、卡通风格渲染、素描风格渲染

    Unity Shader 学习笔记(27)渲染轮廓线(描边)方法.卡通风格渲染.素描风格渲染 参考书籍:<Unity Shader 入门精要> 渲染轮廓线(描边) 五种方法: 基于观察角度 ...

  5. 现代软件工程团队项目贝塔阶段_大规模测试结果_2018.02.08

    现代软件工程团队项目贝塔阶段_大规模测试结果_2018.02.08 经过课程全班同学测试后,将收集到的所有BUG和建议汇总如下 BUG按照状态.严重程度.优先级进行了基本的分类 目前打算的BUG修改顺 ...

  6. ava_212_反射机制_动态操作_构造器_方法_属性_练习

    package java_212_反射机制_动态操作_构造器_方法_属性_练习; /** 属性 */ public class User { private int id; private int a ...

  7. Unity3D教程:教你如何利用Shader来进行3D角色的渲染 |

    Unity3D教程:教你如何利用Shader来进行3D角色的渲染 | Unity3D教程手册 http://www.manew.com/4982.html

  8. shader 反射 水面_【博物纳新】水面涟漪反射效果开源库测评

    [博物纳新]是UWA重磅推出的全新栏目,旨在为开发者推荐新颖.易用.有趣的开源项目,帮助大家在项目研发之余发现世界上的热门项目.前沿技术或者令人惊叹的视觉效果,并探索将其应用到自己项目的可行性.很多时 ...

  9. shader 反射 水面_unity水面波浪光照反射折射物理渲染着色器Lux Water 1.01

    unity水面波浪光照反射折射物理渲染着色器Lux Water 1.01 Lux Water 是一款简单而强大的水面渲染工具,可为你呈现最佳的水面折射.反射和光照效果. 渲染功能 • 基于物理的着色, ...

  10. 【Unity Shader学习笔记】实现反射与折射模拟水面、使用grabPass与环境贴图

    文章目录 写在前面 一个水波效果 大致组成部分与对应的实现方案 交界线与深度贴图 折射效果与GrabPass 使用Cubemap与法线信息来模拟反射 在正确的地点创建对应的cubemap 通过贴图获取 ...

最新文章

  1. 使用C++实现DPCM编码(左向预测8bit、4bit、2bit、1bit和上向预测8bit)(更新过)
  2. idea 设置加载多个资源文件,显示本地图片
  3. autoLayout自动布局
  4. 关于深度学习,我们4年时间写了一本1400页的全栈手册
  5. 史无前例! 中国学者一天发6篇Nature,在多领域取得重大进展
  6. 复杂网络研究:让世界变得简单
  7. iOS开发用如何用类quot;SKStoreProductViewControllerquot;跳转AppStore点赞评分?
  8. (转)淘淘商城系列——使用maven tomcat插件启动web工程
  9. ffmpeg实战教程(七)Android CMake avi解码后SurfaceView显示
  10. c面向对象 java_JAVA基础--面向对象08
  11. 新特性AAtitti css3 新特性attilax总结titti css
  12. ajax上传文件formData
  13. 你以为服务器关了这事就结束了? - XcodeGhost截胡攻击和服务端的复现,以及UnityGhost预警...
  14. google服务框架
  15. 自定义关机计算机,win7自定义定时关机设置方法是什么
  16. 天狮集团云函数实践:自定义业务逻辑实现跨境电商全球直播
  17. 中国石油大学 现代远程教育入学指南
  18. PyCharm使用手册
  19. 【数据分析案例】英雄联盟美服10000条排位数据分析
  20. Facebook数据中心实践分析,OCP主要工作成果介绍

热门文章

  1. Servlet API 中文版
  2. 将PDF转为TXT文本格式提取中文
  3. 构建Flex应用的10大误区
  4. C语言 简单选择排序算法
  5. 黑马程序员 Python学习笔记之变量
  6. c++拼接字符串效率比较(+=、append、stringstream、sprintf)
  7. processing初识
  8. Kinect unity三维重建
  9. Atitit 常见软件设计图纸总结 目录 1.1. ui原型图与html 2 1.2. 业务逻辑 伪代码 各种uml图 2 1.3. 总体设计图纸 结构图 层次图 架构图 2 1.4. 业务逻辑
  10. atitit 组织机构性质与名称表.docx