一、海面网格的波形计算
大体思路:
如上图公式所示,该公式等号左边的向量X表示的是顶点位置,t表示时间,等式右边的向量k可以理解为表示的是单位根向量,即可以认为等式右边是等式左边的离散傅里叶变换(详见:
一小时学会快速傅里叶变换)后的形式,根据理论,我们可以很轻松的算出h(k,t)的数值,所以我们需要使用傅里叶逆变换来得到h(x,t)的数值,该数值即为每个顶点的高度值。得到高度值后,我们接下来要计算每个顶点的水平扰动值(用来模拟出海面波尖的效果,如果不计算的话则是模拟出比较平静的海面状况)以及法线向量。
为了提高性能,可以将波形计算交给GPU来做,使用计算着色器是一个不错的选择。
关于波形计算的代码:
GLfloat RandomUniform(const GLfloat start, const GLfloat end)
{
return ((GLfloat)rand() / (GLfloat)RAND_MAX) * (end - start) + start;
}
GLfloat RandomNormal(const GLfloat mean, const GLfloat standardDeviation)
{
GLfloat x1, x2;
x1 = RandomUniform(GLUS_UNIFORM_RANDOM_BIAS, 1.0f - GLUS_UNIFORM_RANDOM_BIAS);
x2 = RandomUniform(0.0f, 1.0f);
return mean + standardDeviation * (sqrtf(-2.0f * logf(x1)) * cosf(2.0f * GL_PI * x2));
}
GLfloat OceanMeshForGPU::PhillipsSpectrum(GLfloat A, GLfloat L, glm::vec2 waveDirection, glm::vec2 windDirection)
{
GLfloat k = glm::length(waveDirection);
GLfloat waveDotWind = glm::dot(waveDirection, windDirection);
if (L == 0.0f || k == 0.0f)
{
return 0.0f;
}
return A * expf(-1.0f / (k * L * k * L)) / (k * k * k * k) * waveDotWind * waveDotWind;
}
GLboolean OceanMeshForGPU::Init()
{
glm::vec3 lightDirection = { 0.5f, 1.0f, 1.0f };
glm::vec4 color = { 0.0f, 0.8f, 0.8f, 1.0f };
GLUSshape gridPlane;
GLint i, k;
glm::mat4 matrix = glm::mat4();
GLfloat* h0Data;
GLint* butterflyIndices;
GLfloat* butterflyIndicesAsFloat;
glm::vec2 waveDirection;
glm::vec2 windDirection = WIND_DIRECTION;
GLfloat phillipsSpectrumValue;
//通过项数计算出计算 FFT 的时候需要的迭代总次数
GLint steps = 0;
GLint temp = N;
while (!(temp & 0x1))
{
temp = temp >> 1;
steps++;
}
//声明更新海面网格的计算着色器
g_computeUpdateHtProgram = new Shader("OceanUpdate.comp");
//声明进行FFT的计算着色器
g_computeFftProgram = new Shader("OceanFFT.comp");
//声明更新法线向量的计算着色器
g_computeUpdateNormalProgram = new Shader("OceanUpdateNormal.comp");
//声明进行渲染的顶点和像素着色器
g_program = new Shader("OceanGPU.vs", "OceanGPU.frag");
g_totalTimeUpdateHtLocation = glGetUniformLocation(g_computeUpdateHtProgram->Program , "u_totalTime");
g_processColumnFftLocation = glGetUniformLocation(g_computeFftProgram->Program, "u_processColumn");
g_stepsFftLocation = glGetUniformLocation(g_computeFftProgram->Program, "u_steps");
g_ModelLoc = glGetUniformLocation(g_program->Program, "u_Model");
g_ViewLoc = glGetUniformLocation(g_program->Program, "u_View");
g_ProjectionLoc = glGetUniformLocation(g_program->Program, "u_Projection");
g_normalMatrixLocation = glGetUniformLocation(g_program->Program, "u_normalMatrix");
g_lightDirectionLocation = glGetUniformLocation(g_program->Program, "u_lightDirection");
g_colorLocation = glGetUniformLocation(g_program->Program, "u_color");
g_vertexLocation = glGetAttribLocation(g_program->Program, "a_vertex");
g_texCoordLocation = glGetAttribLocation(g_program->Program, "a_texCoord");
// 创建一个方形的网格平面
CreateRectangularGridPlane(&gridPlane, LENGTH, LENGTH, N - 1, N - 1, GL_FALSE);
//获取该网格的索引总数
g_numberIndices = gridPlane.numberIndices;
// 将当前网格以X轴为基础旋转90度使其处于X-Z平面
matrix = glm::rotate(matrix, glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f));
for (i = 0; i < gridPlane.numberVertices; i++)
{
gridPlane.vertices[i] = matrix * gridPlane.vertices[i];
}
//存储顶点坐标的VBO
glGenBuffers(1, &g_verticesVBO);
glBindBuffer(GL_ARRAY_BUFFER, g_verticesVBO);
glBufferData(GL_ARRAY_BUFFER, gridPlane.numberVertices * 4 * sizeof(GLfloat), (GLfloat*)gridPlane.vertices, GL_STATIC_DRAW);
//存储顶点纹理坐标的VBO
glGenBuffers(1, &g_texCoordsVBO);
glBindBuffer(GL_ARRAY_BUFFER, g_texCoordsVBO);
glBufferData(GL_ARRAY_BUFFER, gridPlane.numberVertices * 2 * sizeof(GLfloat), (GLfloat*)gridPlane.texCoords, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
//存储索引的VBO
glGenBuffers(1, &g_indicesVBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_indicesVBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, gridPlane.numberIndices * sizeof(GLuint), (GLuint*)gridPlane.indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
ShapeDestroy(&gridPlane);
//h0Data存储波浪模型的初始值
h0Data = (GLfloat*)malloc(N * N * 2 * sizeof(GLfloat));
if (!h0Data)
{
return GL_FALSE;
}
//Phillips频谱计算出初始波形
windDirection = glm::normalize(windDirection);
for (i = 0; i < N; i++)
{
waveDirection[1] = ((GLfloat)i - (GLfloat)N / 2.0f) * (2.0f * GL_PI / LENGTH);
for (k = 0; k < N; k++)
{
waveDirection[0] = ((GLfloat)k - (GLfloat)N / 2.0f) * (2.0f * GL_PI / LENGTH);
phillipsSpectrumValue = PhillipsSpectrum(AMPLITUDE, LPWA, waveDirection, windDirection);
h0Data[i * 2 * N + k * 2 + 0] = 1.0f / sqrtf(2.0f) * RandomNormal(0.0f, 1.0f) * phillipsSpectrumValue;
h0Data[i * 2 * N + k * 2 + 1] = 1.0f / sqrtf(2.0f) * RandomNormal(0.0f, 1.0f) * phillipsSpectrumValue;
}
}
//将数据存储到一张贴图当中,g_textureH0贴图的R、G两个通道分别存储h0Data(相当于复数)的实部和虚部
glGenTextures(1, &g_textureH0);
glBindTexture(GL_TEXTURE_2D, g_textureH0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, N, N, 0, GL_RG, GL_FLOAT, h0Data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
free(h0Data);
//用于存储波浪函数计算出来的复数值的贴图
glGenTextures(1, &g_textureHt);
glBindTexture(GL_TEXTURE_2D, g_textureHt);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, N, N, 0, GL_RG, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//用于存储顶点偏移值的贴图(按行计算得到的值)
glGenTextures(1, &g_textureDisplacement[0]);
glBindTexture(GL_TEXTURE_2D, g_textureDisplacement[0]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, N, N, 0, GL_RG, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//用于存储顶点偏移值的贴图(按列计算得到的值)
glGenTextures(1, &g_textureDisplacement[1]);
glBindTexture(GL_TEXTURE_2D, g_textureDisplacement[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32F, N, N, 0, GL_RG, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//存储法线向量值
glGenTextures(1, &g_textureNormal);
glBindTexture(GL_TEXTURE_2D, g_textureNormal);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, N, N, 0, GL_RGBA, GL_FLOAT, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_2D, 0);
//生成逆傅里叶变换所需要的蝶形算法使用的索引值
butterflyIndices = (GLint*)malloc(N * sizeof(GLint));
if (!butterflyIndices)
{
return GL_FALSE;
}
//使用GPU计算FFT需要先把第一次迭代的索引计算好例:N=8时 索引为(0,4) (1,5) (2,6) (3,7)
butterflyIndicesAsFloat = (GLfloat*)malloc(N * sizeof(GLfloat));
if (!butterflyIndicesAsFloat)
{
free(butterflyIndices);
return GL_FALSE;
}
for (i = 0; i < N; i++)
{
butterflyIndices[i] = i;
}
FourierButterflyShuffleFFTi(butterflyIndices, butterflyIndices, N);
for (i = 0; i < N; i++)
{
butterflyIndicesAsFloat[i] = (GLfloat)butterflyIndices[i];
}
free(butterflyIndices);
//将计算好的索引值存储到贴图当中
glGenTextures(1, &g_textureIndices);
glBindTexture(GL_TEXTURE_1D, g_textureIndices);
glTexImage1D(GL_TEXTURE_1D, 0, GL_R32F, N, 0, GL_RED, GL_FLOAT, butterflyIndicesAsFloat);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glBindTexture(GL_TEXTURE_1D, 0);
free(butterflyIndicesAsFloat);
//将所需要的迭代次数传递到FFT的计算着色器中
glUseProgram(g_computeFftProgram->Program);
glUniform1i(g_stepsFftLocation, steps);
//设置渲染使用的VAO
glUseProgram(g_program->Program);
glGenVertexArrays(1, &g_vao);
glBindVertexArray(g_vao);
glBindBuffer(GL_ARRAY_BUFFER, g_verticesVBO);
glVertexAttribPointer(g_vertexLocation, 4, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(g_vertexLocation);
glBindBuffer(GL_ARRAY_BUFFER, g_texCoordsVBO);
glVertexAttribPointer(g_texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(g_texCoordLocation);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_indicesVBO);
glBindVertexArray(0);
lightDirection = glm::normalize(lightDirection);
GLfloat HelpLight[3];
HelpLight[0] = lightDirection.x;
HelpLight[1] = lightDirection.y;
HelpLight[2] = lightDirection.z;
glUniform3fv(g_lightDirectionLocation, 1, HelpLight);
GLfloat HelpColor[4];
HelpColor[0] = color.x;
HelpColor[1] = color.y;
HelpColor[2] = color.z;
HelpColor[3] = color.w;
glUniform4fv(g_colorLocation, 1, HelpColor);
glUseProgram(0);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
return GL_TRUE;
}
GLboolean OceanMeshForGPU::update(GLfloat time, glm::mat4 Model, glm::mat4 View, glm::mat4 Perspective, glm::vec3 CamPos, GLuint ReflectText)
{
static GLfloat totalTime = 0.0f;
//将所需要的三大变换矩阵传入到绘制用的着色器当中
glUseProgram(g_program->Program);
glm::mat3 normalMatrix = glm::mat3(Model);
glUniformMatrix4fv(g_ModelLoc, 1, GL_FALSE, glm::value_ptr(Model));
glUniformMatrix4fv(g_ViewLoc, 1, GL_FALSE, glm::value_ptr(View));
glUniformMatrix4fv(g_ProjectionLoc, 1, GL_FALSE, glm::value_ptr(Perspective));
glUniformMatrix3fv(g_normalMatrixLocation, 1, GL_FALSE, glm::value_ptr(normalMatrix));
glUniform3fv(glGetUniformLocation(g_program->Program, "camPos"), 1, &CamPos[0]);
glUseProgram(0);
//使用Update计算着色器并绑定初始值贴图和即时值贴图
glUseProgram(g_computeUpdateHtProgram->Program);
glBindImageTexture(0, g_textureH0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F);
glBindImageTexture(1, g_textureHt, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RG32F);
//传递运行时间
glUniform1f(g_totalTimeUpdateHtLocation, totalTime);
//创建一个N*N大小的工作组,即同时计算所有的顶点高度值
glDispatchCompute(N, N, 1);
//确保所有的数据都写入到贴图里了
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
//使用FFT计算着色器对结果进行逆变换
glUseProgram(g_computeFftProgram->Program);
//绑定索引贴图、上个计算着色器所得结果的波浪函数贴图、将要存储偏移值的贴图
glBindImageTexture(0, g_textureHt, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F);
glBindImageTexture(1, g_textureDisplacement[0], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RG32F);
glBindImageTexture(2, g_textureIndices, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32F);
glUniform1i(g_processColumnFftLocation, 0);
// 先对每一行进行逆变换
glDispatchCompute(1, N, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glBindImageTexture(0, g_textureDisplacement[0], 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F);
glBindImageTexture(1, g_textureDisplacement[1], 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RG32F);
glUniform1i(g_processColumnFftLocation, 1);
// 再对每一列进行逆变换
glDispatchCompute(1, N, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
//更新法线向量
glUseProgram(g_computeUpdateNormalProgram->Program);
glBindImageTexture(0, g_textureDisplacement[1], 0, GL_FALSE, 0, GL_READ_ONLY, GL_RG32F);
glBindImageTexture(1, g_textureNormal, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F);
glDispatchCompute(N, N, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glUseProgram(g_program->Program);
glBindVertexArray(g_vao);
//将波浪高度贴图绑定在GL_TEXTURE0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, g_textureDisplacement[1]);
//将法线贴图绑定在GL_TEXTURE1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, g_textureNormal);
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, ReflectText);
//根据索引开始绘制
glDrawElements(GL_TRIANGLES, g_numberIndices, GL_UNSIGNED_INT, 0);
//重置GL_TEXTURE0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
//重置GL_TEXTURE1
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, 0);
glBindVertexArray(0);
totalTime += time;
return GL_TRUE;
}
Update计算着色器代码:(基本上就是根据理论公式写代码)
#version 430 core
#define N 512
#define LENGTH 250.0
#define GRAVITY 9.81
#define GL_PI 3.1415926535897932384626433832795
layout (binding = 0, rg32f) uniform image2D u_imageIn;
layout (binding = 1, rg32f) uniform image2D u_imageOut;
uniform float u_totalTime;
//使用布局限定符声明本地工作组大小为1*1*1
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
void main(void)
{
//通过gl_GlobalInvocationID来得知当前执行单元在全局工作组中的位置
ivec2 storePos = ivec2(int(gl_GlobalInvocationID.x), int(gl_GlobalInvocationID.y));
ivec2 storePos_negative = ivec2(N - 1 - storePos.x, N - 1 - storePos.y);
//根据位置storePos在贴图中采样得到数据
vec2 h0 = imageLoad(u_imageIn, storePos).xy;
vec2 h0_negative = imageLoad(u_imageIn, storePos_negative).xy;
vec2 waveDirection;
waveDirection.x = (float(-N) / 2.0 + gl_GlobalInvocationID.x) * (2.0 * GL_PI / LENGTH);
waveDirection.y = (float(N) / 2.0 - gl_GlobalInvocationID.y) * (2.0 * GL_PI / LENGTH);
float w2k = GRAVITY * length(waveDirection);
float wktime = sqrt(w2k) * u_totalTime;
float cos_wktime = cos(wktime);
float sin_wktime = sin(wktime);
vec2 ht;
ht.x = (h0.x * cos_wktime - h0.y * sin_wktime) + (h0_negative.x * cos_wktime - h0_negative.y * sin_wktime);
ht.y = (h0.x * sin_wktime + h0.y * cos_wktime) + (h0_negative.x * sin_wktime + h0_negative.y * cos_wktime);
//将算出来的高度值存储到贴图当中
imageStore(u_imageOut, storePos, vec4(ht, 0.0, 0.0));
}
FFT计算着色器代码:
#version 430 core
#define N 512
#define GL_PI 3.1415926535897932384626433832795
uniform int u_processColumn;
uniform int u_steps;
layout (binding = 0, rg32f) uniform image2D u_imageIn;
layout (binding = 1, rg32f) uniform image2D u_imageOut;
layout (binding = 2, r32f) uniform image1D u_imageIndices;
// 如果一个变量被声明为shared,那么它将被保存到特定的位置,从而对同一个本地工作组内的所有计算着色器请求可见,通常访问共享shared变量的性能会远远好于访问图像或者着色器存储缓存(例如主内存)的性能
shared vec2 sharedStore[N];
// as N = 512, so local size is 512/2 = 256. Processing two fields per invocation.
layout (local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
//复数乘法
vec2 mulc(vec2 a, vec2 b)
{
vec2 result;
result.x = a.x * b.x - a.y * b.y;
result.y = a.x * b.y + b.x * a.y;
return result;
}
//转换成单位根向量
vec2 rootOfUnityc(int n, int k)
{
vec2 result;
result.x = cos(2.0 * GL_PI * float(k) / float(n));
result.y = sin(2.0 * GL_PI * float(k) / float(n));
return result;
}
void main(void)
{
ivec2 leftStorePos;
ivec2 rightStorePos;
ivec2 leftLoadPos;
ivec2 rightLoadPos;
int xIndex = int(gl_GlobalInvocationID.x);
int yIndex = int(gl_GlobalInvocationID.y);
int leftStoreIndex = 2 * xIndex;
int rightStoreIndex = 2 * xIndex + 1;
//读取索引(每一组有两个索引例如(0,4))
int leftLoadIndex = int(imageLoad(u_imageIndices, leftStoreIndex).r);
int rightLoadIndex = int(imageLoad(u_imageIndices, rightStoreIndex).r);
// 加载和存储位置取决于行或列。
if (u_processColumn == 0)
{
leftLoadPos = ivec2(leftLoadIndex, yIndex);
rightLoadPos = ivec2(rightLoadIndex, yIndex);
leftStorePos = ivec2(leftStoreIndex, yIndex);
rightStorePos = ivec2(rightStoreIndex, yIndex);
}
else
{
leftLoadPos = ivec2(yIndex, leftLoadIndex);
rightLoadPos = ivec2(yIndex, rightLoadIndex);
leftStorePos = ivec2(yIndex, leftStoreIndex);
rightStorePos = ivec2(yIndex, rightStoreIndex);
}
// 从贴图中读取数据
vec2 leftValue = imageLoad(u_imageIn, leftLoadPos).xy;
vec2 rightValue = imageLoad(u_imageIn, rightLoadPos).xy;
//放入到共享缓存中
sharedStore[leftStoreIndex] = leftValue;
sharedStore[rightStoreIndex] = rightValue;
//确保所有数据都存储完毕(否则后续逻辑将无法读到所需的数据,即要保证时序)
memoryBarrierShared();
barrier();
int numberSections = N / 2;
int numberButterfliesInSection = 1;
int currentSection = xIndex;
int currentButterfly = 0;
// 计算FFT
for (int currentStep = 0; currentStep < u_steps; currentStep++)
{
//根据位置来获取该组所需的两个索引
int leftIndex = currentButterfly + currentSection * numberButterfliesInSection * 2;
int rightIndex = currentButterfly + numberButterfliesInSection + currentSection * numberButterfliesInSection * 2;
//从共享缓存中获得数据
leftValue = sharedStore[leftIndex];
rightValue = sharedStore[rightIndex];
vec2 currentW = rootOfUnityc(numberButterfliesInSection * 2, currentButterfly);
vec2 multiply;
vec2 addition;
vec2 subtraction;
multiply = mulc(currentW, rightValue);
addition = leftValue + multiply;
subtraction = leftValue - multiply;
sharedStore[leftIndex] = addition;
sharedStore[rightIndex] = subtraction;
// 确保所有数据计算并存储完毕
memoryBarrierShared();
// 根据蝴蝶算法来改变参数
numberButterfliesInSection *= 2;
numberSections /= 2;
currentSection /= 2;
currentButterfly = xIndex % numberButterfliesInSection;
// 确保所有的计算着色器都计算完毕
barrier();
}
if (u_processColumn == 1)
{
if ((leftStorePos.x + leftStorePos.y) % 2 == 0)
{
sharedStore[leftStoreIndex] *= -1.0;
}
if ((rightStorePos.x + rightStorePos.y) % 2 == 0)
{
sharedStore[rightStoreIndex] *= -1.0;
}
memoryBarrierShared();
}
imageStore(u_imageOut, leftStorePos, vec4(sharedStore[leftStoreIndex], 0.0, 0.0));
imageStore(u_imageOut, rightStorePos, vec4(sharedStore[rightStoreIndex], 0.0, 0.0));
}
法线计算着色器:(水平、竖直、对角线分别采样并计算切线空间的B、T向量来计算法线)
#version 430 core
#define N 512
#define LENGTH 250.0
#define VERTEX_STEP (LENGTH / float(N - 1))
#define DIAGONAL_VERTEX_STEP sqrt(VERTEX_STEP * VERTEX_STEP * 2.0)
layout (binding = 0, rg32f) uniform image2D u_imageIn;
layout (binding = 1, rgba32f) uniform image2D u_imageOut;
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
float sampleImage(ivec2 pos)
{
ivec2 cpos = clamp(pos, 0, N - 1);
return imageLoad(u_imageIn, cpos).r;
}
vec3 calculateNormal(ivec2 texCoord)
{
vec3 normal = vec3(0.0f, 0.0f, 0.0f);
ivec2 right = ivec2(texCoord.s + 1, texCoord.t);
ivec2 top = ivec2(texCoord.s, texCoord.t + 1);
ivec2 left = ivec2(texCoord.s - 1, texCoord.t);
ivec2 bottom = ivec2(texCoord.s, texCoord.t - 1);
float slopeHorizontal = sampleImage(right) - sampleImage(left);
float slopeVertical = sampleImage(top) - sampleImage(bottom);
vec3 tangent = normalize(vec3(2.0 * VERTEX_STEP, slopeHorizontal, 0.0));
vec3 bitangent = normalize(vec3(0.0, slopeVertical, -2.0 * VERTEX_STEP));
normal += normalize(cross(tangent, bitangent));
ivec2 rightTop = ivec2(texCoord.s + 1, texCoord.t + 1);
ivec2 leftTop = ivec2(texCoord.s - 1, texCoord.t + 1);
ivec2 leftBottom = ivec2(texCoord.s - 1, texCoord.t - 1);
ivec2 rightBottom = ivec2(texCoord.s + 1, texCoord.t - 1);
float slopeDown = sampleImage(rightBottom) - sampleImage(leftTop);
float slopeUp = sampleImage(rightTop) - sampleImage(leftBottom);
tangent = normalize(vec3(2.0 * DIAGONAL_VERTEX_STEP, slopeDown, 2.0 * DIAGONAL_VERTEX_STEP));
bitangent = normalize(vec3(2.0 * DIAGONAL_VERTEX_STEP, slopeUp, -2.0 * DIAGONAL_VERTEX_STEP));
normal += normalize(cross(tangent, bitangent));
return normalize(normal);
}
void main(void)
{
ivec2 storePos = ivec2(int(gl_GlobalInvocationID.x), int(gl_GlobalInvocationID.y));
vec3 normal = calculateNormal(storePos);
imageStore(u_imageOut, storePos, vec4(normal, 0.0));
}
竟会的到下面的结果(注:我没有对顶点进行水平扰动)
二、水面渲染
水面的渲染最主要在于反射效果和折射效果,由于我并没有实现折射效果,所以只会在这里简单说一下反射效果,详见:
实时渲染的水特效
其主要思路是将场景内所有要渲染的物体乘上一个reflection matrix使其根据反射面倒置,然后将这些渲染到一张贴图当中(要使用帧缓冲),然后在将这张帖图绑在水平面模型上即可。
大体代码:
glBindFramebuffer(GL_FRAMEBUFFER, captureFBO);
glBindRenderbuffer(GL_RENDERBUFFER, captureRBO);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, 512, 512);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, Texture, 0);
glViewport(0, 0, 512, 512);
ThisSky.Render(ThisView, ThisProjection, GPUOcean.GetReflectMatrix(), 1);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, screenWidth, screenHeight);
GPUOcean.update(deltaTime, ThisModel, ThisView, ThisProjection, camera.Position, Texture);
ThisSky.Render(ThisView, ThisProjection, GPUOcean.GetReflectMatrix(), 0);
vertexshader:
#version 430 core
layout(binding = 0) uniform sampler2D u_displacementMap;
uniform mat4 u_Model;
uniform mat4 u_View;
uniform mat4 u_Projection;
in vec4 a_vertex;
in vec2 a_texCoord;
out vec2 v_texCoord;
out vec3 WorldPos;
out vec4 vReflectCoordinates;
void main(void)
{
v_texCoord = a_texCoord;
vec4 displacement = vec4(0.0, texture(u_displacementMap, a_texCoord).r, 0.0, 0.0);
WorldPos = vec3(u_Model * (a_vertex + displacement));
gl_Position = u_Projection * u_View * vec4(WorldPos, 1.0f);
vReflectCoordinates = gl_Position;
//vReflectCoordinates = u_Projection * u_View * u_Model * u_ReflectMatrix * (a_vertex + displacement);
}
fragmentshader:
#version 430 core
layout(binding = 1) uniform sampler2D u_normalMap;
layout(binding = 2) uniform sampler2D ReflectionText;
uniform mat3 u_normalMatrix;
uniform vec3 u_lightDirection;
uniform vec4 u_color;
uniform vec3 camPos;
in vec2 v_texCoord;
in vec3 WorldPos;
in vec4 vReflectCoordinates;
out vec4 fragColor;
float u_exposure = 0.5;
vec3 hdr (vec3 color, float exposure)
{
return 1.0 - exp(-color * exposure);
}
void main (void)
{
vec3 normal = texture2D( u_normalMap, v_texCoord ).rgb;
vec3 view = normalize( camPos - WorldPos );
vec3 R = normalize( reflect( -view, normalize(vec3(0.0f, 1.0f, 0.0f)) ) );
vec3 reflection = normalize( reflect( -u_lightDirection, normal ) );
float specularFactor = pow( max( 0.0, dot( view, reflection ) ), 500.0 ) * 200.0;
vec3 distortion = normal * vec3( 0.1, 0.0, 0.1 ) ;
//vec3 reflectionColor = texture2DProj( ReflectionText, vReflectCoordinates.xyz + distortion ).xyz;
//vec3 reflectionColor = vec3( 0.1f, 0.1f, 0.1f );
//vec3 reflectionColor = vec3( texture(skybox, v_texCoord + distortion.xz) ) * 0.4f;
vec2 texf = vec2( vReflectCoordinates.x, vReflectCoordinates.y ) / vReflectCoordinates.w * 0.5 + 0.5 + distortion.xz;
vec3 reflectionColor = vec3(texture( ReflectionText, texf )) * 0.3f;
float distanceRatio = min( 1.0, log( 1.0 / length( camPos - WorldPos ) * 3000.0 + 1.0 ) );
distanceRatio *= distanceRatio;
distanceRatio = distanceRatio * 0.7 + 0.3;
normal = ( distanceRatio * normal + vec3( 0.0, 1.0 - distanceRatio, 0.0 ) ) * 0.5;
normal /= length( normal );
float fresnel = pow( 1.0 - dot( normal, view ), 2.0 );
float skyFactor = ( fresnel + 0.2 ) * 10.0;
vec3 waterColor = ( 1.0 - fresnel ) * vec3( 0.004, 0.016, 0.047 );
vec3 color = ( skyFactor + specularFactor + waterColor ) * reflectionColor + waterColor * 0.5 ;
color = hdr( color, u_exposure );
fragColor = vec4( color, 1.0 );
}