I base my water shader off of this work, its directX based but I'm sure with a bit of work you can convert it over but ill go over how I do it so you have an idea\recipe.
First up I make a copy of the current frames back buffer to use as the refraction buffer or you can draw everything again and use clip planes but I only do that for the reflection buffer to save a heap of draw calls at the cost of some artifacts in the refraction buffer.
I then compute a reflection camera and then draw everything that I want to reflect into the reflection buffer using the reflection camera as the view matrix.
Water Camera View:
// play with the (0,0,0) value here to adjust the water camera offset
Dim WaterPos As Vector3 = BasePos + CType(Vector3.Transform(New Vector3(0, 0, 0), Matrix.RotationX(MathHelper.ToRadians(0)) * Matrix.RotationY(MathHelper.ToRadians(_yaw))), Vector3)
WaterPos.Y = -WaterPos.Y + 175 * 2 // sets the camera Y cord(adjust 175 to water height +- 3-5)
//locks the pitch part of the rotation matrix
Dim watertemp As Matrix = Matrix.RotationX(MathHelper.ToRadians(15)) * Matrix.RotationY(MathHelper.ToRadians(_yaw)) * Matrix.Translation(WaterPos)
I have the reflection buffer resolution set to 1/4 full screen resolution make sure it has the same aspect as the back buffer it the projection will fail, the normal perturbation(waves+wind) hides the jaggyness.
When I come to render the water I just draw the water plain with an opaque blendstate and use this shader to blend it all togeather.
Water shader:
WaterVertexToPixel WaterVS(float4 inPos : POSITION, float2 inTex: TEXCOORD)
{
WaterVertexToPixel Output = (WaterVertexToPixel)0;
float4×4 preViewProjection = mul (xView, xProjection);
float4×4 preWorldViewProjection = mul (xWorld, preViewProjection);
float4×4 preReflectionViewProjection = mul (xReflectionView, xProjection);
float4×4 preWorldReflectionViewProjection = mul (xWorld, preReflectionViewProjection);
Output.Position = mul(inPos, preWorldViewProjection);
Output.ReflectionMapSamplingPos = mul(inPos, preWorldReflectionViewProjection);
Output.RefractionMapSamplingPos = mul(inPos, preWorldViewProjection);
return Output;
}
void WaterPS(WaterVertexToPixel PSIn)
{
float3 eyeVector = normalize(WorldCameraPos - WorldPos);
float3 NormalData1 = 0;
float3 NormalData2 = 0;
NormalData1 = BumpMap1.Sample(samANISOTROPIC,texturecoords).rgb;
NormalData2 = BumpMap2.Sample(samANISOTROPIC, texturecoords).rgb;
float3 normalT = normalize(lerp(NormalData1 - 0.5, NormalData2 - 0.5, flowLerp) *2);
float2 perturbation = WaveHeight * normalT.rg;
// Project Reflection Buffer To Water Surface
ProjectedTexCoords.x = PSIn.ReflectionMapSamplingPos.x/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f;
ProjectedTexCoords.y = PSIn.ReflectionMapSamplingPos.y/PSIn.ReflectionMapSamplingPos.w/2.0f + 0.5f;
float2 perturbatedTexCoords = ProjectedTexCoords + perturbation;
float4 reflectiveColor = tex2D(ReflectionSampler, perturbatedTexCoords);
// Project Refraction Buffer To Water Surface
ProjectedRefrTexCoords.x = PSIn.RefractionMapSamplingPos.x/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f;
ProjectedRefrTexCoords.y = -PSIn.RefractionMapSamplingPos.y/PSIn.RefractionMapSamplingPos.w/2.0f + 0.5f;
float2 perturbatedRefrTexCoords = ProjectedRefrTexCoords + perturbation;
float4 refractiveColor = tex2D(RefractionSampler, perturbatedRefrTexCoords);
float3 combinedColor;
//The Fresnel equations, deduced by Augustin-Jean Fresnel, describe the behaviour of light when moving between media of differing refractive indices.
float fresnelTerm;
fresnelTerm = dot(eyeVector, normalVector);
fresnelTerm = 1 - fresnelTerm*1.3f;
fresnelTerm = fresnelTerm * FresnelAdjust;
//just to be sure that the value is between 0 and 1;
fresnelTerm = fresnelTerm < 0 ? 0 : fresnelTerm;
fresnelTerm = fresnelTerm > 1 ? 1 : fresnelTerm;
// creating the combined color
combinedColor = lerp(WaterColor, ReflectionColor.rgb, fresnelTerm);
combinedColor = lerp(RefractionColor.rgb, combinedColor , alpha);
return combinedColor ;
}
I know you have a mine craft style world but I assume you would just apply the shader to each block or each chunk of blocks.
when you come to draw the reflection buffer you will need to clip everything bellow the water or it looks funky also have a play with the cull mode as you may need to flip it.
Clip plain:
dim reflectionPlane as plain = CreatePlane(120, New SharpDX.Vector3(0, -1, 0), True)
//Use this float4 on the GPU to clip my geometry when filling the reflection buffer, not sure how to do it in openGL
dim planeConvert as vector4 = New SharpDX.Vector4(reflectionPlane.Normal, reflectionPlane.D)
Public Function CreatePlane(Height As Single, planeNormalDirection As SharpDX.Vector3, clipSide As Boolean) As SharpDX.Plane
planeNormalDirection.Normalize()
Dim planeCoeffs As New SharpDX.Vector4(planeNormalDirection, Height)
If clipSide Then
planeCoeffs *= -1
End If
Dim tempCoeffs As Single() = New Single(3) {}
tempCoeffs(0) = planeCoeffs.X
tempCoeffs(1) = planeCoeffs.Y
tempCoeffs(2) = planeCoeffs.Z
tempCoeffs(3) = planeCoeffs.W
Dim finalPlane As New SharpDX.Plane(tempCoeffs)
Return finalPlane
End Function
I also have not tested that shader because I had to pull it out of my messy beast of a water shader but you should get the general idea.
Vertex shader takes World, View, Projection and WaterView as inputs(float4x4).
Pixel shader has two textures Relfection and Refraction, two normal maps for the details on the surface of the water.
Maybe use bigger chunks and some GPU occlusion queries to cull out even more chunks you cant see.