Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
1.1.0:water [2023/07/31 15:01] – created - external edit 127.0.0.11.1.0:water [2024/01/23 08:23] (current) – [Example] admin
Line 3: Line 3:
 A water surface is not seldomly a popular game component. We do not provide directly a water surface component, but it is quite easy to setup one. A water surface is not seldomly a popular game component. We do not provide directly a water surface component, but it is quite easy to setup one.
  
-{{ :water-example.jpg |}}+The water shader generates the surface by using different textures: DUDV, Normal, Specular, Displacement, Foam and ShoreFoam. 
 +By those user specific textures you can control the look of your water. 
 + 
 +Besides texturing you'll need additional render pass computation for rendering depth, refraction and reflection. 
 + 
 +{{:1.1.0:g3d-water-new.png|}}
  
 Water rendering is based on: Water rendering is based on:
-  * a plane +  * TGorillaPlane (best results) 
-  * refraction computation +  * TGorillaWaterMaterialSource 
-  * reflection computation +  * TGorillaRenderPassReflection (Reflections) 
-  * and the water shader itself+  * TGorillaRenderPassRefraction (Depth + Refraction)
 ===== Components ===== ===== Components =====
  
Line 15: Line 20:
  
 The plane is a simple and opaque instance of TGorillaPlane or TPlane. It will be used as basis mesh to render water onto. The plane is a simple and opaque instance of TGorillaPlane or TPlane. It will be used as basis mesh to render water onto.
 +
 +Using a none planar object is not recommended, because reflection and refraction computation is based on plane models.
  
 ==== Refraction ==== ==== Refraction ====
Line 27: Line 34:
 FRefraction.IgnoreControl(FWaterPlane); FRefraction.IgnoreControl(FWaterPlane);
 </file> </file>
 +
 The water surface would be black, because it's image information will be computed in main render pass first. The water surface would be black, because it's image information will be computed in main render pass first.
 +
 +Because the refraction pass should render from a certain position, we have to adjust the surface position to the current water plane position everytime it changes:
 +<file pascal>
 +  // set the current position of the water plane as surface plane
 +  // this needs to be updated, if water plane moves
 +  FRefraction.SurfacePosition.Point := TPoint3D(FWaterPlane.AbsolutePosition);
 +</file>
  
 ==== Reflection ==== ==== Reflection ====
Line 47: Line 62:
   // set the current position of the water plane as mirror plane   // set the current position of the water plane as mirror plane
   // this needs to be updated, if water plane moves   // this needs to be updated, if water plane moves
-  FReflection.MirrorPosition := TPoint3D(FWaterPlane.AbsolutePosition);+  FReflection.MirrorPosition.Point := TPoint3D(FWaterPlane.AbsolutePosition);
 </file> </file>
  
 In case plane size or position changes, you have to update those values in reflection pass. In case plane size or position changes, you have to update those values in reflection pass.
  
 +==== Displacement ====
 +
 +Our water shader supports displacement mapping to render waves. This manipulates vertex position in the vertex shader.
 +To use displacement mapping, apply a tetxure to the "DisplacementMap" property and configure the intensity of waves by
 +the "DisplIntensity" property of your material source.
 +
 +When working with displacement mapping, vertices getting shifted. This leads to offsets in refraction and reflection, because
 +in those render passes we rendered with different information.
 +
 +Therefor you have to adjust the reflection mirror position and refraction surface position by the "DisplIntensity" value:
 +
 +<file pascal>
 +  FReflection.MirrorPosition.Point := TPoint3D(FWaterPlane.AbsolutePosition) + 
 +    Point3D(0, -FWaterMaterial.DisplIntensity, 0);
 +    
 +  FRefraction.SurfacePosition.Point := TPoint3D(FWaterPlane.AbsolutePosition) + 
 +    Point3D(0, -FWaterMaterial.DisplIntensity, 0);
 +</file>
 ==== Water Material ==== ==== Water Material ====
  
Line 62: Line 95:
   * SpeculareTexture   * SpeculareTexture
   * FoamTexture   * FoamTexture
 +  * ShoreTexture
  
 The different textures may look like these: The different textures may look like these:
Line 98: Line 132:
 | ReflCorrection | By this value you can modify the color of reflection, default value: Vector3D(1.0, 1.0, 1.0, 1.0) | | ReflCorrection | By this value you can modify the color of reflection, default value: Vector3D(1.0, 1.0, 1.0, 1.0) |
 | RefrCorrection | By this value you can modify the color of refraction on water surface, default value: Vector3D(1.1, 1.1, 1.1, 1.0) | | RefrCorrection | By this value you can modify the color of refraction on water surface, default value: Vector3D(1.1, 1.1, 1.1, 1.0) |
 +|DepthIntensity|Control the depth map value intensity while computation. This influences the blending of edges and the mixture between refraction and reflection.| 
 +|DisplIntensity|Control displacement mapping intensity| 
 +|FoamIntensity|Control the color intensity of the applied foam texture.| 
 +|ShoreIntensity|Control the color intensity of the applied shore foam texture.| 
 +|ShoreWidth|Increase or decrease the size of the shore where the texture affects|
 ==== Ripples ==== ==== Ripples ====
  
Line 128: Line 166:
 end; end;
 </file> </file>
 +
 +Ripple computation in the vertex shader is embedded inside our water shader program.
 +In case you'll need additional handling in the fragment shader, you could a code snippet like that.
 +
 +<file Pascal>
 +var LStr := TStringList.Create();
 +try
 +  LStr.Text := 
 + '''
 +   void SurfaceShader(inout TLocals DATA){
 + float l_TotalRippleEffect = 0.0;
 + float l_Time = abs(_TimeInfo.w);
 + vec3 l_TransfVertexPos = DATA.TransfVertPos.xyz;
 +
 + for(int i = 0; i < int(_RippleCount); i++){
 +   float l_RippleDist = distance(l_TransfVertexPos.xz, _Ripples[i].xz);
 +   float l_RippleTime = abs((_TimeInfo.x + l_Time) - _Ripples[i].w);
 +   float l_TimeLimit = clamp(1.0 / (l_RippleTime / (_RippleProximity * _RippleDecay)), 0.0, 1.0);
 +
 +   float l_RippleEffect = (-_RippleAmplification * exp(_RippleDecay * - l_RippleDist));
 +   l_RippleEffect *= cos(_RippleProximity * (l_RippleDist - l_RippleTime));
 +   l_RippleEffect *= l_TimeLimit;
 +
 +   l_TotalRippleEffect += exp(-l_RippleDist) * sin(0.5 * l_RippleDist) * l_RippleEffect;
 + }
 +
 + if(l_TotalRippleEffect > 0.01){
 +   vec4 l_BoatTrail = tex2D(_WaterShore, DATA.TexCoord0.xy);
 +   DATA.BaseColor.rgb += vec3(l_BoatTrail.rgb * l_TotalRippleEffect);
 +   DATA.SumColor.rgb += vec3(l_BoatTrail.rgb * l_TotalRippleEffect);
 + }
 +   }
 + ''';
 +
 +  FWaterMat.SurfaceShader := LStr;
 +finally
 +  LStr.Free;
 +end;
 +</file>
 +
 +This snippet will render the water shore texture onto the ripples if they have a minimum size of 0.01.
 +===== Tutorial =====
 +
 +{{youtube>bIXeUcTrd58}}
 +
 ===== Example ===== ===== Example =====
  
Line 135: Line 218:
  
 <file pascal> <file pascal>
-procedure TForm1.CreateWater(const AAssetsPath String); +unit Unit1; 
-var LTexPath : String;+ 
 +interface 
 + 
 +uses 
 +  System.SysUtils, 
 +  System.Types, 
 +  System.UITypes, 
 +  System.Classes, 
 +  System.Variants, 
 +  System.IOUtils, 
 +  System.Math.Vectors, 
 +  FMX.Types, 
 +  FMX.Controls, 
 +  FMX.Forms, 
 +  FMX.Graphics, 
 +  FMX.Dialogs, 
 +  FMX.Types3D, 
 +  FMX.Controls3D, 
 +  FMX.Objects3d, 
 +  FMX.MaterialSources, 
 +  FMX.StdCtrls, 
 +  FMX.Controls.Presentation, 
 +  Gorilla.DefTypes, 
 +  Gorilla.Viewport, 
 +  Gorilla.Skybox, 
 +  Gorilla.Cube, 
 +  Gorilla.Plane, 
 +  Gorilla.Terrain, 
 +  Gorilla.Terrain.Utils, 
 +  Gorilla.Terrain.Algorithm, 
 +  Gorilla.Controller.Passes.Reflection, 
 +  Gorilla.Controller.Passes.Refraction, 
 +  Gorilla.Material.Water, 
 +  Gorilla.Material.Lambert; 
 + 
 +type 
 +  TForm1 = class(TForm) 
 +    procedure FormCreate(Sender: TObject); 
 +  private 
 +    FGorilla  : TGorillaViewport; 
 +    FLight    : TLight; 
 +    FSkyBox   : TGorillaSkyBox; 
 +    FTerrain  : TGorillaTerrain; 
 +    FTerrainMat : TGorillaLambertMaterialSource; 
 +    FWaterPlane : TGorillaPlane; 
 +    FWaterMat   : TGorillaWaterMaterialSource; 
 +    FReflection : TGorillaRenderPassReflection; 
 +    FRefraction : TGorillaRenderPassRefraction; 
 +    FReflectBody : TGorillaCube; 
 +    FReflectBodyMat : TGorillaLambertMaterialSource; 
 +  public 
 +    { Public-Deklarationen } 
 +  end; 
 + 
 +var 
 +  Form1: TForm1; 
 + 
 +implementation 
 + 
 +{$R *.fmx} 
 + 
 +uses 
 +  Gorilla.Utils.Math, 
 +  Gorilla.Control, 
 +  Gorilla.Controller, 
 +  Gorilla.Context.Texturing; 
 + 
 +procedure TForm1.FormCreate(SenderTObject); 
 +var LPath : String;
 begin begin
-  LTexPath := IncludeTrailingPathDelimiter(AAssetsPath + 'water');+  // Get the platform independent assets path 
 +{$IFDEF MSWINDOWS} 
 +  LPath := IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0))) + 
 +    IncludeTrailingPathDelimiter('assets'); 
 +{$ELSE} 
 +  LPath := IncludeTrailingPathDelimiter(TPath.GetHomePath()); 
 +{$ENDIF}
  
-  /// create the water plane +  // Create the Gorilla3D viewport 
-  FWaterPlane := TGorillaPlane.Create(FViewport); +  FGorilla := TGorillaViewport.Create(Self); 
-  FWaterPlane.Name := 'WaterPlane1'; +  FGorilla.Name := 'GorillaViewport1'; 
-  FWaterPlane.BeginUpdate();+  FGorilla.Color := TAlphaColorRec.Black; 
 +  FGorilla.OnMouseUp := DoOnViewportMouseUp; 
 +  FGorilla.FXAA := 0; 
 + 
 +  // Create a light source 
 +  FLight := TLight.Create(FGorilla); 
 +  FLight.Parent := FGorilla; 
 +  FLight.Name := 'GorillaLight1'; 
 +  FLight.LightType := TLightType.Point; 
 +  FLight.AbsolutePosition := Vector3D(0, -25, -5); 
 + 
 +  // Skybox to have more reflections 
 +  FSkyBox := TGorillaSkyBox.Create(FGorilla); 
 +  FSkyBox.Parent := FGorilla; 
 + 
 +  // Terrain with random generation 
 +  FTerrain := TGorillaTerrain.Create(FGorilla); 
 +  FTerrain.Parent := FGorilla; 
 +  fTerrain.BeginUpdate();
   try   try
-    FWaterPlane.Parent := FViewport+    FTerrain.ResolutionX := 128
-    FWaterPlane.HitTest := false+    FTerrain.ResolutionY := 128
-    FWaterPlane.RotationAngle.X := -90+    FTerrain.HitTest := false
-    FWaterPlane.Width  := MAP_SIZE+    FTerrain.Height := 1
-    FWaterPlane.Height := MAP_SIZE+    FTerrain.Scale.X := 10
-    FWaterPlane.Depth  := 1; +    FTerrain.Scale.Y := 1; 
-    FWaterPlane.SubdivisionsHeight := 64; +    FTerrain.Scale.:= 10
-    FWaterPlane.SubdivisionsWidth  := 64+    FTerrain.RandomTerrain(TRandomTerrainAlgorithmType.DiamondSquare, true);
-    FWaterPlane.Position.Y := -25;+
   finally   finally
-    FWaterPlane.EndUpdate();+    FTerrain.EndUpdate();
   end;   end;
  
-  /// create reflection render pass for water material +  // Create a material for the terrain mesh 
-  FReflection := TGorillaRenderPassReflection.Create(FViewport); +  FTerrainMat := TGorillaLambertMaterialSource.Create(fTerrain); 
-  FReflection.Viewport := FViewport+  FTerrainMat.Parent := FTerrain; 
-  FReflection.Camera := FCamera;+  TGorillaLambertMaterialSource(FTerrainMat).Texture.LoadFromFile(LPath + 'terrain.jpg'); 
 +  FTerrain.MaterialSource := FTerrainMat; 
 +   
 +  // Water surface plane to render water    
 +  FWaterPlane := TGorillaPlane.Create(FGorilla); 
 +  FWaterPlane.SetHitTestValue(false); 
 +  FWaterPlane.Parent := FGorilla; 
 +  FWaterPlane.RotationAngle.X := 90; 
 +  FWaterPlane.Width  := 10; 
 +  FWaterPlane.Height := 10; 
 +  FWaterPlane.Position.Y := -0.5; 
 +   
 +  // High resolution for ripple testing - vertex shader modifies it 
 +  FWaterPlane.SubdivisionsWidth  := 256; 
 +  FWaterPlane.SubdivisionsHeight := 256; 
 + 
 +  // Water shader material attached to our water surface plane 
 +  FWaterMat := TGorillaWaterMaterialSource.Create(FWaterPlane); 
 +  FWaterMat.Parent := FWaterPlane; 
 + 
 +  FWaterMat.DisplacementMap.LoadFromFile(LPath + 'water-height.jpg'); 
 +  FWaterMat.DisplIntensity := 0.1; 
 +  FWaterMat.NormalMap.LoadFromFile(LPath + 'water-normal.jpg'); 
 +  FWaterMat.DUDVTexture.LoadFromFile(LPath + 'water-dudv.jpg'); 
 +  FWaterMat.SpecularMap.LoadFromFile(LPath + 'water-specular.jpg'); 
 +  FWaterMat.FoamTexture.LoadFromFile(LPath + 'water-foam.jpg'); 
 +  FWaterMat.TextureTiling[WATER_TEX_FOAM] := PointF(4, 4); 
 + 
 +  FWaterMat.ShoreTexture.LoadFromFile(LPath + 'water-shore.png'); 
 +  FWaterMat.TextureTiling[WATER_TEX_SHORE] := PointF(2, 2); 
 + 
 +  // Link material to the water surface plane 
 +  FWaterPlane.MaterialSource := FWaterMat; 
 + 
 +  // Reflection render pass 
 +  FReflection := TGorillaRenderPassReflection.Create(FGorilla); 
 +  FReflection.Viewport := FGorilla; 
 +   
 +  // Ignore the water plane, otherwise the rendering might be black
   FReflection.IgnoreControl(FWaterPlane);   FReflection.IgnoreControl(FWaterPlane);
   FReflection.MirrorSize := FWaterPlane.Width;   FReflection.MirrorSize := FWaterPlane.Width;
      
-  // set the current position of the water plane as mirror plane +  // The mirror surface position is important to to render correctly 
-  // this needs to be updatedif water plane moves +  // Because displacement mapping is activatedwe need to render reflections 
-  FReflection.MirrorPosition := TPoint3D(FWaterPlane.AbsolutePosition);+  // with the displacement offset 
 +  FReflection.MirrorPosition.Point := TPoint3D(FWaterPlane.AbsolutePosition) 
 +    Point3D(0, -FWaterMat.DisplIntensity, 0); 
 +  
 +  // We do not need fullscreen rendering for reflections, half of the size is enough 
 +  FReflection.ViewportScale := 0.5;
   FReflection.Enabled := true;   FReflection.Enabled := true;
  
-  /// create refraction render pass for water material +  // Refraction render pass 
-  FRefraction := TGorillaRenderPassRefraction.Create(FViewport); +  FRefraction := TGorillaRenderPassRefraction.Create(fGorilla); 
-  FRefraction.Viewport := FViewport+  FRefraction.Viewport := fGorilla; 
-  FRefraction.IgnoreControl(FWaterPlane);+   
 +  // Ignore the water plane, otherwise the rendering might be black 
 +  FRefraction.IgnoreControl(fWaterPlane); 
 +   
 +  // The mirror surface position is important to to render correctly 
 +  // Because displacement mapping is activated, we need to render reflections 
 +  // with the displacement offset 
 +  FRefraction.SurfacePosition.Point := TPoint3D(FWaterPlane.AbsolutePosition
 +    Point3D(0, -FWaterMat.DisplIntensity, 0); 
 +  
 +  // We do not need fullscreen rendering for refractions, half of the size is enough 
 +  FRefraction.ViewportScale := 0.5;
   FRefraction.Enabled := true;   FRefraction.Enabled := true;
  
-  /// create material source +  // Link reflection and refraction render passes to the material 
-  FWaterMaterial := TGorillaWaterMaterialSource.Create(FWaterPlane)+  FWaterMat.ReflectionPass := fReflection
-  FWaterMaterial.Parent := FWaterPlane+  FWaterMat.Reflections := true
-  FWaterMaterial.NormalMap.LoadFromFile(LTexPath + 'water_normal.png')+  FWaterMat.ReflectionPower := 1
-  FWaterMaterial.DUDVTexture.LoadFromFile(LTexPath + 'water3-dudv.jpg')+  FWaterMat.RefractionPass := fRefraction
-  FWaterMaterial.DisplacementMap.LoadFromFile(LTexPath + 'water_height.png')+  FWaterMat.Refractions := true
-  FWaterMaterial.SpecularMap.LoadFromFile(LTexPath + 'water_height.png'); +  FWaterMat.RefractionPower := 1;
-  FWaterMaterial.FoamTexture.LoadFromFile(LTexPath + 'foam.png');+
  
-  /// link reflection and refraction render pass to water material +  // Create a reflective body and material to test reflections / refraction 
-  FWaterMaterial.ReflectionPass := FReflection+  FReflectBody := TGorillaCube.Create(FGorilla)
-  FWaterMaterial.RefractionPass := FRefraction;+  FReflectBody.Parent := FGorilla;
  
-  FWaterPlane.MaterialSource := FWaterMaterial;+  FReflectBodyMat := TGorillaLambertMaterialSource.Create(FReflectBody); 
 +  FReflectBodyMat.Parent := FReflectBody; 
 +  FReflectBodyMat.UseTexture0 := false; 
 +  FReflectBody.MaterialSource := FReflectBodyMat;
 end; end;
 +
 +end.
 </file> </file>
  
 Next step: [[bokeh|Bokeh]] Next step: [[bokeh|Bokeh]]