Simple ocean water

Under construction

Water shader in [RTWR]:

The demo implementation that was developed during the work on this thesis was implemented using the implementation suggested in Section 3.1.3. It was implemented using DirectX 9 and the target platform is the ATi R300-class of GPUs (Radeon 9500 or higher).
It is available at the following URL:
http://graphics.cs.lth.se/theses/projects/projgrid
The animated Perlin noise is rendered on the CPU. The base resolution of each octave is 32×32 pixels and four octaves are stacked together in texture chunks with the resulting resolution of 256×256 pixels. Two of these chunks were used for a total of 8 octaves of noise.
The plane was forced to the xz-plane for simplicity’s sake.
The vertices were (at default) calculated at a grid with the resolution of 65×129 vertices, resulting in 64x128x2 triangles. The line-plane intersections were only done at the corner vertices, the rest were interpolated using homogenous coordinates. The height-data was read by linear interpolation (but without mip-mapping) on the CPU before the vertex buffer were uploaded to the GPU as world-space coordinates.
The two chunks of Perlin noise was uploaded to the GPU and the same height map was generated on the GPU but with the resolution of 512×1024 pixels. Aliasing was avoided by using mip-mapping. The normal map was generated from and at the same resolution as the height map.
A simple demo scene consisting of an island and a moving rubber duck was mirrored against Sbase and rendered to a texture for use as local reflections. The alpha channel was later used to determine which parts of the texture that was transparent.
A sky dome, the demo scene and the water surface was rendered to the back-buffer.
The water rendering was done by doing the following computations per-pixel:

  • Using the normal map and the view-direction (at every point, not just the direction of the camera) the reflection vector was calculated.
  • Using a cube map texture read the reflected part of the sky was sampled (RG).
  • The Fresnel reflectance (f) was obtained by doing a 1D texture read of the dot-product of the reflection vector and the normal.
  • The sunlight contribution (RS) was calculated with phong shading using the normal map and a lightdirection vector.
  • The local reflections (RL) was approximated (cannot be done properly without ray tracing) by offsetting the texture coordinate with the xz-part of the normal.
  • The refraction colour was set to a constant colour. (CW)
  • The output colour was: C = (1-f) ·CW + f· (RL.rgb·RL.a + (1- RL.a)·(RG+RS))

A simpler implementation for the Nebula2 engine was also created. It’s named nPGwater and should be available at the same location as the DX9-demo. It uses per-vertex normals and has no local reflections but is otherwise similar to the DirectX 9 demo implementation.

HLSL code for water surface rendering:

float4x4 mViewProj;
float4x4 mView;
float4 view_position;
float3 watercolour;
float LODbias;
float sun_alfa, sun_theta, sun_shininess, sun_strength;
float reflrefr_offset;
bool diffuseSkyRef;
texture EnvironmentMap, FresnelMap, Heightmap, Normalmap, Refractionmap, Reflectionmap;
struct VS_INPUT
{
float3 Pos : POSITION;
float3 Normal : NORMAL;
float2 tc : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 Pos : POSITION;
float2 tc : TEXCOORD0;
float3 normal : TEXCOORD1;
float3 viewvec : TEXCOORD2;
float3 screenPos : TEXCOORD3;
float3 sun : TEXCOORD5;
float3 worldPos : TEXCOORD6;
};
samplerCUBE sky = sampler_state
{
Texture = <EnvironmentMap>;
MipFilter = NONE; MinFilter = LINEAR; MagFilter = LINEAR;
AddressU = WRAP; AddressV = WRAP; AddressW = WRAP;
};
sampler fresnel = sampler_state
{
Texture = <FresnelMap>;
MipFilter = NONE; MinFilter = LINEAR; MagFilter = LINEAR;
AddressU = CLAMP; AddressV = CLAMP;
};
sampler hmap = sampler_state
{
Texture = <Heightmap>;
MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR;
AddressU = CLAMP; AddressV = CLAMP;
};
sampler nmap = sampler_state
{
Texture = <Normalmap>;
MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR;
AddressU = CLAMP; AddressV = CLAMP;
};
sampler refrmap = sampler_state
{
Texture = <Refractionmap>;
MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR;
AddressU = CLAMP; AddressV = CLAMP;
};
sampler reflmap = sampler_state
{
Texture = <Reflectionmap>;
MipFilter = LINEAR; MinFilter = LINEAR; MagFilter = LINEAR;
AddressU = CLAMP; AddressV = CLAMP;
};
/* DX9 class shaders */
VS_OUTPUT VShaderR300(VS_INPUT i)
{
VS_OUTPUT o;
o.worldPos = i.Pos.xyz/4;
o.Pos = mul(float4(i.Pos.xyz,1), mViewProj);
o.normal = normalize(i.Normal.xyz);
o.viewvec = normalize(i.Pos.xyz – view_position.xyz/view_position.w);
o.tc = i.tc;
// alt screenpos
// this is the screenposition of the undisplaced vertices (assuming the plane is y=0)
// it is used for the reflection/refraction lookup
float4 tpos = mul(float4(i.Pos.x,0,i.Pos.z,1), mViewProj);
o.screenPos = tpos.xyz/tpos.w;
o.screenPos.xy = 0.5 + 0.5*o.screenPos.xy*float2(1,-1);
o.screenPos.z = reflrefr_offset/o.screenPos.z; // reflrefr_offset controls
//the strength of the distortion
// what am i doing here? (this should really be done on the CPU as it isn’t a per-vertex operation)
o.sun.x = cos(sun_theta)*sin(sun_alfa);
o.sun.y = sin(sun_theta);
o.sun.z = cos(sun_theta)*cos(sun_alfa);
return o;
}
float4 PShaderR300(VS_OUTPUT i) : COLOR
{
float4 ut;
ut.a = 1;
float3 v = i.viewvec;
// depending on whether normals are provided per vertex or via a normal map, N is set differently
//float3 N = i.normal;
float3 N = 2*tex2D(nmap,i.tc)-1;
float3 R = normalize(reflect(v,N));
R.y = max(R.y,0);
float4 f = tex1D(fresnel,dot(R,N));
float3 sunlight = pow(sun_strength*pow(saturate(dot(R, i.sun)),sun_shininess)*float3(1.2, 0.4, 0.1), 1/2.2);
float4 refl = tex2D(reflmap,i.screenPos.xy-i.screenPos.z*N.xz);
float3 skyrefl;
skyrefl = texCUBE(sky,R);
float3 col = lerp(skyrefl+sunlight,refl.rgb,refl.a);
float3 refr = watercolour; // constant colour but
//float3 refr = tex2D(refrmap,i.screenPos.xy-i.screenPos.z*N.xz); // refraction could be used instead
ut.rgb = lerp(refr, col, f.r);
return ut;
}

HLSL code for heightmap generation:

float scale; // the xz-scale of the noise
struct VS_INPUT
{
float3 Pos : POSITION;
float2 tc : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 Pos : POSITION;
float2 tc0 : TEXCOORD0;
float2 tc1 : TEXCOORD1;
};
texture noise0;
texture noise1;
// samplers
sampler N0 = sampler_state
{
texture = <noise0>;
AddressU = WRAP;
AddressV = WRAP;
MIPFILTER = LINEAR;
MINFILTER = LINEAR;
MAGFILTER = LINEAR;
MipMapLodBias = -1;
};
sampler N1 = sampler_state
{
texture = <noise1>;
AddressU = WRAP;
AddressV = WRAP;
MIPFILTER = LINEAR;
MINFILTER = LINEAR;
MAGFILTER = LINEAR;
MipMapLodBias = -1;
};
VS_OUTPUT VShader(VS_INPUT i)
{
VS_OUTPUT o;
o.Pos = float4( i.tc.x*2-1,1-i.tc.y*2, 0, 1 );
o.tc0 = scale*i.Pos.xz*0.015625;
o.tc1 = scale*i.Pos.xz*0.25;
return o;
}
float4 PShader(VS_OUTPUT i) : COLOR
{
return tex2D(N0, i.tc0) + tex2D(N1, i.tc1) – 0.5;
}

float inv_mapsize_x,inv_mapsize_y;
float4 corner00, corner01, corner10, corner11;
float amplitude; // the amplitude of the noise.. this determine the strength of the normals
struct VS_INPUT
{
float3 Pos : POSITION;
float2 tc : TEXCOORD0;
};
struct VS_OUTPUT
{
float4 Pos : POSITION;
float2 tc : TEXCOORD0;
float3 tc_p_dx : TEXCOORD1;
float3 tc_p_dy : TEXCOORD2;
float3 tc_m_dx : TEXCOORD3;
float3 tc_m_dy : TEXCOORD4;
};
texture hmap;
sampler hsampler = sampler_state
{
texture = <hmap>;
AddressU = WRAP;
AddressV = WRAP;
MIPFILTER = NONE;
MINFILTER = LINEAR;
MAGFILTER = LINEAR;
};
VS_OUTPUT VShader(VS_INPUT i)
{
VS_OUTPUT o;
o.Pos = float4( i.tc.x*2-1,1-i.tc.y*2, 0, 1 );
float scale = 1;
float2 tc = i.tc + float2(-inv_mapsize_x*scale,0);
float4 meh = lerp(lerp(corner00,corner01,tc.x),lerp(corner10,corner11,tc.x),tc.y);
o.tc_m_dx = meh.xyz/meh.w;
tc = i.tc + float2(+inv_mapsize_x*scale,0);
meh = lerp(lerp(corner00,corner01,tc.x),lerp(corner10,corner11,tc.x),tc.y);
o.tc_p_dx = meh.xyz/meh.w;
tc = i.tc + float2(0,-inv_mapsize_y*scale);
meh = lerp(lerp(corner00,corner01,tc.x),lerp(corner10,corner11,tc.x),tc.y);
o.tc_m_dy = meh.xyz/meh.w;
tc = i.tc + float2(0,inv_mapsize_y*scale);
meh = lerp(lerp(corner00,corner01,tc.x),lerp(corner10,corner11,tc.x),tc.y);
o.tc_p_dy = meh.xyz/meh.w;
o.tc = i.tc;
return o;
}
float4 PShader(VS_OUTPUT i) : COLOR
{
float2 dx = {inv_mapsize_x,0},
dy = {0,inv_mapsize_y};
i.tc_p_dx.y = amplitude*tex2D(hsampler, i.tc+dx);
i.tc_m_dx.y = amplitude*tex2D(hsampler, i.tc-dx);
i.tc_p_dy.y = amplitude*tex2D(hsampler, i.tc+dy);
i.tc_m_dy.y = amplitude*tex2D(hsampler, i.tc-dy);
float3 normal = normalize(-cross(i.tc_p_dx-i.tc_m_dx, i.tc_p_dy-i.tc_m_dy));
return float4(0.5+0.5*normal,1);
}

References

[RTWR] – Real-time water rendering – introducing the projected grid concept

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Liked it here?
Why not try sites on the blogroll...

%d bloggers like this: