// This code was written for the OpenTK library and has been released // to the Public Domain. // It is provided "as is" without express or implied warranty of any kind. using System; using System.Diagnostics; using System.Drawing; using System.IO; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; using Examples.TextureLoaders; namespace Examples.Tutorial { /// Demonstrates Swizzled DXT5 Parallax Mapping /// The idea is described in more detail right here: http://www.opentk.com/node/394 /// [Example("Swizzled Parallax Mapping", ExampleCategory.OpenGL, "2.x", Documentation = "SwizzledParallax")] public class T12_GLSL_Parallax: GameWindow { public T12_GLSL_Parallax( ) : base( 800, 600 ) { } #region internal Fields // Shader int VertexShaderObject, FragmentShaderObject, ProgramObject; const string VertexShaderFilename = "Data/Shaders/Parallax_VS.glsl"; const string FragmentShaderFilename = "Data/Shaders/Parallax_FS.glsl"; const int AttribTangent = 5; // slot where to pass tangents to VS, not sure which are reserved besides 0 Vector3 Tangent = new Vector3( 1f, 0f, 0f ); Vector3 Normal = new Vector3( 0f, 0f, 1f ); // Material parameter //Vector3 MaterialScaleAndBiasAndShininess = new Vector3( 0.07f, 0.0f, 38.0f ); // for Metal tex Vector3 MaterialScaleAndBiasAndShininess = new Vector3( 0.04f, 0.0f, 92.0f ); // for Rock tex // Textures const TextureUnit TMU0_Unit = TextureUnit.Texture0; const int TMU0_UnitInteger = 0; const string TMU0_Filename = "Data/Textures/swizzled-rock-diffuse-height.dds"; uint TMU0_Handle; TextureTarget TMU0_Target; const TextureUnit TMU1_Unit = TextureUnit.Texture1; const int TMU1_UnitInteger = 1; const string TMU1_Filename = "Data/Textures/swizzled-rock-normal-gloss.dds"; uint TMU1_Handle; TextureTarget TMU1_Target; // Camera Vector3 EyePos = new Vector3( 0.0f, 0.0f, 3.0f ); // Light Vector3 LightPosition = new Vector3( 0.0f, 1.0f, 1.0f ); Vector3 LightDiffuse = new Vector3( 0.5f, 0.5f, 0.5f ); Vector3 LightSpecular = new Vector3( 1f, 1f, 1f ); #endregion internal Fields /// Setup OpenGL and load resources here. /// Not used. protected override void OnLoad(EventArgs e) { this.VSync = VSyncMode.Off; // Check for necessary capabilities: string extensions = GL.GetString(StringName.Extensions); if ( !GL.GetString(StringName.Extensions).Contains("GL_ARB_shading_language")) { throw new NotSupportedException(String.Format("This example requires OpenGL 2.0. Found {0}. Aborting.", GL.GetString(StringName.Version).Substring(0, 3))); } if (!extensions.Contains("GL_ARB_texture_compression") || !extensions.Contains("GL_EXT_texture_compression_s3tc")) { throw new NotSupportedException("This example requires support for texture compression. Aborting."); } int[] temp = new int[1]; GL.GetInteger( GetPName.MaxTextureImageUnits, out temp[0] ); Trace.WriteLine( temp[0] + " TMU's for Fragment Shaders found. (2 required)" ); GL.GetInteger( GetPName.MaxVaryingFloats, out temp[0] ); Trace.WriteLine( temp[0] + " varying floats between VS and FS allowed. (6 required)" ); GL.GetInteger( GetPName.MaxVertexUniformComponents, out temp[0] ); Trace.WriteLine( temp[0] + " uniform components allowed in Vertex Shader. (6 required)" ); GL.GetInteger( GetPName.MaxFragmentUniformComponents, out temp[0] ); Trace.WriteLine( temp[0] + " uniform components allowed in Fragment Shader. (11 required)" ); Trace.WriteLine("" ); #region GL State GL.ClearColor( 0.2f, 0f, 0.4f, 0f ); GL.PointSize( 10f ); GL.Disable( EnableCap.Dither ); GL.FrontFace( FrontFaceDirection.Ccw ); GL.PolygonMode( MaterialFace.Front, PolygonMode.Fill ); GL.PolygonMode( MaterialFace.Back, PolygonMode.Line ); #endregion GL State #region Shaders string LogInfo; // Load&Compile Vertex Shader using ( StreamReader sr = new StreamReader( VertexShaderFilename ) ) { VertexShaderObject = GL.CreateShader( ShaderType.VertexShader ); GL.ShaderSource( VertexShaderObject, sr.ReadToEnd( ) ); GL.CompileShader( VertexShaderObject ); } GL.GetShaderInfoLog( VertexShaderObject, out LogInfo ); if ( LogInfo.Length > 0 && !LogInfo.Contains( "hardware" ) ) Trace.WriteLine( "Vertex Shader failed!\nLog:\n" + LogInfo ); else Trace.WriteLine( "Vertex Shader compiled without complaint." ); // Load&Compile Fragment Shader using ( StreamReader sr = new StreamReader( FragmentShaderFilename ) ) { FragmentShaderObject = GL.CreateShader( ShaderType.FragmentShader ); GL.ShaderSource( FragmentShaderObject, sr.ReadToEnd( ) ); GL.CompileShader( FragmentShaderObject ); } GL.GetShaderInfoLog( FragmentShaderObject, out LogInfo ); if ( LogInfo.Length > 0 && !LogInfo.Contains( "hardware" ) ) Trace.WriteLine( "Fragment Shader failed!\nLog:\n" + LogInfo ); else Trace.WriteLine( "Fragment Shader compiled without complaint." ); // Link the Shaders to a usable Program ProgramObject = GL.CreateProgram( ); GL.AttachShader( ProgramObject, VertexShaderObject ); GL.AttachShader( ProgramObject, FragmentShaderObject ); // must bind the attribute before linking GL.BindAttribLocation( ProgramObject, AttribTangent, "AttributeTangent" ); // link it all together GL.LinkProgram( ProgramObject ); // flag ShaderObjects for delete when not used anymore GL.DeleteShader( VertexShaderObject ); GL.DeleteShader( FragmentShaderObject ); GL.GetProgram( ProgramObject, ProgramParameter.LinkStatus, out temp[0] ); Trace.WriteLine( "Linking Program (" + ProgramObject + ") " + ( ( temp[0] == 1 ) ? "succeeded." : "FAILED!" ) ); if ( temp[0] != 1 ) { GL.GetProgramInfoLog( ProgramObject, out LogInfo ); Trace.WriteLine( "Program Log:\n" + LogInfo ); } GL.GetProgram( ProgramObject, ProgramParameter.ActiveAttributes, out temp[0] ); Trace.WriteLine( "Program registered " + temp[0] + " Attributes. (Should be 4: Pos, UV, Normal, Tangent)" ); Trace.WriteLine( "Tangent attribute bind location: " + GL.GetAttribLocation( ProgramObject, "AttributeTangent" ) ); Trace.WriteLine( "End of Shader build. GL Error: " + GL.GetError( ) ); #endregion Shaders #region Textures TextureLoaderParameters.MagnificationFilter = TextureMagFilter.Linear; TextureLoaderParameters.MinificationFilter = TextureMinFilter.LinearMipmapLinear; TextureLoaderParameters.WrapModeS = TextureWrapMode.ClampToBorder; TextureLoaderParameters.WrapModeT = TextureWrapMode.ClampToBorder; TextureLoaderParameters.EnvMode = TextureEnvMode.Modulate; ImageDDS.LoadFromDisk( TMU0_Filename, out TMU0_Handle, out TMU0_Target ); Trace.WriteLine( "Loaded " + TMU0_Filename + " with handle " + TMU0_Handle + " as " + TMU0_Target ); ImageDDS.LoadFromDisk( TMU1_Filename, out TMU1_Handle, out TMU1_Target ); Trace.WriteLine( "Loaded " + TMU1_Filename + " with handle " + TMU1_Handle + " as " + TMU1_Target ); #endregion Textures Trace.WriteLine( "End of Texture Loading. GL Error: " + GL.GetError( ) ); Trace.WriteLine( ""); } protected override void OnUnload(EventArgs e) { GL.DeleteProgram( ProgramObject ); GL.DeleteTextures( 1, ref TMU0_Handle ); GL.DeleteTextures( 1, ref TMU1_Handle ); base.OnUnload( e ); } /// Respond to resize events here. /// Contains information on the new GameWindow size. /// There is no need to call the base implementation. protected override void OnResize( EventArgs e ) { GL.Viewport( 0, 0, Width, Height ); GL.MatrixMode( MatrixMode.Projection ); Matrix4 p = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Width / (float)Height, 0.1f, 100.0f); GL.LoadMatrix(ref p); GL.MatrixMode( MatrixMode.Modelview ); GL.LoadIdentity( ); base.OnResize( e ); } /// Add your game logic here. /// Contains timing information. /// There is no need to call the base implementation. protected override void OnUpdateFrame( FrameEventArgs e ) { base.OnUpdateFrame( e ); if ( Keyboard[OpenTK.Input.Key.Escape] ) this.Exit( ); if ( Keyboard[OpenTK.Input.Key.Space] ) Trace.WriteLine( "GL: " + GL.GetError( ) ); if ( Keyboard[OpenTK.Input.Key.Q] ) { MaterialScaleAndBiasAndShininess.X += 0.01f; Trace.WriteLine( "Scale: " + MaterialScaleAndBiasAndShininess.X + " Bias: " + MaterialScaleAndBiasAndShininess.Y ); } if ( Keyboard[OpenTK.Input.Key.A] ) { MaterialScaleAndBiasAndShininess.X -= 0.01f; Trace.WriteLine( "Scale: " + MaterialScaleAndBiasAndShininess.X + " Bias: " + MaterialScaleAndBiasAndShininess.Y ); } if ( Keyboard[OpenTK.Input.Key.W] ) { MaterialScaleAndBiasAndShininess.Y += 0.01f; Trace.WriteLine( "Scale: " + MaterialScaleAndBiasAndShininess.X + " Bias: " + MaterialScaleAndBiasAndShininess.Y ); } if ( Keyboard[OpenTK.Input.Key.S] ) { MaterialScaleAndBiasAndShininess.Y -= 0.01f; Trace.WriteLine( "Scale: " + MaterialScaleAndBiasAndShininess.X + " Bias: " + MaterialScaleAndBiasAndShininess.Y ); } if ( Keyboard[OpenTK.Input.Key.E] ) { MaterialScaleAndBiasAndShininess.Z += 1f; Trace.WriteLine( "Shininess: " + MaterialScaleAndBiasAndShininess.Z ); } if ( Keyboard[OpenTK.Input.Key.D] ) { MaterialScaleAndBiasAndShininess.Z -= 1f; Trace.WriteLine( "Shininess: " + MaterialScaleAndBiasAndShininess.Z ); } LightPosition.X = ( -( this.Width / 2 ) + Mouse.X ) / 100f; LightPosition.Y = ( ( this.Height / 2 ) - Mouse.Y ) / 100f; EyePos.Y = Mouse.Wheel * 0.5f; } /// Add your game rendering code here. /// Contains timing information. /// There is no need to call the base implementation. protected override void OnRenderFrame( FrameEventArgs e ) { this.Title = "FPS: " + (1 / e.Time).ToString("0."); GL.Clear( ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit ); GL.UseProgram( ProgramObject ); #region Textures GL.ActiveTexture( TMU0_Unit ); GL.BindTexture( TMU0_Target, TMU0_Handle ); GL.ActiveTexture( TMU1_Unit ); GL.BindTexture( TMU1_Target, TMU1_Handle ); #endregion Textures #region Uniforms // first Material's uniforms GL.Uniform1( GL.GetUniformLocation( ProgramObject, "Material_DiffuseAndHeight" ), TMU0_UnitInteger ); GL.Uniform1( GL.GetUniformLocation( ProgramObject, "Material_NormalAndGloss" ), TMU1_UnitInteger ); GL.Uniform3( GL.GetUniformLocation( ProgramObject, "Material_ScaleBiasShininess" ), MaterialScaleAndBiasAndShininess.X, MaterialScaleAndBiasAndShininess.Y, MaterialScaleAndBiasAndShininess.Z ); // the rest are vectors GL.Uniform3( GL.GetUniformLocation( ProgramObject, "Camera_Position" ), EyePos.X, EyePos.Y, EyePos.Z ); GL.Uniform3( GL.GetUniformLocation( ProgramObject, "Light_Position" ), LightPosition.X, LightPosition.Y, LightPosition.Z ); GL.Uniform3( GL.GetUniformLocation( ProgramObject, "Light_DiffuseColor" ), LightDiffuse.X, LightDiffuse.Y, LightDiffuse.Z ); GL.Uniform3( GL.GetUniformLocation( ProgramObject, "Light_SpecularColor" ), LightSpecular.X, LightSpecular.Y, LightSpecular.Z ); #endregion Uniforms GL.PushMatrix( ); Matrix4 t = Matrix4.LookAt( EyePos, Vector3.Zero, Vector3.UnitY ); GL.MultMatrix(ref t); #region Draw Quad GL.Color3( 1f, 1f, 1f ); GL.Begin( BeginMode.Quads ); { GL.Normal3( Normal ); GL.VertexAttrib3( AttribTangent, ref Tangent ); GL.MultiTexCoord2( TextureUnit.Texture0, 0f, 1f ); GL.Vertex3( -1.0f, 1.0f, 0.0f ); GL.Normal3( Normal ); GL.VertexAttrib3( AttribTangent, ref Tangent ); GL.MultiTexCoord2( TextureUnit.Texture0, 0f, 0f ); GL.Vertex3( -1.0f, -1.0f, 0.0f ); GL.Normal3( Normal ); GL.VertexAttrib3( AttribTangent, ref Tangent ); GL.MultiTexCoord2( TextureUnit.Texture0, 1f, 0f ); GL.Vertex3( 1.0f, -1.0f, 0.0f ); GL.Normal3( Normal ); GL.VertexAttrib3( AttribTangent, ref Tangent ); GL.MultiTexCoord2( TextureUnit.Texture0, 1f, 1f ); GL.Vertex3( 1.0f, 1.0f, 0.0f ); } GL.End( ); #endregion Draw Quad GL.UseProgram( 0 ); // visualize the light position 'somehow' GL.Begin( BeginMode.Points ); { GL.Color3( LightSpecular ); GL.Vertex3( LightPosition ); } GL.End( ); GL.PopMatrix( ); this.SwapBuffers( ); } /// Entry point [STAThread] public static void Main( ) { using ( T12_GLSL_Parallax example = new T12_GLSL_Parallax( ) ) { Utilities.SetWindowTitle( example ); example.Run( 30.0, 0.0 ); } } } }