1. 程式人生 > >海面模擬以及渲染(計算著色器、FFT、Reflection Matrix)

海面模擬以及渲染(計算著色器、FFT、Reflection Matrix)

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計算著色器程式碼:(基本上就是根據理論公式寫程式碼)