├── Assets ├── ArtResources.meta ├── ArtResources │ ├── HeightMap.meta │ ├── HeightMap │ │ ├── height128.raw │ │ └── height128.raw.meta │ ├── Materials.meta │ ├── Materials │ │ ├── TerrainRender.mat │ │ └── TerrainRender.mat.meta │ ├── Shaders.meta │ ├── Shaders │ │ ├── TerrainRender.shader │ │ └── TerrainRender.shader.meta │ ├── Textures.meta │ └── Textures │ │ ├── HighTile.tga │ │ ├── HighTile.tga.meta │ │ ├── TerrainTexture.tga │ │ ├── TerrainTexture.tga.meta │ │ ├── detailMap.tga │ │ ├── detailMap.tga.meta │ │ ├── grass_1.tga │ │ ├── grass_1.tga.meta │ │ ├── highestTile.tga │ │ ├── highestTile.tga.meta │ │ ├── lowTile.tga │ │ ├── lowTile.tga.meta │ │ ├── lowestTile.tga │ │ └── lowestTile.tga.meta ├── Resources.meta ├── Runtime_TerrainTexture.png ├── Runtime_TerrainTexture.png.meta ├── Scenes.meta ├── Scenes │ ├── DemoScene.unity │ └── DemoScene.unity.meta ├── Scripts.meta └── Scripts │ ├── CQuadTreeTerrain.cs │ ├── CQuadTreeTerrain.cs.meta │ ├── Common.meta │ ├── Common │ ├── CCommon.cs │ └── CCommon.cs.meta │ ├── DemoFramework.cs │ ├── DemoFramework.cs.meta │ ├── RenderInWireframe.cs │ ├── RenderInWireframe.cs.meta │ ├── Utility.meta │ └── Utility │ ├── CUtility.cs │ └── CUtility.cs.meta ├── Backup └── Scripts_Roughess.rar └── README.md /Assets/ArtResources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b6c2c625af4f01040bebb193ff4b9198 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/ArtResources/HeightMap.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d9ddb46e7c7b9e243b428c262b48e0ec 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/ArtResources/HeightMap/height128.raw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/HeightMap/height128.raw -------------------------------------------------------------------------------- /Assets/ArtResources/HeightMap/height128.raw.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a14ddeafa51be3d4487b7f3dbf6975cf 3 | DefaultImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/ArtResources/Materials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 71789424a7e035246b5e5e505a16c145 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/ArtResources/Materials/TerrainRender.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Materials/TerrainRender.mat -------------------------------------------------------------------------------- /Assets/ArtResources/Materials/TerrainRender.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fcefd9e03a2bcd84fb8f25e6e70dcbf2 3 | NativeFormatImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/ArtResources/Shaders.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ba867fab2b25704885d143deed5ec4d 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/ArtResources/Shaders/TerrainRender.shader: -------------------------------------------------------------------------------- 1 | Shader "Terrain/QuadTree/TerrainRender" 2 | { 3 | Properties 4 | { 5 | _Color("Color Tint",Color) = (1,1,1,1) 6 | _MainTex ("Main Tex", 2D) = "white" {} 7 | _DetailTex("Detail Tex",2D) = "white"{} 8 | } 9 | SubShader 10 | { 11 | 12 | Tags { "LightMode"="ForwardBase" } 13 | 14 | Cull Off 15 | 16 | 17 | 18 | Pass 19 | { 20 | CGPROGRAM 21 | #include "UnityCG.cginc" 22 | #include "Lighting.cginc" 23 | #pragma vertex vert 24 | #pragma fragment frag 25 | 26 | fixed4 _Color ; 27 | sampler2D _MainTex ; 28 | float4 _MainTex_ST ; 29 | sampler2D _DetailTex ; 30 | float4 _DetailTex_ST ; 31 | 32 | 33 | struct a2v 34 | { 35 | float4 vertex : POSITION ; 36 | float4 normal : NORMAL ; 37 | float4 texcoord : TEXCOORD0; 38 | } ; 39 | 40 | struct v2f 41 | { 42 | float4 pos : SV_POSITION ; 43 | float3 worldNormal : TEXCOORD0 ; 44 | float3 worldPos : TEXCOORD1 ; 45 | float2 uv : TEXCOORD2 ; 46 | 47 | } ; 48 | 49 | v2f vert(a2v v) 50 | { 51 | v2f o ; 52 | o.pos = mul(UNITY_MATRIX_MVP,v.vertex) ; 53 | o.worldNormal = mul( v.normal,(float3x3)_World2Object) ; 54 | o.worldPos = mul(_Object2World,v.vertex).xyz ; 55 | o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw ; 56 | return o ; 57 | } 58 | 59 | fixed4 frag(v2f i ): SV_Target 60 | { 61 | 62 | fixed3 color1 = tex2D(_MainTex,i.uv).rgb ; 63 | fixed3 color2 = tex2D(_DetailTex,i.uv).rgb ; 64 | fixed3 finalColor = color1 * ( color2 * 2 ) ; 65 | 66 | return fixed4(finalColor,1.0) ; 67 | 68 | } 69 | 70 | ENDCG 71 | } 72 | } 73 | FallBack "Diffuse" 74 | } 75 | -------------------------------------------------------------------------------- /Assets/ArtResources/Shaders/TerrainRender.shader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e94f63abff8b04141b346ab185b944e4 3 | ShaderImporter: 4 | defaultTextures: [] 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f6e614d8add9a1f408d28c5f2b318f77 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/HighTile.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/HighTile.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/HighTile.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a2832f94d1d0b9419bcaebd75d7443f 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/TerrainTexture.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/TerrainTexture.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/TerrainTexture.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb92e53e5590d0144945e0c32589c44c 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/detailMap.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/detailMap.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/detailMap.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a9fa776a92c749a46984c648eb7b961a 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/grass_1.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/grass_1.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/grass_1.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d3c3e0f6cdc9b8b47bb85fbfdd4a7b6e 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/highestTile.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/highestTile.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/highestTile.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 78860135b381d9e4196ae930a8267960 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/lowTile.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/lowTile.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/lowTile.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46b71604c70121144b0985cf8f135793 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/lowestTile.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/ArtResources/Textures/lowestTile.tga -------------------------------------------------------------------------------- /Assets/ArtResources/Textures/lowestTile.tga.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01b833fa002b6284ab081b09144431c6 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 0 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 1 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -1 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 0 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 5 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/Resources.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e44f4e192431b747a0aa5d0e92cf26a 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/Runtime_TerrainTexture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/Runtime_TerrainTexture.png -------------------------------------------------------------------------------- /Assets/Runtime_TerrainTexture.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7339b3ccc111ac14f9bc6ab2678d7450 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | serializedVersion: 2 6 | mipmaps: 7 | mipMapMode: 0 8 | enableMipMap: 1 9 | linearTexture: 0 10 | correctGamma: 0 11 | fadeOut: 0 12 | borderMipMap: 0 13 | mipMapFadeDistanceStart: 1 14 | mipMapFadeDistanceEnd: 3 15 | bumpmap: 16 | convertToNormalMap: 0 17 | externalNormalMap: 0 18 | heightScale: .25 19 | normalMapFilter: 0 20 | isReadable: 0 21 | grayScaleToAlpha: 0 22 | generateCubemap: 0 23 | seamlessCubemap: 0 24 | textureFormat: -3 25 | maxTextureSize: 1024 26 | textureSettings: 27 | filterMode: -1 28 | aniso: 16 29 | mipBias: -1 30 | wrapMode: 1 31 | nPOTScale: 0 32 | lightmap: 0 33 | compressionQuality: 50 34 | spriteMode: 1 35 | spriteExtrude: 1 36 | spriteMeshType: 1 37 | alignment: 0 38 | spritePivot: {x: .5, y: .5} 39 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 40 | spritePixelsToUnits: 100 41 | alphaIsTransparency: 1 42 | textureType: 8 43 | buildTargetSettings: [] 44 | spriteSheet: 45 | sprites: [] 46 | spritePackingTag: 47 | userData: 48 | -------------------------------------------------------------------------------- /Assets/Scenes.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0245904ee3a3bdc4ca00440e40789c79 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/Scenes/DemoScene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Assets/Scenes/DemoScene.unity -------------------------------------------------------------------------------- /Assets/Scenes/DemoScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cf73a4ff0c053294b8775d09f5688346 3 | DefaultImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6cfcece6c353b4c42bd4ee57dfaf4e91 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/Scripts/CQuadTreeTerrain.cs: -------------------------------------------------------------------------------- 1 | //This code came from book 《Focus On 3D Terrain Programming》 ,thanks Trent Polack a lot 2 | using UnityEngine; 3 | using System.Collections.Generic; 4 | using Assets.Scripts.Utility; 5 | using Assets.Scripts.Common; 6 | 7 | namespace Assets.Scripts.QuadTree 8 | { 9 | 10 | #region 结点定义 11 | 12 | class CQuadTreeNode 13 | { 14 | public CQuadTreeNode mTopLeftNode; 15 | public CQuadTreeNode mTopRightNode; 16 | public CQuadTreeNode mBottomRightNode; 17 | public CQuadTreeNode mBottomLetfNode; 18 | 19 | public bool mbSubdivide; 20 | public int mIndexX; 21 | public int mIndexZ; 22 | public enNodeType mNodeType; 23 | 24 | public CQuadTreeNode(int x, int z) 25 | { 26 | mIndexX = x; 27 | mIndexZ = z; 28 | mbSubdivide = true; 29 | } 30 | } 31 | 32 | enum enNodeTriFanType 33 | { 34 | Complete_Fan = 0, //全部划三角形 35 | BottomLeft_TopLeft_TopRight = 1, 36 | TopLeft_TopRight_BottomRight = 2, 37 | TopLeft_TopRight = 3, 38 | TopRight_BottomRight_BottomLeft = 4, 39 | BottomLeft_TopRight = 5, 40 | TopRight_BottomRight = 6, 41 | TopRight = 7, 42 | BottomRight_BottomLeft_TopLeft = 8, 43 | BottomLeft_TopLeft = 9, 44 | BottomRight_TopLeft = 10, 45 | TopLeft = 11, 46 | BottomLeft_BottomRight = 12, 47 | BottomLeft = 13, 48 | BottomRight = 14, //右下划三角形 49 | No_Fan = 15 //全部子结点都要进行下一部划分 50 | } 51 | 52 | 53 | //哪个位置的三角形 54 | enum enFanPosition 55 | { 56 | One = 1, 57 | Two = 2, 58 | Four = 4, 59 | Eight = 8, 60 | } 61 | 62 | 63 | //节点的位置 64 | enum enNodeType 65 | { 66 | Root = 0, 67 | BottomRight = 1, 68 | BottomLeft = 2, 69 | TopLeft = 4, 70 | TopRight = 8, 71 | } 72 | 73 | 74 | 75 | struct stFanGenerator 76 | { 77 | private bool mbDrawLeftMid; 78 | private bool mbDrawTopMid; 79 | private bool mbDrawRightMid; 80 | private bool mbDrawBottomMid; 81 | 82 | private stVertexAtrribute mCenterVertex; 83 | private stVertexAtrribute mBottomLeftVertex; 84 | private stVertexAtrribute mLeftMidVertex; 85 | private stVertexAtrribute mTopLeftVertex; 86 | private stVertexAtrribute mTopMidVertex; 87 | private stVertexAtrribute mTopRightVertex; 88 | private stVertexAtrribute mRightMidVertex; 89 | private stVertexAtrribute mBottomRightVertex; 90 | private stVertexAtrribute mBottomMidVertex; 91 | 92 | 93 | public stFanGenerator( 94 | bool drawLeftMid, 95 | bool drawTopMid, 96 | bool drawRightMid, 97 | bool drawBottomMid, 98 | stVertexAtrribute centerVertex, 99 | stVertexAtrribute bottomLeftVertex, 100 | stVertexAtrribute leftMidVertex, 101 | stVertexAtrribute topLeftVertex, 102 | stVertexAtrribute topMidVertex, 103 | stVertexAtrribute topRightVertex, 104 | stVertexAtrribute rightMidVertex, 105 | stVertexAtrribute bottomRightVertex, 106 | stVertexAtrribute bottomMidVertex 107 | ) 108 | { 109 | mbDrawLeftMid = drawLeftMid; 110 | mbDrawTopMid = drawTopMid; 111 | mbDrawRightMid = drawRightMid; 112 | mbDrawBottomMid = drawBottomMid; 113 | 114 | mCenterVertex = centerVertex; 115 | mBottomLeftVertex = bottomLeftVertex; 116 | mLeftMidVertex = leftMidVertex; 117 | mTopLeftVertex = topLeftVertex; 118 | mTopMidVertex = topMidVertex; 119 | mTopRightVertex = topRightVertex; 120 | mRightMidVertex = rightMidVertex; 121 | mBottomRightVertex = bottomRightVertex; 122 | mBottomMidVertex = bottomMidVertex; 123 | } 124 | 125 | 126 | public void DrawFan(ref stTerrainMeshData meshData, enFanPosition fanPos) 127 | { 128 | switch (fanPos) 129 | { 130 | #region Draw 1 131 | case enFanPosition.One: 132 | { 133 | ///////////////////////Draw 1 //////////////////////// 134 | //Bottom Right 2 Triangle 135 | if (mbDrawRightMid && mbDrawBottomMid) 136 | { 137 | ///1 center right mid bottom right 138 | meshData.RenderTriangle(mCenterVertex, mRightMidVertex, mBottomRightVertex); 139 | ///2 center bottom right bottom mid 140 | meshData.RenderTriangle(mCenterVertex, mBottomRightVertex, mBottomMidVertex); 141 | } 142 | else if (mbDrawRightMid) 143 | { 144 | ///1 center right mid bottom right 145 | meshData.RenderTriangle(mCenterVertex, mRightMidVertex, mBottomRightVertex); 146 | ///2 center bottom right bottom left 147 | meshData.RenderTriangle(mCenterVertex, mBottomRightVertex, mBottomLeftVertex); 148 | } 149 | else if (mbDrawBottomMid) 150 | { 151 | ///1 center top right bottom right 152 | meshData.RenderTriangle(mCenterVertex, mTopRightVertex, mBottomRightVertex); 153 | ///2 center bottom right bottom mid 154 | meshData.RenderTriangle(mCenterVertex, mBottomRightVertex, mBottomMidVertex); 155 | } 156 | else 157 | { 158 | meshData.RenderTriangle(mCenterVertex, mTopRightVertex, mBottomRightVertex); 159 | meshData.RenderTriangle(mCenterVertex, mBottomRightVertex, mBottomLeftVertex); 160 | } 161 | 162 | /////////////////////Draw 1 //////////////////////////////// 163 | break; 164 | } 165 | 166 | #endregion 167 | 168 | #region Draw 2 169 | case enFanPosition.Two: 170 | { 171 | 172 | ///////////////// Draw 2 //////////////////////// 173 | //Bottom Left 2 Triangle 174 | if (mbDrawLeftMid && mbDrawBottomMid) 175 | { 176 | ///1 center bottom mid bottom left 177 | meshData.RenderTriangle(mCenterVertex, mBottomMidVertex, mBottomLeftVertex); 178 | ///2 center bottom left left mid 179 | meshData.RenderTriangle(mCenterVertex, mBottomLeftVertex, mLeftMidVertex); 180 | } 181 | else if (mbDrawLeftMid) 182 | { 183 | ///1 center bottom mid bottom left 184 | meshData.RenderTriangle(mCenterVertex, mBottomRightVertex, mBottomLeftVertex); 185 | ///2 center bottom left left mid 186 | meshData.RenderTriangle(mCenterVertex, mBottomLeftVertex, mLeftMidVertex); 187 | } 188 | else if (mbDrawBottomMid) 189 | { 190 | ///1 center bottom mid bottom left 191 | meshData.RenderTriangle(mCenterVertex, mBottomMidVertex, mBottomLeftVertex); 192 | 193 | ///2 center bottom left top left 194 | meshData.RenderTriangle(mCenterVertex, mBottomLeftVertex, mTopLeftVertex); 195 | } 196 | else 197 | { 198 | ///1 center bottom mid bottom left 199 | meshData.RenderTriangle(mCenterVertex, mBottomRightVertex, mBottomLeftVertex); 200 | ///2 center bottom left top left 201 | meshData.RenderTriangle(mCenterVertex, mBottomLeftVertex, mTopLeftVertex); 202 | } 203 | /////////////////////Draw 2 ////////////////////////////// 204 | break; 205 | } 206 | 207 | #endregion 208 | 209 | #region Draw 4 210 | case enFanPosition.Four: 211 | { 212 | //////////////Draw 4 /////////////////////// 213 | //Top Left 2 Triangle 214 | if (mbDrawLeftMid && mbDrawTopMid) 215 | { 216 | ///1 center left mid top left 217 | meshData.RenderTriangle(mCenterVertex, mLeftMidVertex, mTopLeftVertex); 218 | ///2 centert top left top mid 219 | meshData.RenderTriangle(mCenterVertex, mTopLeftVertex, mTopMidVertex); 220 | } 221 | else if (mbDrawLeftMid) 222 | { 223 | ///1 center left mid top left 224 | meshData.RenderTriangle(mCenterVertex, mLeftMidVertex, mTopLeftVertex); 225 | ///2 centert top left top right 226 | meshData.RenderTriangle(mCenterVertex, mTopLeftVertex, mTopRightVertex); 227 | } 228 | else if (mbDrawTopMid) 229 | { 230 | ///1 center left mid top left 231 | meshData.RenderTriangle(mCenterVertex, mBottomLeftVertex, mTopLeftVertex); 232 | ///2 centert top left top mid 233 | meshData.RenderTriangle(mCenterVertex, mTopLeftVertex, mTopMidVertex); 234 | } 235 | else 236 | { 237 | ///1 center left mid top left 238 | meshData.RenderTriangle(mCenterVertex, mBottomLeftVertex, mTopLeftVertex); 239 | ///2 centert top left top right 240 | meshData.RenderTriangle(mCenterVertex, mTopLeftVertex, mTopRightVertex); 241 | } 242 | /////////////////Draw 4 /////////////////// 243 | 244 | 245 | break; 246 | } 247 | #endregion 248 | 249 | #region Draw 8 250 | case enFanPosition.Eight: 251 | { 252 | //Top Right Triangle 253 | if (mbDrawTopMid && mbDrawRightMid) 254 | { 255 | ///1 center top mid top right 256 | meshData.RenderTriangle(mCenterVertex, mTopMidVertex, mTopRightVertex); 257 | ///2 center top right right mid 258 | meshData.RenderTriangle(mCenterVertex, mTopRightVertex, mRightMidVertex); 259 | } 260 | else if (mbDrawTopMid) 261 | { 262 | ///1 center top mid top right 263 | meshData.RenderTriangle(mCenterVertex, mTopMidVertex, mTopRightVertex); 264 | // 2 center top right bottom right 265 | meshData.RenderTriangle(mCenterVertex, mTopRightVertex, mBottomRightVertex); 266 | } 267 | else if (mbDrawRightMid) 268 | { 269 | ///1 center top mid top right 270 | meshData.RenderTriangle(mCenterVertex, mTopLeftVertex, mTopRightVertex); 271 | ///2 center top right right mid 272 | meshData.RenderTriangle(mCenterVertex, mTopRightVertex, mRightMidVertex); 273 | } 274 | else 275 | { 276 | ///1 center top mid top right 277 | meshData.RenderTriangle(mCenterVertex, mTopLeftVertex, mTopRightVertex); 278 | // 2 center top right bottom right 279 | meshData.RenderTriangle(mCenterVertex, mTopRightVertex, mBottomRightVertex); 280 | } 281 | break; 282 | } 283 | 284 | #endregion 285 | 286 | } 287 | 288 | } 289 | 290 | } 291 | 292 | 293 | 294 | 295 | 296 | #endregion 297 | 298 | 299 | class CQuadTreeTerrain 300 | { 301 | 302 | #region 核心逻辑 303 | 304 | public List mNodeList = new List(); 305 | 306 | private int GetIndex( int x, int z ) 307 | { 308 | return z * mHeightData.mSize + x; 309 | } 310 | 311 | private CQuadTreeNode GetNode( int x, int z ) 312 | { 313 | //不合法的输入直接排队 314 | if( x < 0 || x >= mHeightData.mSize || z < 0 || z >= mHeightData.mSize ) 315 | { 316 | return null; 317 | } 318 | 319 | int idx = GetIndex(x, z); 320 | return (idx >=0 && idx < mNodeList.Count) ? mNodeList[idx] : null; 321 | } 322 | 323 | 324 | public void GenerateNodes( int size ) 325 | { 326 | mNodeList.Clear(); 327 | for(int z = 0; z < size; ++z ) 328 | { 329 | for(int x = 0; x < size; ++x ) 330 | { 331 | CQuadTreeNode node = new CQuadTreeNode(x, z); 332 | mNodeList.Add(node); 333 | } 334 | } 335 | 336 | Debug.Log(string.Format("[GenerateNodes Done] {0} ",mNodeList.Count)); 337 | } 338 | 339 | 340 | public void ResetNodeSubdivide() 341 | { 342 | for( int i = 0; i < mNodeList.Count; ++i ) 343 | { 344 | mNodeList[i].mbSubdivide = false; 345 | } 346 | } 347 | 348 | 349 | //如果当前结点需要拆分,但是如果 既是父结点,也是当前结点的邻结点不拆分的话,那当前结点也不能够,不然的话,会出现裂缝 350 | private bool CanNodeDivide( CQuadTreeNode childNode , CQuadTreeNode parentANeighborNode , CQuadTreeNode parentBNeighborNode ) 351 | { 352 | bool bRet = false; 353 | if( childNode != null ) 354 | { 355 | bRet = childNode.mbSubdivide; 356 | 357 | if( parentANeighborNode != null ) 358 | { 359 | bRet &= parentANeighborNode.mbSubdivide; //还要看看紧贴的结点是否也要拆 360 | } 361 | if( parentBNeighborNode != null ) 362 | { 363 | bRet &= parentBNeighborNode.mbSubdivide; 364 | } 365 | } 366 | return bRet; 367 | } 368 | 369 | 370 | public void PropagateRoughness(float minResolution, float desResolution) 371 | { 372 | //先重置一波 373 | mRoughnessData.Reset(0); 374 | 375 | int iCurNodeLength = 3; //最小结点 376 | while (iCurNodeLength <= mHeightData.mSize) 377 | { 378 | int iHalfLengthOffset = (iCurNodeLength - 1) >> 1; //半个结点长度,用来求出相邻结点 379 | int iChildHalfLengthOffset = (iCurNodeLength - 1) >> 2; //半个子结点的长度,用来求出相邻子结点 380 | 381 | for (int z = iHalfLengthOffset; z < mHeightData.mSize; z += (iCurNodeLength - 1)) 382 | { 383 | for (int x = iHalfLengthOffset; x < mHeightData.mSize; x += (iCurNodeLength - 1)) 384 | { 385 | int xLeftCur = x - iHalfLengthOffset; 386 | int zBottomCur = z - iHalfLengthOffset; 387 | int xRightCur = x + iHalfLengthOffset; 388 | int zTopCur = z + iHalfLengthOffset; 389 | 390 | int xLeftChild = x - iChildHalfLengthOffset; 391 | int xRightChild = x + iChildHalfLengthOffset; 392 | int zTopChild = z + iChildHalfLengthOffset; 393 | int zBottomChild = z - iChildHalfLengthOffset; 394 | 395 | int topLeftHeight = mHeightData.GetRawHeightValue(xLeftCur, zTopCur); 396 | int topRightHeight = mHeightData.GetRawHeightValue(xRightCur, zTopCur); 397 | int bottomLeftHeight = mHeightData.GetRawHeightValue(xLeftCur, zBottomCur); 398 | int bottomRightHeight = mHeightData.GetRawHeightValue(xRightCur, zBottomCur); 399 | int topMidHeight = mHeightData.GetRawHeightValue(x, zTopCur); 400 | int rightMidHeight = mHeightData.GetRawHeightValue(xRightCur, z); 401 | int bottomMidHeight = mHeightData.GetRawHeightValue(x, zBottomCur); 402 | int leftMidHeight = mHeightData.GetRawHeightValue(xLeftCur, z); 403 | int centerHeight = mHeightData.GetRawHeightValue(x, z); 404 | 405 | float topMidHeightDiff = Mathf.Abs(((float)(topLeftHeight + topRightHeight) / 2.0f) - topMidHeight); 406 | float rightMidHeightDiff = Mathf.Abs(((float)(topRightHeight + bottomRightHeight) /2.0f) - rightMidHeight); 407 | float bottomMidHeightDiff = Mathf.Abs(((float)(bottomLeftHeight + bottomRightHeight) / 2.0f) - bottomMidHeight); 408 | float leftMidHeightDiff = Mathf.Abs(((float)(topLeftHeight + bottomLeftHeight) / 2.0f) - leftMidHeight); 409 | float bottomLeft2TopRightHeightDiff = Mathf.Abs(((float)(bottomLeftHeight + topRightHeight) / 2.0f) - centerHeight); 410 | float bottomRight2TopLeftHeightDiff = Mathf.Abs(((float)(bottomRightHeight + topLeftHeight) / 2.0f) - centerHeight); 411 | 412 | //取出最大的差值 413 | float maxHeightDiff = Mathf.Max(leftMidHeightDiff, topMidHeightDiff, rightMidHeightDiff, bottomMidHeightDiff, bottomLeft2TopRightHeightDiff, bottomRight2TopLeftHeightDiff); 414 | 415 | float localD2 = maxHeightDiff / iCurNodeLength ; //根据论文的公式 d2 = d2Max / d 416 | if( iCurNodeLength != 3 ) //如果不是叶子结点 417 | { 418 | //论文的公式: K = C / (2C - 1),上边界 419 | float fKupperBound = minResolution / (2.0f * (minResolution - 1)); 420 | 421 | //这里也是根据论文的内容 422 | //The d2-value of each block is the maximum 423 | //of the local value and K times the previously 424 | //calculated values of adjacent blocks at the next lower 425 | //level. 426 | 427 | //上一个Lod计算出来的D2值 428 | float fLeftChildMidD2 = mRoughnessData.GetRoughnessValue(xLeftChild, z) * fKupperBound; 429 | float fRightChildMidD2 = mRoughnessData.GetRoughnessValue(xRightChild, z) * fKupperBound; 430 | float fTopChildMidD2 = mRoughnessData.GetRoughnessValue(x, zTopChild) * fKupperBound; 431 | float fBottomChildMidD2 = mRoughnessData.GetRoughnessValue(x, zBottomChild) * fKupperBound; 432 | 433 | //取出最大值 434 | localD2 = (int)Mathf.Max( 435 | localD2, 436 | fLeftChildMidD2, 437 | fRightChildMidD2, 438 | fTopChildMidD2, 439 | fBottomChildMidD2 440 | ); 441 | } // else 442 | 443 | 444 | float tCenterD2 = mRoughnessData.GetRoughnessValue(x, z); 445 | localD2 = Mathf.Max(tCenterD2, localD2); 446 | mRoughnessData.SetRoughnessValue(localD2, x, z); 447 | 448 | //推荐的四个的D2值 449 | float bottomLeftD2 = Mathf.Max(mRoughnessData.GetRoughnessValue(xLeftCur, zBottomCur), localD2); 450 | float topLeftD2 = Mathf.Max(mRoughnessData.GetRoughnessValue(xLeftCur, zTopCur), localD2); 451 | float topRightD2 = Mathf.Max(mRoughnessData.GetRoughnessValue(xRightCur, zTopCur), localD2); 452 | float bottomRightD2 = Mathf.Max(mRoughnessData.GetRoughnessValue(xRightCur, zBottomCur), localD2); 453 | 454 | 455 | //传递到四个顶点,其实不太明白这样的做的原因 456 | mRoughnessData.SetRoughnessValue(bottomLeftD2, xLeftCur, zBottomCur); 457 | mRoughnessData.SetRoughnessValue(topLeftD2, xLeftCur, zTopCur); 458 | mRoughnessData.SetRoughnessValue(topRightD2, xRightCur, zTopCur); 459 | mRoughnessData.SetRoughnessValue(bottomRightD2, xRightCur, zBottomCur); 460 | 461 | } // for 462 | } // for 463 | 464 | iCurNodeLength = (iCurNodeLength << 1) - 1; 465 | 466 | } //while 467 | } //function 468 | 469 | 470 | public CQuadTreeNode RefineNode( 471 | float x, 472 | float z, 473 | int curNodeLength, //暂时定为节点的个数 474 | Camera viewCamera, 475 | Vector3 vectorScale, 476 | float desiredResolution, 477 | float minResolution, 478 | bool useRoughnessEvalute 479 | ) 480 | { 481 | //这个其实只要运行一次就够了,但是为了可以演示的时候运行调整,放在每次更新的时候了 482 | Profiler.BeginSample("QuadTree.PropagateRoughness"); 483 | if ( useRoughnessEvalute ) 484 | { 485 | PropagateRoughness(minResolution, desiredResolution); 486 | } 487 | Profiler.EndSample(); 488 | 489 | return RefineNodeImpl(x, z, curNodeLength, enNodeType.Root, null, null, null, null, viewCamera, vectorScale, desiredResolution, minResolution,useRoughnessEvalute); 490 | } 491 | 492 | public CQuadTreeNode RefineNodeImpl( 493 | float x , 494 | float z , 495 | int curNodeLength , //暂时定为节点的个数 496 | enNodeType nodeType , 497 | CQuadTreeNode parentTopNeighborNode , 498 | CQuadTreeNode parentRightNeighborNode , 499 | CQuadTreeNode parentBottomNeighborNode , 500 | CQuadTreeNode parentLeftNeighborNode, 501 | Camera viewCamera , 502 | Vector3 vectorScale, 503 | float desiredResolution, 504 | float minResolution, 505 | bool useRoughnessEvalute 506 | ) 507 | { 508 | if( null == viewCamera ) 509 | { 510 | Debug.LogError("[RefineNode]View Camera is Null!"); 511 | return null; 512 | } 513 | 514 | int tX = (int)x; 515 | int tZ = (int)z; 516 | 517 | CQuadTreeNode qtNode = GetNode(tX, tZ); 518 | if( null == qtNode ) 519 | { 520 | Debug.LogError( string.Format("[RefineNode]No Such Node at :{0}|{1}",tX,tZ)); 521 | return null ; 522 | } 523 | 524 | qtNode.mNodeType = nodeType; 525 | 526 | //评价公式 527 | ushort nodeHeight = GetTrueHeightAtPoint(qtNode.mIndexX, qtNode.mIndexZ); 528 | float fViewDistance = Mathf.Sqrt( 529 | Mathf.Pow(viewCamera.transform.position.x - qtNode.mIndexX * vectorScale.x, 2) + 530 | Mathf.Pow(viewCamera.transform.position.y - nodeHeight * vectorScale.y, 2) + 531 | Mathf.Pow(viewCamera.transform.position.z - qtNode.mIndexZ * vectorScale.z, 2) 532 | ); 533 | 534 | 535 | //1、没有高度的话,单纯就判断水平上的距离来判断是否拆分 536 | //2、有高度的话, 537 | //float fDenominator = vectorScale.y == 0 ? 538 | // Mathf.Max(curNodeLength * vectorScale.x * minResolution, 1.0f) : 539 | // (curNodeLength * minResolution * Mathf.Max(desiredResolution * nodeHeight / 3, 1.0f)); 540 | //float f = fViewDistance / fDenominator; 541 | float f = 0; 542 | if(useRoughnessEvalute) 543 | { 544 | float d = (curNodeLength - 1) * vectorScale.x; 545 | float d2 = mRoughnessData.GetRoughnessValue(tX, tZ); 546 | float C = minResolution; 547 | float c = desiredResolution; 548 | float fDenominator = vectorScale.y == 0 ? 549 | (Mathf.Max(d * Mathf.Max(c, C), 1.0f)) : 550 | (d * C * Mathf.Max( c * d2 , 1.0f) ); 551 | f = fViewDistance / fDenominator; 552 | } 553 | else 554 | { 555 | // l / dc < 1 556 | float d = ( curNodeLength - 1 ) * vectorScale.x; 557 | float C = Mathf.Max(minResolution, desiredResolution); 558 | 559 | float fDenominator = Mathf.Max(d * C, 1.0f) ; 560 | f = fViewDistance / fDenominator; 561 | } 562 | 563 | 564 | //这个其实是补丁,原本的算法没有的,这里作用是如果预处理之后,还是发生了crack的情况,就强行拆面 565 | //一个节点是否能够划分 566 | //1、满足评价 567 | //2、和相邻的结点不相差两个层级,也就当前结点和父结点的兄弟结点不能相差两个层级 568 | qtNode.mbSubdivide = f < 1.0f ? true : false; 569 | switch (qtNode.mNodeType) 570 | { 571 | case enNodeType.TopLeft: 572 | { 573 | qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentTopNeighborNode, parentLeftNeighborNode); 574 | break; 575 | } 576 | case enNodeType.TopRight: 577 | { 578 | qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentTopNeighborNode, parentRightNeighborNode); 579 | break; 580 | } 581 | case enNodeType.BottomRight: 582 | { 583 | qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentRightNeighborNode, parentBottomNeighborNode); 584 | break; 585 | } 586 | case enNodeType.BottomLeft: 587 | { 588 | qtNode.mbSubdivide &= CanNodeDivide(qtNode, parentLeftNeighborNode, parentBottomNeighborNode); 589 | break; 590 | } 591 | } 592 | 593 | //决定是否画顶点的 594 | int iNeighborOffset = curNodeLength - 1; 595 | int iX = (int)x; 596 | int iZ = (int)z; 597 | 598 | int iZBottom = iZ - iNeighborOffset; 599 | int iZTop = iZ + iNeighborOffset; 600 | int iXLeft = iX - iNeighborOffset; 601 | int iXRight = iX + iNeighborOffset; 602 | 603 | CQuadTreeNode bottomNeighborNode = GetNode(iX, iZBottom); 604 | CQuadTreeNode rightNeighborNode = GetNode(iXRight, iZ); 605 | CQuadTreeNode topNeighborNode = GetNode(iX, iZTop); 606 | CQuadTreeNode leftNeighborNode = GetNode(iXLeft, iZ); 607 | 608 | 609 | if ( qtNode.mbSubdivide ) 610 | { 611 | if( !(curNodeLength <= 3) ) 612 | { 613 | float fChildeNodeOffset = (float)((curNodeLength - 1) >> 2); 614 | int tChildNodeLength = (curNodeLength + 1) >> 1; 615 | 616 | //bottom-right 617 | qtNode.mBottomRightNode = RefineNodeImpl(x + fChildeNodeOffset, z - fChildeNodeOffset, tChildNodeLength, enNodeType.BottomRight, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution , useRoughnessEvalute); 618 | //bottom-left 619 | qtNode.mBottomLetfNode = RefineNodeImpl(x - fChildeNodeOffset, z - fChildeNodeOffset, tChildNodeLength, enNodeType.BottomLeft, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution, useRoughnessEvalute); 620 | //top-left 621 | qtNode.mTopLeftNode = RefineNodeImpl(x - fChildeNodeOffset, z + fChildeNodeOffset, tChildNodeLength, enNodeType.TopLeft, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution, useRoughnessEvalute); 622 | //top-right 623 | qtNode.mTopRightNode = RefineNodeImpl(x + fChildeNodeOffset, z + fChildeNodeOffset, tChildNodeLength, enNodeType.TopRight, topNeighborNode, rightNeighborNode, bottomNeighborNode, leftNeighborNode, viewCamera, vectorScale, desiredResolution, minResolution, useRoughnessEvalute); 624 | } 625 | } 626 | 627 | return qtNode; 628 | } 629 | 630 | 631 | #endregion 632 | 633 | 634 | 635 | #region 将模型渲染上去 636 | 637 | 638 | public void DrawGizoms(ref stTerrainMeshData meshData , Vector3 vertexScale, float gizmosScale, Color gizmosColor) 639 | { 640 | Gizmos.color = gizmosColor; 641 | 642 | Mesh mesh = meshData.mMesh; 643 | if (null == mesh) 644 | { 645 | Debug.LogError("Terrain without Mesh"); 646 | return; 647 | } 648 | 649 | meshData.Reset(); 650 | 651 | for (int z = 0; z < mHeightData.mSize; ++z) 652 | { 653 | for (int x = 0; x < mHeightData.mSize; ++x) 654 | { 655 | float y = mHeightData.GetRawHeightValue(x, z); 656 | Gizmos.DrawSphere(new Vector3(x * vertexScale.x, y * vertexScale.y, z * vertexScale.z),gizmosScale); 657 | } 658 | } 659 | } 660 | 661 | 662 | public void CLOD_Render( ref stTerrainMeshData meshData , Vector3 vertexScale ) 663 | { 664 | meshData.Reset(); 665 | float fCenter = (mHeightData.mSize - 1) >> 1; 666 | 667 | RenderNode(fCenter, fCenter, mHeightData.mSize,ref meshData ,vertexScale); 668 | 669 | meshData.Present(); 670 | } 671 | 672 | 673 | private Vector3 GetScaleVector3(float x,float z, Vector3 vectorScale ) 674 | { 675 | return new Vector3( 676 | x * vectorScale.x, 677 | mHeightData.GetRawHeightValue((int)x,(int)z) * vectorScale.y, 678 | z * vectorScale.z 679 | ); 680 | } 681 | 682 | 683 | private stVertexAtrribute GenerateVertex( 684 | int vertexX, 685 | int vertexZ, 686 | float fX, 687 | float fZ, 688 | float uvX , float uvZ , Vector3 vectorScale ) 689 | { 690 | return new stVertexAtrribute( 691 | GetIndex(vertexX, vertexZ), 692 | GetScaleVector3(fX, fZ, vectorScale), 693 | new Vector2(uvX, uvZ) 694 | ); 695 | } 696 | 697 | 698 | private void RenderNode( float fX ,float fZ ,int curNodeLength ,ref stTerrainMeshData meshData, Vector3 vectorScale ) 699 | { 700 | int tHeightMapSize = mHeightData.mSize; 701 | int iX = (int)fX; 702 | int iZ = (int)fZ; 703 | 704 | CQuadTreeNode curNode = GetNode(iX, iZ); 705 | if( null == curNode ) 706 | { 707 | Debug.LogError(string.Format("[RenderNode]No Node at :{0}|{1}", iX, iZ)); 708 | return; 709 | } 710 | 711 | //当前节点边长的一半 712 | int iHalfNodeLength = (curNodeLength - 1) >> 1; 713 | float fHalfNodeLength = (curNodeLength - 1) / 2.0f; 714 | 715 | //中点左位置 716 | int iLeftX = iX - iHalfNodeLength; 717 | float fLeftX = fX - fHalfNodeLength; 718 | 719 | //中点右位置 720 | int iRightX = iX + iHalfNodeLength; 721 | float fRightX = fX + iHalfNodeLength; 722 | 723 | //顶点中点位置 724 | int iTopZ = iZ + iHalfNodeLength; 725 | float fTopZ = fZ + fHalfNodeLength; 726 | 727 | //底部中点位置 728 | int iBottomZ = iZ - iHalfNodeLength; 729 | float fBottomZ = fZ - fHalfNodeLength; 730 | 731 | 732 | //边长减1 ?相邻节点的距离 733 | int iNeighborOffset = curNodeLength - 1; 734 | 735 | float fTexLeft = Mathf.Abs(fX - fHalfNodeLength) / tHeightMapSize; 736 | float fTexBottom = Mathf.Abs(fZ - fHalfNodeLength) / tHeightMapSize; 737 | float fTexRight = Mathf.Abs(fX+ fHalfNodeLength) / tHeightMapSize; 738 | float fTexTop = Mathf.Abs(fZ + fHalfNodeLength) / tHeightMapSize; 739 | 740 | float fTexMidX = (fTexLeft + fTexRight) / 2.0f; 741 | float fTexMidZ = (fTexBottom + fTexTop) / 2.0f; 742 | 743 | 744 | //决定是否画顶点的 745 | int iZBottom = iZ - iNeighborOffset; 746 | int iZTop = iZ + iNeighborOffset; 747 | int iXLeft = iX - iNeighborOffset; 748 | int iXRight = iX + iNeighborOffset; 749 | 750 | CQuadTreeNode bottomNeighborNode = GetNode(iX,iZBottom ); 751 | CQuadTreeNode rightNeighborNode = GetNode(iXRight, iZ); 752 | CQuadTreeNode topNeighborNode = GetNode(iX, iZTop); 753 | CQuadTreeNode leftNeighborNode = GetNode(iXLeft, iZ); 754 | 755 | //举一个例子,如果下边的邻结点没有,或者下面的同级的邻结点是需要划分的,即当前结点也需要划分 756 | bool bDrawBottomMidVertex = (iZBottom < 0) || (bottomNeighborNode != null && bottomNeighborNode.mbSubdivide); 757 | bool bDrawRightMidVertex = (iXRight >= tHeightMapSize) || (rightNeighborNode != null && rightNeighborNode.mbSubdivide); 758 | bool bDrawTopMidVertex = (iZTop >= tHeightMapSize) || (topNeighborNode != null && topNeighborNode.mbSubdivide); 759 | bool bDrawLeftMidVertex = (iXLeft< 0) || (leftNeighborNode != null && leftNeighborNode.mbSubdivide); 760 | 761 | 762 | //Center Vertex 763 | stVertexAtrribute tCenterVertex = GenerateVertex(iX, iZ, fX, fZ, fTexMidX, fTexMidZ, vectorScale); 764 | //Bottom Left Vertex 765 | stVertexAtrribute tBottomLeftVertex = GenerateVertex(iLeftX, iBottomZ, fLeftX, fBottomZ, fTexLeft, fTexBottom, vectorScale); 766 | //Left Mid Vertext 767 | stVertexAtrribute tLeftMidVertex = GenerateVertex(iLeftX, iZ, fLeftX, fZ, fTexLeft, fTexMidZ, vectorScale); 768 | //Top Left Vertex 769 | stVertexAtrribute tTopLeftVertex = GenerateVertex(iLeftX, iTopZ, fLeftX, fTopZ, fTexLeft, fTexTop, vectorScale); 770 | //Top Mid Vertex 771 | stVertexAtrribute tTopMidVertex = GenerateVertex(iX, iTopZ, fX, fTopZ, fTexMidX, fTexTop, vectorScale); 772 | //Top Right Vertex 773 | stVertexAtrribute tTopRightVertex = GenerateVertex(iRightX, iTopZ, fRightX, fTopZ, fTexRight, fTexTop, vectorScale); 774 | //Right Mid Vertex 775 | stVertexAtrribute tRightMidVertex = GenerateVertex(iRightX, iZ, fRightX, fZ, fTexRight, fTexMidZ, vectorScale); 776 | //Bottom Right Vertex 777 | stVertexAtrribute tBottomRightVertex = GenerateVertex(iRightX, iBottomZ, fRightX, fBottomZ, fTexRight, fTexBottom, vectorScale); 778 | //Bottom Mide Vertex 779 | stVertexAtrribute tBottomMidVertex = GenerateVertex(iX, iBottomZ, fX, fBottomZ, fTexMidX, fTexBottom, vectorScale); 780 | 781 | stFanGenerator tFanGenerator = new stFanGenerator( 782 | bDrawLeftMidVertex, 783 | bDrawTopMidVertex, 784 | bDrawRightMidVertex, 785 | bDrawBottomMidVertex, 786 | tCenterVertex, 787 | tBottomLeftVertex, 788 | tLeftMidVertex, 789 | tTopLeftVertex, 790 | tTopMidVertex, 791 | tTopRightVertex, 792 | tRightMidVertex, 793 | tBottomRightVertex, 794 | tBottomMidVertex 795 | ); 796 | 797 | if ( curNode.mbSubdivide ) 798 | { 799 | #region 已经是最小的LOD 800 | 801 | //已经是最小的LOD的 802 | if( curNodeLength <= 3 ) 803 | { 804 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 805 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 806 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 807 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 808 | } // <= 3 809 | 810 | #endregion 811 | 812 | #region 还可以继续划分 813 | 814 | else 815 | { 816 | int tChildHalfLength = (curNodeLength - 1) >> 2; 817 | float fChildHalfLength = (float)tChildHalfLength; 818 | 819 | int tChildNodeLength = (curNodeLength + 1) >> 1; 820 | 821 | int tFanCode = 0; 822 | 823 | 824 | int iChildRightX = iX + tChildHalfLength; 825 | int iChildTopZ = iZ + tChildHalfLength; 826 | int iChildLeftX = iX - tChildHalfLength; 827 | int iChildBottomZ = iZ - tChildHalfLength; 828 | 829 | float fChildRightX = fX + fChildHalfLength; 830 | float fChildTopZ = fZ + fChildHalfLength; 831 | float fChildLeftX = fX - fChildHalfLength; 832 | float fChildBottomZ = fZ - fChildHalfLength; 833 | 834 | CQuadTreeNode topRightChildNode = GetNode( iChildRightX,iChildTopZ); 835 | CQuadTreeNode topLeftChildNode = GetNode(iChildLeftX, iChildTopZ); 836 | CQuadTreeNode bottomLeftChildNode = GetNode(iChildLeftX, iChildBottomZ); 837 | CQuadTreeNode bottomRightChildNode = GetNode(iChildRightX, iChildBottomZ); 838 | 839 | //拆分程度不能相差2 840 | //左上子结点 841 | //bool bTopLeftChildDivide = CanNodeDivide(topLeftChildNode,leftNeighborNode,topNeighborNode); 842 | bool bTopLeftChildDivide = topLeftChildNode != null && topLeftChildNode.mbSubdivide; 843 | //右上子结点 844 | //bool bTopRightChildDivide = CanNodeDivide(topRightChildNode,topNeighborNode,rightNeighborNode); 845 | bool bTopRightChildDivide = topRightChildNode != null && topRightChildNode.mbSubdivide; 846 | //右下 847 | //bool bBottomRightChildDivide = CanNodeDivide(bottomRightChildNode,bottomNeighborNode,rightNeighborNode); 848 | bool bBottomRightChildDivide = bottomRightChildNode != null && bottomRightChildNode.mbSubdivide; 849 | //左下 850 | //bool bBottomLeftChildDivide = CanNodeDivide(bottomLeftChildNode,bottomNeighborNode,leftNeighborNode); 851 | bool bBottomLeftChildDivide = bottomLeftChildNode != null && bottomLeftChildNode.mbSubdivide; 852 | 853 | //top right sud divide 854 | if ( bTopRightChildDivide ) 855 | { 856 | tFanCode |= 8; 857 | } 858 | 859 | //top left 860 | if ( bTopLeftChildDivide ) 861 | { 862 | tFanCode |= 4; 863 | } 864 | 865 | //bottom left 866 | if (bBottomLeftChildDivide) 867 | { 868 | tFanCode |= 2; 869 | } 870 | 871 | //bottom right 872 | if ( bBottomRightChildDivide ) 873 | { 874 | tFanCode |= 1; 875 | } 876 | 877 | #region 各种情况的组合 878 | 879 | enNodeTriFanType fanType = (enNodeTriFanType)tFanCode; 880 | switch (fanType) 881 | { 882 | 883 | #region 15 四个子结点都分割 884 | //子结点一个都不分割 885 | case enNodeTriFanType.No_Fan: 886 | { 887 | //bottom right 888 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 889 | 890 | //bottom left 891 | RenderNode( fChildLeftX ,fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 892 | 893 | //top left 894 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 895 | 896 | //top right 897 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 898 | 899 | break; 900 | } 901 | #endregion 902 | 903 | #region 5 左上右下分割,左下右上画三角形 904 | //左上右下分割,左下右上画三角形 905 | case enNodeTriFanType.BottomLeft_TopRight: 906 | { 907 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 908 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 909 | 910 | //bottom right 911 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 912 | //top left 913 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 914 | 915 | break; 916 | } 917 | #endregion 918 | 919 | #region 10 左下右上分割,左上右下画三角形 920 | case enNodeTriFanType.BottomRight_TopLeft: 921 | { 922 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 923 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 924 | 925 | //bottom left 926 | RenderNode(fX - fChildHalfLength, fZ - fChildHalfLength, tChildNodeLength, ref meshData, vectorScale); 927 | //top right 928 | RenderNode(fX + fChildHalfLength, fZ + fChildHalfLength, tChildNodeLength, ref meshData, vectorScale); 929 | 930 | break; 931 | } 932 | 933 | #endregion 934 | 935 | #region 0 直接画出8个三角形 936 | case enNodeTriFanType.Complete_Fan: 937 | { 938 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 939 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 940 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 941 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 942 | 943 | break; 944 | } 945 | 946 | #endregion 947 | 948 | #region 1 左下左上右上划三角形 949 | case enNodeTriFanType.BottomLeft_TopLeft_TopRight: 950 | { 951 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 952 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 953 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 954 | 955 | //Bottom Right Child Node 956 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 957 | 958 | break; 959 | } 960 | 961 | #endregion 962 | 963 | #region 2 左上右上右下划三角形 964 | 965 | case enNodeTriFanType.TopLeft_TopRight_BottomRight: 966 | { 967 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 968 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 969 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 970 | 971 | //bottom left 972 | RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 973 | 974 | break; 975 | } 976 | 977 | 978 | #endregion 979 | 980 | #region 3 左上右上划三角形 981 | 982 | case enNodeTriFanType.TopLeft_TopRight: 983 | { 984 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 985 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 986 | 987 | //bottom left 988 | RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 989 | //bottom right 990 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 991 | 992 | break; 993 | } 994 | 995 | #endregion 996 | 997 | #region 4 右上右下左下划三角形 998 | case enNodeTriFanType.TopRight_BottomRight_BottomLeft: 999 | { 1000 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 1001 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 1002 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 1003 | 1004 | //top left 1005 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1006 | 1007 | break; 1008 | } 1009 | 1010 | #endregion 1011 | 1012 | #region 6 右上右下划三角形 1013 | case enNodeTriFanType.TopRight_BottomRight: 1014 | { 1015 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 1016 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 1017 | 1018 | //bottom left 1019 | RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1020 | 1021 | //top left 1022 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1023 | 1024 | break; 1025 | } 1026 | 1027 | #endregion 1028 | 1029 | #region 7 右上划三角形 1030 | case enNodeTriFanType.TopRight: 1031 | { 1032 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Eight); 1033 | 1034 | //bottom right 1035 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1036 | //bottom left 1037 | RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1038 | //top left 1039 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1040 | 1041 | break; 1042 | } 1043 | 1044 | #endregion 1045 | 1046 | #region 8 右下左下左上划三角形 1047 | case enNodeTriFanType.BottomRight_BottomLeft_TopLeft: 1048 | { 1049 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 1050 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 1051 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 1052 | //top right 1053 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1054 | 1055 | break; 1056 | } 1057 | #endregion 1058 | 1059 | #region 9 左下左上划三角形 1060 | case enNodeTriFanType.BottomLeft_TopLeft: 1061 | { 1062 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 1063 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 1064 | 1065 | //bottom right 1066 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1067 | //top right 1068 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1069 | 1070 | break; 1071 | } 1072 | 1073 | #endregion 1074 | 1075 | #region 11 左上划三角形 1076 | case enNodeTriFanType.TopLeft: 1077 | { 1078 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Four); 1079 | //bottom right 1080 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1081 | //bottom left 1082 | RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1083 | //top right 1084 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1085 | 1086 | break; 1087 | } 1088 | 1089 | #endregion 1090 | 1091 | #region 12 左下右下划三角形 1092 | case enNodeTriFanType.BottomLeft_BottomRight: 1093 | { 1094 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 1095 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 1096 | 1097 | //top left 1098 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1099 | //top right 1100 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1101 | 1102 | break; 1103 | } 1104 | 1105 | #endregion 1106 | 1107 | #region 13 左下划三角形 1108 | case enNodeTriFanType.BottomLeft: 1109 | { 1110 | tFanGenerator.DrawFan(ref meshData, enFanPosition.Two); 1111 | 1112 | //bottom right 1113 | RenderNode(fChildRightX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1114 | 1115 | //top left 1116 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1117 | 1118 | //top right 1119 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1120 | 1121 | break; 1122 | } 1123 | 1124 | #endregion 1125 | 1126 | #region 14 右下划三角形 1127 | case enNodeTriFanType.BottomRight: 1128 | { 1129 | tFanGenerator.DrawFan(ref meshData, enFanPosition.One); 1130 | //bottom left 1131 | RenderNode(fChildLeftX, fChildBottomZ, tChildNodeLength, ref meshData, vectorScale); 1132 | //top left 1133 | RenderNode(fChildLeftX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1134 | //top right 1135 | RenderNode(fChildRightX, fChildTopZ, tChildNodeLength, ref meshData, vectorScale); 1136 | 1137 | break; 1138 | } 1139 | 1140 | #endregion 1141 | } 1142 | 1143 | #endregion 1144 | 1145 | } 1146 | 1147 | #endregion 1148 | 1149 | } // if subdivied 1150 | 1151 | } //RenderNode 1152 | 1153 | 1154 | 1155 | public void Render( ref stTerrainMeshData meshData ,Vector3 vertexScale ) 1156 | { 1157 | Mesh mesh = meshData.mMesh; 1158 | if( null == mesh ) 1159 | { 1160 | Debug.LogError("Terrain without Mesh"); 1161 | return; 1162 | } 1163 | 1164 | meshData.Reset(); 1165 | 1166 | 1167 | Profiler.BeginSample("Rebuild Vertices & UVs"); 1168 | Vector2[] uv = meshData.mUV; 1169 | Vector3[] normals = meshData.mNormals; 1170 | Vector3[] vertices = meshData.mVertices ; 1171 | for( int z = 0; z < mHeightData.mSize ; ++z) 1172 | { 1173 | for(int x = 0; x < mHeightData.mSize ; ++x) 1174 | { 1175 | float y = mHeightData.GetRawHeightValue(x, z); 1176 | int vertexIdx = z * mHeightData.mSize + x; 1177 | vertices[vertexIdx] = new Vector3(x*vertexScale.x, y * vertexScale.y, z * vertexScale.z); 1178 | uv[vertexIdx] = new Vector2((float)x / (float)mHeightData.mSize, (float)z / (float)mHeightData.mSize); 1179 | normals[vertexIdx] = Vector3.zero; 1180 | } 1181 | } 1182 | //mesh.vertices = vertices; 1183 | //mesh.uv = uv; 1184 | //mesh.normals = normals; 1185 | Profiler.EndSample(); 1186 | 1187 | 1188 | Profiler.BeginSample("Rebuild Triangles"); 1189 | int nIdx = 0; 1190 | int[] triangles = meshData.mTriangles; //一个正方形对应两个三角形,6个顶点 1191 | for (int z = 0; z < mHeightData.mSize - 1; ++z) 1192 | { 1193 | for (int x = 0; x < mHeightData.mSize - 1; ++x) 1194 | { 1195 | int bottomLeftIdx = z * mHeightData.mSize + x; 1196 | int topLeftIdx = (z + 1) * mHeightData.mSize + x; 1197 | int topRightIdx = topLeftIdx + 1; 1198 | int bottomRightIdx = bottomLeftIdx + 1; 1199 | 1200 | triangles[nIdx++] = bottomLeftIdx; 1201 | triangles[nIdx++] = topLeftIdx; 1202 | triangles[nIdx++] = bottomRightIdx; 1203 | triangles[nIdx++] = topLeftIdx; 1204 | triangles[nIdx++] = topRightIdx; 1205 | triangles[nIdx++] = bottomRightIdx; 1206 | 1207 | } 1208 | } 1209 | 1210 | //mesh.triangles = triangles; 1211 | 1212 | meshData.Present(); 1213 | 1214 | Profiler.EndSample(); 1215 | } 1216 | 1217 | 1218 | 1219 | #endregion 1220 | 1221 | 1222 | 1223 | #region 地形纹理操作 1224 | 1225 | private List mTerrainTiles = new List(); 1226 | private Texture2D mTerrainTexture; 1227 | public Texture2D TerrainTexture 1228 | { 1229 | get 1230 | { 1231 | return mTerrainTexture; 1232 | } 1233 | 1234 | } 1235 | 1236 | 1237 | 1238 | public void GenerateTextureMap( uint uiSize ,ushort maxHeight , ushort minHeight ) 1239 | { 1240 | if( mTerrainTiles.Count <= 0 ) 1241 | { 1242 | return; 1243 | } 1244 | 1245 | mTerrainTexture = null; 1246 | int tHeightStride = maxHeight / mTerrainTiles.Count ; 1247 | 1248 | float[] fBend = new float[mTerrainTiles.Count]; 1249 | 1250 | //注意,这里的区域是互相重叠的 1251 | int lastHeight = -1; 1252 | for(int i = 0; i < mTerrainTiles.Count; ++i) 1253 | { 1254 | CTerrainTile terrainTile = mTerrainTiles[i]; 1255 | //lastHeight += 1; 1256 | terrainTile.lowHeight = lastHeight + 1 ; 1257 | lastHeight += tHeightStride; 1258 | 1259 | terrainTile.optimalHeight = lastHeight; 1260 | terrainTile.highHeight = (lastHeight - terrainTile.lowHeight) + lastHeight; 1261 | } 1262 | 1263 | for(int i = 0; i < mTerrainTiles.Count; ++i ) 1264 | { 1265 | CTerrainTile terrainTile = mTerrainTiles[i]; 1266 | string log = string.Format("Tile Type:{0}|lowHeight:{1}|optimalHeight:{2}|highHeight:{3}", 1267 | terrainTile.TileType.ToString(), 1268 | terrainTile.lowHeight, 1269 | terrainTile.optimalHeight, 1270 | terrainTile.highHeight 1271 | ); 1272 | Debug.Log(log); 1273 | } 1274 | 1275 | 1276 | 1277 | mTerrainTexture = new Texture2D((int)uiSize,(int)uiSize, TextureFormat.RGBA32,false); 1278 | 1279 | CUtility.SetTextureReadble(mTerrainTexture, true); 1280 | 1281 | float fMapRatio = (float)mHeightData.mSize / uiSize; 1282 | 1283 | for (int z = 0; z < uiSize; ++z) 1284 | { 1285 | for (int x = 0; x < uiSize; ++x) 1286 | { 1287 | 1288 | Color totalColor = new Color(); 1289 | 1290 | for (int i = 0; i < mTerrainTiles.Count; ++i) 1291 | { 1292 | CTerrainTile tile = mTerrainTiles[i]; 1293 | if (tile.mTileTexture == null) 1294 | { 1295 | continue; 1296 | } 1297 | 1298 | int uiTexX = x; 1299 | int uiTexZ = z; 1300 | 1301 | //CUtility.SetTextureReadble(tile.mTileTexture, true); 1302 | 1303 | GetTexCoords(tile.mTileTexture, ref uiTexX, ref uiTexZ); 1304 | 1305 | 1306 | 1307 | Color color = tile.mTileTexture.GetPixel(uiTexX, uiTexZ); 1308 | fBend[i] = RegionPercent(tile.TileType, Limit(InterpolateHeight(x, z, fMapRatio),maxHeight,minHeight)); 1309 | 1310 | totalColor.r = Mathf.Min(color.r * fBend[i] + totalColor.r, 1.0f); 1311 | totalColor.g = Mathf.Min(color.g * fBend[i] + totalColor.g, 1.0f); 1312 | totalColor.b = Mathf.Min(color.b * fBend[i] + totalColor.b, 1.0f); 1313 | totalColor.a = 1.0f; 1314 | 1315 | //CUtility.SetTextureReadble(tile.mTileTexture, false); 1316 | }// 1317 | 1318 | //输出到纹理上 1319 | if (totalColor.r == 0.0f 1320 | && totalColor.g == 0.0f 1321 | && totalColor.b == 0.0f) 1322 | { 1323 | ushort xHeight = (ushort)(x * fMapRatio); 1324 | ushort zHeight = (ushort)(z * fMapRatio); 1325 | Debug.Log(string.Format("Color is Black | uiX:{0}|uiZ:{1}|hX:{2}|hZ:{3}|h:{4}",x,z,xHeight,zHeight,GetTrueHeightAtPoint(xHeight,zHeight))); 1326 | } 1327 | 1328 | mTerrainTexture.SetPixel(x, z,totalColor); 1329 | } 1330 | } 1331 | 1332 | //OpenGL纹理的操作 1333 | mTerrainTexture.Apply(); 1334 | CUtility.SetTextureReadble(mTerrainTexture, false); 1335 | 1336 | //string filePath = string.Format("{0}/{1}", Application.dataPath, "Runtime_TerrainTexture.png"); 1337 | //File.WriteAllBytes(filePath,mTerrainTexture.EncodeToPNG()); 1338 | //AssetDatabase.ImportAsset(filePath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate); 1339 | //AssetDatabase.SaveAssets(); 1340 | } // 1341 | 1342 | 1343 | public CTerrainTile GetTile( enTileTypes tileType ) 1344 | { 1345 | return mTerrainTiles.Count > 0 ? mTerrainTiles.Find(curTile => curTile.TileType == tileType) : null; 1346 | } 1347 | 1348 | 1349 | float RegionPercent( enTileTypes tileType , ushort usHeight ) 1350 | { 1351 | CTerrainTile tile = GetTile(tileType); 1352 | if (tile == null) 1353 | { 1354 | Debug.LogError(string.Format("No tileType : Type:{0}|Height:{1}", tileType.ToString(), usHeight)); 1355 | return 0.0f ; 1356 | } 1357 | 1358 | CTerrainTile lowestTile = GetTile(enTileTypes.lowest_tile); 1359 | CTerrainTile lowTile = GetTile(enTileTypes.low_tile); 1360 | CTerrainTile highTile = GetTile(enTileTypes.high_tile); 1361 | CTerrainTile highestTile = GetTile(enTileTypes.highest_tile); 1362 | 1363 | //如果最低的块已经加载了,且落在它的low Height的块里面 1364 | if (lowestTile != null ) 1365 | { 1366 | if( tileType == enTileTypes.lowest_tile 1367 | && IsHeightAllLocateInTile(tileType,usHeight)) 1368 | { 1369 | return 1.0f; 1370 | } 1371 | } 1372 | 1373 | else if (lowTile != null) 1374 | { 1375 | if (tileType == enTileTypes.low_tile 1376 | && IsHeightAllLocateInTile(tileType, usHeight)) 1377 | { 1378 | return 1.0f; 1379 | } 1380 | } 1381 | else if ( highTile!= null) 1382 | { 1383 | if (tileType == enTileTypes.high_tile 1384 | && IsHeightAllLocateInTile(tileType, usHeight)) 1385 | { 1386 | return 1.0f; 1387 | } 1388 | } 1389 | else if (highestTile != null) 1390 | { 1391 | if (tileType == enTileTypes.highest_tile 1392 | && IsHeightAllLocateInTile(tileType, usHeight)) 1393 | { 1394 | return 1.0f; 1395 | } 1396 | } 1397 | 1398 | //以[,)左闭右开吧 1399 | if (usHeight < tile.lowHeight || usHeight > tile.highHeight) 1400 | { 1401 | return 0.0f; 1402 | } 1403 | 1404 | 1405 | if ( usHeight < tile.optimalHeight ) 1406 | { 1407 | float fTemp1 = usHeight - tile.lowHeight; 1408 | float fTemp2 = tile.optimalHeight - tile.lowHeight; 1409 | 1410 | //这段会产生小斑点,因为有些值可能会比较特殊 1411 | //if (fTemp1 == 0.0f) 1412 | //{ 1413 | // Debug.LogError(string.Format("Lower than Optimal Height: Type:{0}|Height:{1}|fTemp1:{2}|lowHeight:{3}|optimalHeight:{4}", tileType.ToString(), usHeight, fTemp1, tile.lowHeight, tile.optimalHeight)); 1414 | // return 1.0f; 1415 | //} 1416 | return fTemp1 / fTemp2; 1417 | } 1418 | else if( usHeight == tile.optimalHeight ) 1419 | { 1420 | return 1.0f; 1421 | } 1422 | else if( usHeight > tile.optimalHeight ) 1423 | { 1424 | float fTemp1 = tile.highHeight - tile.optimalHeight; 1425 | 1426 | //这段会产生小斑点,因为有些值可能会比较特殊 1427 | //if (((fTemp1 - (usHeight - tile.optimalHeight)) / fTemp1) == 0.0f) 1428 | //{ 1429 | // Debug.LogError(string.Format("Higher than Optimal Height: Type:{0}|Height:{1}|fTemp1:{2}|optimalHeight:{3}", tileType.ToString(), usHeight, fTemp1, tile.optimalHeight)); 1430 | // return 1.0f; 1431 | //} 1432 | return ((fTemp1 - (usHeight - tile.optimalHeight)) / fTemp1); 1433 | } 1434 | 1435 | Debug.LogError(string.Format("Unknow: Type:{0}|Height:{1}", tileType.ToString(), usHeight)); 1436 | return 0.0f; 1437 | } 1438 | 1439 | 1440 | private ushort GetTrueHeightAtPoint( int x ,int z ) 1441 | { 1442 | return mHeightData.GetRawHeightValue(x, z); 1443 | } 1444 | 1445 | //两个高度点之间的插值,这里的很有意思的 1446 | private ushort InterpolateHeight( int x,int z , float fHeight2TexRatio ) 1447 | { 1448 | float fScaledX = x * fHeight2TexRatio; 1449 | float fScaledZ = z * fHeight2TexRatio; 1450 | 1451 | ushort usHighX = 0; 1452 | ushort usHighZ = 0; 1453 | 1454 | //X的A点 1455 | ushort usLow = GetTrueHeightAtPoint((int)fScaledX, (int)fScaledZ); 1456 | 1457 | if( ( fScaledX + 1 ) > mHeightData.mSize ) 1458 | { 1459 | return usLow; 1460 | } 1461 | else 1462 | { 1463 | //X的B点 1464 | usHighX = GetTrueHeightAtPoint((int)fScaledX + 1, (int)fScaledZ); 1465 | } 1466 | 1467 | //X的A、B两点之间插值 1468 | float fInterpolation = (fScaledX - (int)fScaledX); 1469 | float usX = (usHighX - usLow) * fInterpolation + usLow; //插值出真正的高度值 1470 | 1471 | 1472 | //Z轴同理 1473 | if ((fScaledZ + 1) > mHeightData.mSize) 1474 | { 1475 | return usLow; 1476 | } 1477 | else 1478 | { 1479 | //X的B点 1480 | usHighZ = GetTrueHeightAtPoint((int)fScaledX, (int)fScaledZ + 1); 1481 | } 1482 | 1483 | fInterpolation = (fScaledZ - (int)fScaledZ); 1484 | float usZ = (usHighZ - usLow) * fInterpolation + usLow; //插值出真正的高度值 1485 | 1486 | return ((ushort)((usX + usZ) / 2)); 1487 | } 1488 | 1489 | private ushort Limit( ushort usValue , ushort maxHeight , ushort minHeight ) 1490 | { 1491 | if( usValue > maxHeight ) 1492 | { 1493 | return maxHeight ; 1494 | } 1495 | else if( usValue < minHeight ) 1496 | { 1497 | return minHeight; 1498 | } 1499 | return usValue; 1500 | } 1501 | 1502 | 1503 | private bool IsHeightAllLocateInTile( enTileTypes tileType , ushort usHeight ) 1504 | { 1505 | bool bRet = false; 1506 | CTerrainTile tile = GetTile(tileType); 1507 | if (tile != null 1508 | && usHeight <= tile.optimalHeight) 1509 | { 1510 | bRet = true; 1511 | } 1512 | return bRet; 1513 | } 1514 | 1515 | 1516 | //因为要渲染出来的一张地形纹理,可能会比tile的宽高都要大,所以要tile其实是平铺布满地形纹理的 1517 | public void GetTexCoords( Texture2D texture , ref int x , ref int y) 1518 | { 1519 | int uiWidth = texture.width; 1520 | int uiHeight = texture.height; 1521 | 1522 | int tRepeatX = -1; 1523 | int tRepeatY = -1; 1524 | int i = 0; 1525 | 1526 | while( tRepeatX == -1 ) 1527 | { 1528 | i++; 1529 | if( x < (uiWidth * i)) 1530 | { 1531 | tRepeatX = i - 1; 1532 | } 1533 | } 1534 | 1535 | 1536 | i = 0; 1537 | while( tRepeatY == -1 ) 1538 | { 1539 | ++i; 1540 | if( y < ( uiHeight * i) ) 1541 | { 1542 | tRepeatY = i - 1; 1543 | } 1544 | } 1545 | 1546 | 1547 | x = x - (uiWidth * tRepeatX); 1548 | y = y - (uiHeight * tRepeatY); 1549 | } 1550 | 1551 | 1552 | 1553 | 1554 | public void AddTile( enTileTypes tileType , Texture2D tileTexture ) 1555 | { 1556 | if( tileTexture != null ) 1557 | { 1558 | if( mTerrainTiles.Exists( curTile => curTile.TileType == tileType )) 1559 | { 1560 | CTerrainTile oldTile = mTerrainTiles.Find(curTile => curTile.TileType == tileType); 1561 | if( oldTile != null ) 1562 | { 1563 | oldTile = new CTerrainTile(tileType, tileTexture); 1564 | } 1565 | } 1566 | else 1567 | { 1568 | mTerrainTiles.Add(new CTerrainTile(tileType, tileTexture)); 1569 | } 1570 | } 1571 | } 1572 | 1573 | 1574 | #endregion 1575 | 1576 | #region 高度图数据操作 1577 | 1578 | private stHeightData mHeightData; 1579 | 1580 | public void UnloadHeightMap() 1581 | { 1582 | mHeightData.Release(); 1583 | 1584 | Debug.Log("Height Map is Unload!"); 1585 | } 1586 | 1587 | 1588 | 1589 | 1590 | 1591 | 1592 | /// 1593 | /// This fuction came from book 《Focus On 3D Terrain Programming》 ,thanks Trent Polack a lot 1594 | /// 1595 | /// 1596 | /// 1597 | /// 1598 | /// 1599 | /// 1600 | /// 1601 | public bool MakeTerrainFault( int size , int iter , ushort minHeightValue , ushort maxHeightValue , float fFilter ) 1602 | { 1603 | if( mHeightData.IsValid() ) 1604 | { 1605 | UnloadHeightMap(); 1606 | } 1607 | 1608 | mHeightData.Allocate(size); 1609 | 1610 | float[,] fTempHeightData = new float[size, size]; 1611 | 1612 | for( int iCurIter = 0; iCurIter < iter; ++iCurIter ) 1613 | { 1614 | //高度递减 1615 | int tHeight = maxHeightValue - ((maxHeightValue - minHeightValue ) * iCurIter) / iter; 1616 | //int tHeight = Random.Range(minHeightValue , maxHeightValue); //temp 1617 | 1618 | int tRandomX1 = Random.Range(0, size); 1619 | int tRandomZ1 = Random.Range(0, size); 1620 | 1621 | int tRandomX2 = 0; 1622 | int tRandomZ2 = 0; 1623 | do 1624 | { 1625 | tRandomX2 = Random.Range(0, size); 1626 | tRandomZ2 = Random.Range(0, size); 1627 | } while ( tRandomX2 == tRandomX1 && tRandomZ2 == tRandomZ1 ); 1628 | 1629 | 1630 | //两个方向的矢量 1631 | int tDirX1 = tRandomX2 - tRandomX1; 1632 | int tDirZ1 = tRandomZ2 - tRandomZ1; 1633 | 1634 | //遍历每个顶点,看看分布在分割线的哪一边 1635 | for( int z = 0; z < size; ++z) 1636 | { 1637 | for(int x = 0; x < size; ++x ) 1638 | { 1639 | int tDirX2 = x - tRandomX1; 1640 | int tDirZ2 = z - tRandomZ1; 1641 | 1642 | if( (tDirX2 * tDirZ1 - tDirX1 * tDirZ2) > 0 ) 1643 | { 1644 | fTempHeightData[x, z] += tHeight; //!!!!!自加符号有问题!!!! 1645 | } 1646 | } 1647 | } 1648 | 1649 | FilterHeightField(ref fTempHeightData,size,fFilter); 1650 | 1651 | } 1652 | 1653 | NormalizeTerrain(ref fTempHeightData, size , maxHeightValue ); 1654 | 1655 | for(int z = 0; z < size; ++z) 1656 | { 1657 | for(int x = 0; x < size; ++x) 1658 | { 1659 | SetHeightAtPoint((ushort)fTempHeightData[x, z], x, z); 1660 | } 1661 | } 1662 | 1663 | return true ; 1664 | } 1665 | 1666 | 1667 | void SetHeightAtPoint( ushort usHeight , int x, int z) 1668 | { 1669 | mHeightData.SetHeightValue(usHeight, x, z); 1670 | } 1671 | 1672 | 1673 | void NormalizeTerrain(ref float[,] fHeightData, int size , ushort maxHeight ) 1674 | { 1675 | float fMin = fHeightData[0, 0]; 1676 | float fMax = fHeightData[0, 0]; 1677 | 1678 | for (int z = 0; z < size; ++z) 1679 | { 1680 | for (int x = 0; x < size; ++x ) 1681 | { 1682 | if( fHeightData[x,z] > fMax) 1683 | { 1684 | fMax = fHeightData[x, z]; 1685 | } 1686 | 1687 | if(fHeightData[x,z] < fMin) 1688 | { 1689 | fMin = fHeightData[x, z]; 1690 | } 1691 | 1692 | } 1693 | } 1694 | 1695 | Debug.Log(string.Format("Before Normailzed MaxHeight:{0}|MinHeight:{1}",fMax,fMin)); 1696 | 1697 | if(fMax <= fMin) 1698 | { 1699 | return; 1700 | } 1701 | 1702 | float fHeight = fMax - fMin; 1703 | for (int z = 0; z < size; ++z) 1704 | { 1705 | for (int x = 0; x < size; ++x) 1706 | { 1707 | fHeightData[x, z] = ((fHeightData[x, z] - fMin) / fHeight) * maxHeight ; 1708 | } 1709 | } 1710 | 1711 | 1712 | ///////////////打LOG用 1713 | fMax = fHeightData[0, 0]; 1714 | fMin = fHeightData[0, 0]; 1715 | for (int z = 0; z < size; ++z) 1716 | { 1717 | for (int x = 0; x < size; ++x) 1718 | { 1719 | if (fHeightData[x, z] > fMax) 1720 | { 1721 | fMax = fHeightData[x, z]; 1722 | } 1723 | 1724 | if (fHeightData[x, z] < fMin) 1725 | { 1726 | fMin = fHeightData[x, z]; 1727 | } 1728 | } 1729 | } 1730 | 1731 | Debug.Log(string.Format("After Normailzed MaxHeight:{0}|MinHeight:{1}", fMax, fMin)); 1732 | 1733 | /////////////////////////// 1734 | } 1735 | 1736 | void FilterHeightField( ref float[,] fHeightData ,int size , float fFilter ) 1737 | { 1738 | //四向模糊 1739 | 1740 | //从左往右的模糊 1741 | for ( int i = 0; i < size; ++i) 1742 | { 1743 | FilterHeightBand(ref fHeightData, 1744 | i,0, //初始的x,y 1745 | 0,1, //数组步进值 1746 | size, //数组个数 1747 | fFilter); 1748 | } 1749 | 1750 | //从右往左的模糊 1751 | for( int i = 0; i < size; ++i) 1752 | { 1753 | FilterHeightBand(ref fHeightData, 1754 | i, size -1, 1755 | 0,-1, 1756 | size, 1757 | fFilter); 1758 | } 1759 | 1760 | 1761 | //从上到下的模糊 1762 | for (int i = 0; i < size; ++i) 1763 | { 1764 | FilterHeightBand(ref fHeightData, 1765 | 0,i, 1766 | 1,0, 1767 | size, 1768 | fFilter); 1769 | } 1770 | 1771 | 1772 | //从下到上的模糊 1773 | for (int i = 0; i < size; ++i) 1774 | { 1775 | FilterHeightBand(ref fHeightData, 1776 | size - 1,i , 1777 | -1,0, 1778 | size, 1779 | fFilter); 1780 | } 1781 | } 1782 | 1783 | 1784 | 1785 | void FilterHeightBand( 1786 | ref float[,] fBandData , 1787 | int beginX, 1788 | int beginY, 1789 | int strideX, 1790 | int strideY, 1791 | int count , 1792 | float fFilter ) 1793 | { 1794 | //Debug.Log(string.Format("BeginX:{0} | BeginY:{1} | StrideX:{2} | StrideY:{3}",beginX,beginY,strideX,strideY)); 1795 | 1796 | //float beginValue = fBandData[beginX, beginY]; 1797 | float curValue = fBandData[beginX,beginY]; 1798 | int jx = strideX; 1799 | int jy = strideY; 1800 | 1801 | //float delta = fFilter / (count - 1); 1802 | for( int i = 0; i < count - 1; ++i) 1803 | { 1804 | int nextX = beginX + jx; 1805 | int nextY = beginY + jy; 1806 | 1807 | fBandData[nextX, nextY] = fFilter * curValue + (1 - fFilter) * fBandData[nextX, nextY]; 1808 | curValue = fBandData[nextX, nextY]; 1809 | 1810 | //float tFilter = fFilter - delta * ((jx - beginX + jy - beginY) * 0.5f); 1811 | //fBandData[nextX, nextY] = tFilter * beginValue; 1812 | 1813 | jx += strideX; 1814 | jy += strideY; 1815 | } 1816 | } 1817 | 1818 | 1819 | #endregion 1820 | 1821 | #region 粗糙度传播 1822 | private stRoughnessData mRoughnessData; 1823 | 1824 | public void MakeRounessData(int size) 1825 | { 1826 | mRoughnessData.Allocate(size); 1827 | } 1828 | 1829 | 1830 | #endregion 1831 | 1832 | } 1833 | } 1834 | -------------------------------------------------------------------------------- /Assets/Scripts/CQuadTreeTerrain.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 77a9fb9d2c6b9aa4c83d8f93b361b2aa 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/Scripts/Common.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ccc378b2d3153a04ab79812b93f1af4a 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/Scripts/Common/CCommon.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | 5 | namespace Assets.Scripts.Common 6 | { 7 | #region 纹理数据 8 | 9 | public enum enTileTypes 10 | { 11 | lowest_tile = 0, 12 | low_tile = 1, 13 | high_tile = 2, 14 | highest_tile = 3, 15 | max_tile = 4, 16 | } 17 | 18 | public class CTerrainTile 19 | { 20 | public int lowHeight; 21 | public int optimalHeight; 22 | public int highHeight; 23 | public enTileTypes TileType; 24 | 25 | public Texture2D mTileTexture; 26 | 27 | 28 | public CTerrainTile(enTileTypes tileType, Texture2D texture) 29 | { 30 | lowHeight = 0; 31 | optimalHeight = 0; 32 | highHeight = 0; 33 | 34 | TileType = tileType; 35 | mTileTexture = texture; 36 | } 37 | } 38 | 39 | 40 | 41 | 42 | 43 | 44 | #endregion 45 | 46 | #region 高度图数据 47 | 48 | public struct stHeightData 49 | { 50 | private ushort[,] mHeightData; 51 | public int mSize; 52 | 53 | public bool IsValid() 54 | { 55 | return mHeightData != null; 56 | } 57 | 58 | public void Release() 59 | { 60 | mHeightData = null; 61 | mSize = 0; 62 | } 63 | 64 | 65 | public void Allocate(int mapSize) 66 | { 67 | if (mapSize > 0) 68 | { 69 | mHeightData = new ushort[mapSize, mapSize]; 70 | mSize = mapSize; 71 | } 72 | } 73 | 74 | public void SetHeightValue(ushort value, int x, int y) 75 | { 76 | if (IsValid() && InRange(x, y)) 77 | { 78 | mHeightData[x, y] = value; 79 | } 80 | } 81 | 82 | public ushort GetRawHeightValue(int x, int y) 83 | { 84 | ushort ret = 0; 85 | if (IsValid() && InRange(x, y)) 86 | { 87 | ret = mHeightData[x, y]; 88 | } 89 | return ret; 90 | } 91 | 92 | 93 | 94 | 95 | private bool InRange(int x, int y) 96 | { 97 | return x >= 0 && x < mSize && y >= 0 && y < mSize; 98 | } 99 | } 100 | 101 | #endregion 102 | 103 | 104 | #region Mesh相关 105 | 106 | public struct stVertexAtrribute 107 | { 108 | public Vector3 mVertice; 109 | public Vector2 mUV; 110 | public int mVerticeIdx; 111 | 112 | public stVertexAtrribute(int vertexIdx, Vector3 vertex, Vector2 uv) 113 | { 114 | mVerticeIdx = vertexIdx; 115 | mVertice = vertex; 116 | mUV = uv; 117 | } 118 | 119 | public stVertexAtrribute Clone() 120 | { 121 | return new stVertexAtrribute(mVerticeIdx, mVertice, mUV); 122 | } 123 | 124 | } 125 | 126 | 127 | 128 | public struct stTerrainMeshData 129 | { 130 | public Mesh mMesh; 131 | public Vector3[] mVertices; 132 | public Vector2[] mUV; 133 | public Vector3[] mNormals; 134 | public int[] mTriangles; 135 | 136 | 137 | private int mTriIdx; 138 | 139 | 140 | public void RenderVertex( 141 | int idx, 142 | Vector3 vertex, 143 | Vector3 uv 144 | ) 145 | { 146 | mVertices[idx] = vertex; 147 | mUV[idx] = uv; 148 | mTriangles[mTriIdx++] = idx; 149 | } 150 | 151 | 152 | public void RenderTriangle( 153 | stVertexAtrribute a, 154 | stVertexAtrribute b, 155 | stVertexAtrribute c 156 | ) 157 | { 158 | RenderVertex(a.mVerticeIdx, a.mVertice, a.mUV); 159 | RenderVertex(b.mVerticeIdx, b.mVertice, b.mUV); 160 | RenderVertex(c.mVerticeIdx, c.mVertice, c.mUV); 161 | } 162 | 163 | 164 | public void Present() 165 | { 166 | if (mMesh != null) 167 | { 168 | mMesh.vertices = mVertices; 169 | mMesh.uv = mUV; 170 | mMesh.triangles = mTriangles; 171 | mMesh.normals = mNormals; 172 | } 173 | } 174 | 175 | 176 | public void Reset() 177 | { 178 | if (mVertices != null) 179 | { 180 | for (int i = 0; i < mVertices.Length; ++i) 181 | { 182 | mVertices[i].x = mVertices[i].y = mVertices[i].z = 0; 183 | if (mUV != null) 184 | { 185 | mUV[i].x = mUV[i].y = 0; 186 | } 187 | if (mNormals != null) 188 | { 189 | mNormals[i].x = mNormals[i].y = 0; 190 | } 191 | } 192 | } 193 | 194 | mTriIdx = 0; 195 | if (mTriangles != null) 196 | { 197 | for (int i = 0; i < mTriangles.Length; ++i) 198 | { 199 | mTriangles[i] = 0; 200 | } 201 | } 202 | 203 | } 204 | 205 | } 206 | 207 | 208 | #endregion 209 | 210 | 211 | #region 粗糙度数据 212 | public struct stRoughnessData 213 | { 214 | private float[,] mRoughnessData; 215 | public int mSize; 216 | 217 | public bool IsValid() 218 | { 219 | return mRoughnessData != null; 220 | } 221 | 222 | public void Release() 223 | { 224 | mRoughnessData = null; 225 | mSize = 0; 226 | } 227 | 228 | 229 | public void Allocate(int mapSize) 230 | { 231 | if (mapSize > 0) 232 | { 233 | mRoughnessData = new float[mapSize, mapSize]; 234 | mSize = mapSize; 235 | } 236 | } 237 | 238 | public void SetRoughnessValue(float value, int x, int y) 239 | { 240 | if (IsValid() && InRange(x, y)) 241 | { 242 | mRoughnessData[x, y] = value; 243 | } 244 | } 245 | 246 | public float GetRoughnessValue(int x, int y) 247 | { 248 | float ret = 0; 249 | if (IsValid() && InRange(x, y)) 250 | { 251 | ret = mRoughnessData[x, y]; 252 | } 253 | return ret; 254 | } 255 | 256 | 257 | private bool InRange(int x, int y) 258 | { 259 | return x >= 0 && x < mSize && y >= 0 && y < mSize; 260 | } 261 | 262 | public void Reset(float value = 1.0f) 263 | { 264 | if (IsValid()) 265 | { 266 | for (int z = 0; z < mSize; ++z) 267 | { 268 | for (int x = 0; x < mSize; ++x) 269 | { 270 | mRoughnessData[x, z] = value; 271 | } 272 | } 273 | } 274 | } 275 | } 276 | 277 | #endregion 278 | } 279 | -------------------------------------------------------------------------------- /Assets/Scripts/Common/CCommon.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c7839c5c4a97a724d924cdddbedd84b6 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/Scripts/DemoFramework.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using Assets.Scripts.QuadTree; 3 | using Assets.Scripts.Common; 4 | 5 | 6 | public class DemoFramework : MonoBehaviour { 7 | 8 | #region 输入操作 9 | public float movementSpeed = 1f; 10 | public float mouseSensitive = 1f; 11 | 12 | public float mouseScrollSensitive = 1.0f; 13 | 14 | private RenderInWireframe mWireFrameCtrl; 15 | 16 | #endregion 17 | 18 | 19 | public bool renderQuadTreeCLOD = false; 20 | public bool useRounghnessEvaluate = false; 21 | 22 | #region Gizmos 23 | public bool drawGizmos = false; 24 | public float gizmosScale = 10f; 25 | public Color gizmosColor = Color.green; 26 | #endregion 27 | 28 | 29 | //摄像机对象 30 | public GameObject cameraGo; 31 | public Camera renderCamera; 32 | //地形对象 33 | public GameObject terrainGo; 34 | 35 | //顶点间的距离 36 | public Vector3 vertexScale; 37 | 38 | //高度图的边长,也就是结点的个数 39 | public int heightSize; 40 | 41 | //是否从高度图读取高度信息 42 | //True从文件读取 43 | //False动态生成 44 | public bool isLoadHeightDataFromFile; 45 | public string heightFileName; 46 | 47 | public bool isGenerateHeightDataRuntime; 48 | public int iterations; 49 | [Range(0, 255)] 50 | public int minHeightValue; 51 | [Range(0, 65536)] 52 | public int maxHeightValue; 53 | [Range(0, 0.9f)] 54 | public float filter; 55 | 56 | 57 | public float desiredResolution = 50f; 58 | public float minResolution = 10f; 59 | 60 | 61 | #region 地图Tile 62 | public Texture2D detailTexture; 63 | 64 | [Range(1, 2048)] 65 | public int terrainTextureSize = 256; 66 | public Texture2D[] tiles; 67 | 68 | #endregion 69 | 70 | 71 | 72 | private CQuadTreeTerrain mQuadTreeTerrain; 73 | 74 | 75 | 76 | #region 顶点数据放里 77 | 78 | private stTerrainMeshData mMeshData; 79 | 80 | #endregion 81 | 82 | 83 | //1、读取高度图, 84 | //2、设置顶点间距离, 85 | //3、读取纹理 86 | //4、设置光照阴影 87 | void Start() 88 | { 89 | InitMeshData(); 90 | InitRenderMode(); 91 | 92 | mQuadTreeTerrain = new CQuadTreeTerrain(); 93 | mQuadTreeTerrain.GenerateNodes(heightSize); 94 | //制造高度图 95 | mQuadTreeTerrain.MakeTerrainFault(heightSize, iterations, (ushort)minHeightValue, (ushort)maxHeightValue, filter); 96 | mQuadTreeTerrain.MakeRounessData(heightSize); 97 | 98 | //设置对应的纹理块 99 | AddTile(enTileTypes.lowest_tile); 100 | AddTile(enTileTypes.low_tile); 101 | AddTile(enTileTypes.high_tile); 102 | AddTile(enTileTypes.highest_tile); 103 | mQuadTreeTerrain.GenerateTextureMap((uint)terrainTextureSize, (ushort)maxHeightValue, (ushort)minHeightValue); 104 | ApplyTerrainTexture(mQuadTreeTerrain.TerrainTexture); 105 | 106 | } 107 | 108 | 109 | #region 地图块操作 110 | 111 | 112 | private void ApplyTerrainTexture(Texture2D texture) 113 | { 114 | if (terrainGo != null) 115 | { 116 | MeshRenderer meshRender = terrainGo.GetComponent(); 117 | if (meshRender != null) 118 | { 119 | Shader terrainShader = Shader.Find("Terrain/QuadTree/TerrainRender"); 120 | if (terrainShader != null) 121 | { 122 | meshRender.material = new Material(terrainShader); 123 | if (meshRender.material != null) 124 | { 125 | meshRender.material.SetTexture("_MainTex", texture); 126 | if (detailTexture != null) 127 | { 128 | meshRender.material.SetTexture("_DetailTex", detailTexture); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | } 135 | 136 | 137 | void AddTile(enTileTypes tileType) 138 | { 139 | int tileIdx = (int)tileType; 140 | if (tileIdx < tiles.Length 141 | && tiles[tileIdx] != null) 142 | { 143 | mQuadTreeTerrain.AddTile((enTileTypes)tileIdx, tiles[tileIdx]); 144 | } 145 | } 146 | 147 | 148 | #endregion 149 | 150 | 151 | #region 输入操作 152 | 153 | public void DemoInput() 154 | { 155 | if (Input.GetKeyDown(KeyCode.Escape)) 156 | { 157 | Application.Quit(); 158 | } 159 | 160 | if (cameraGo != null 161 | && renderCamera) 162 | { 163 | //鼠标操作 164 | 165 | // 滚轮实现镜头缩进和拉远 166 | if (Input.GetAxis("Mouse ScrollWheel") != 0) 167 | { 168 | renderCamera.fieldOfView = renderCamera.fieldOfView - Input.GetAxis("Mouse ScrollWheel") * mouseScrollSensitive; 169 | renderCamera.fieldOfView = Mathf.Clamp(renderCamera.fieldOfView, renderCamera.nearClipPlane, renderCamera.farClipPlane); 170 | } 171 | //鼠标右键实现视角转动,类似第一人称视角 172 | if (Input.GetMouseButton(0)) 173 | { 174 | float rotationX = Input.GetAxis("Mouse X") * mouseSensitive; 175 | float rotationY = Input.GetAxis("Mouse Y") * mouseSensitive; 176 | cameraGo.transform.Rotate(-rotationY, rotationX, 0); 177 | } 178 | 179 | 180 | //键盘操作 181 | if (Input.GetKey(KeyCode.UpArrow)) 182 | { 183 | cameraGo.transform.Translate(transform.forward * movementSpeed, Space.Self); 184 | } 185 | if (Input.GetKey(KeyCode.DownArrow)) 186 | { 187 | cameraGo.transform.Translate(transform.forward * movementSpeed * -1, Space.Self); 188 | } 189 | if (Input.GetKey(KeyCode.LeftArrow)) 190 | { 191 | cameraGo.transform.Translate(transform.right * movementSpeed * -1, Space.Self); 192 | } 193 | if (Input.GetKey(KeyCode.RightArrow)) 194 | { 195 | cameraGo.transform.Translate(transform.right * movementSpeed, Space.Self); 196 | } 197 | if (Input.GetKeyDown(KeyCode.W)) 198 | { 199 | mWireFrameCtrl.wireframeMode = !mWireFrameCtrl.wireframeMode; 200 | } 201 | if( Input.GetKeyDown(KeyCode.S)) 202 | { 203 | renderQuadTreeCLOD = !renderQuadTreeCLOD; 204 | } 205 | if (Input.GetKeyDown(KeyCode.G)) 206 | { 207 | drawGizmos = !drawGizmos; 208 | } 209 | if( Input.GetKeyDown(KeyCode.R)) 210 | { 211 | useRounghnessEvaluate = !useRounghnessEvaluate; 212 | } 213 | } 214 | } 215 | 216 | #endregion 217 | 218 | 219 | 220 | #region 更新及渲染 221 | public void DemoRender() 222 | { 223 | if (mQuadTreeTerrain != null) 224 | { 225 | 226 | 227 | if( renderQuadTreeCLOD ) 228 | { 229 | Profiler.BeginSample("QuadTree.FirstRefineNode"); 230 | float fCenter = (heightSize - 1) >> 1; 231 | mQuadTreeTerrain.RefineNode(fCenter, fCenter, heightSize, renderCamera, vertexScale, desiredResolution, minResolution , useRounghnessEvaluate); 232 | Profiler.EndSample(); 233 | 234 | Profiler.BeginSample("QuadTree.Render"); 235 | mQuadTreeTerrain.CLOD_Render(ref mMeshData, vertexScale); 236 | Profiler.EndSample(); 237 | } 238 | else 239 | { 240 | Profiler.BeginSample("Normla.Render"); 241 | mQuadTreeTerrain.Render(ref mMeshData, vertexScale); 242 | Profiler.EndSample(); 243 | } 244 | 245 | } 246 | } 247 | 248 | public void InitMeshData() 249 | { 250 | if (null == terrainGo) 251 | { 252 | Debug.LogError("Terrain GameObject is Null"); 253 | return; 254 | } 255 | 256 | MeshFilter meshFilter = terrainGo.GetComponent(); 257 | if (null == meshFilter) 258 | { 259 | Debug.LogError("Terrain without Comp [MeshFilter]"); 260 | return; 261 | } 262 | 263 | if (meshFilter.mesh == null) 264 | { 265 | meshFilter.mesh = new Mesh(); 266 | } 267 | 268 | 269 | 270 | int vertexCnt = heightSize * heightSize; 271 | int trianglesCnt = (heightSize - 1) * (heightSize - 1) * 6; 272 | mMeshData.mVertices = new Vector3[vertexCnt]; 273 | mMeshData.mUV = new Vector2[vertexCnt]; 274 | mMeshData.mNormals = new Vector3[vertexCnt]; 275 | mMeshData.mTriangles = new int[trianglesCnt]; 276 | mMeshData.mMesh = meshFilter.mesh; 277 | } 278 | 279 | 280 | 281 | public void InitRenderMode() 282 | { 283 | if (cameraGo != null) 284 | { 285 | mWireFrameCtrl = cameraGo.GetComponent(); 286 | } 287 | } 288 | 289 | #endregion 290 | 291 | 292 | // Update is called once per frame 293 | void Update () 294 | { 295 | Profiler.BeginSample("DemoInput"); 296 | DemoInput(); 297 | Profiler.EndSample(); 298 | 299 | Profiler.BeginSample("DemoRender"); 300 | DemoRender(); 301 | Profiler.EndSample(); 302 | } 303 | 304 | private void OnDrawGizmos() 305 | { 306 | if (mQuadTreeTerrain != null 307 | && drawGizmos) 308 | { 309 | Profiler.BeginSample("DrawGizmos"); 310 | mQuadTreeTerrain.DrawGizoms(ref mMeshData, vertexScale, gizmosScale, gizmosColor); 311 | Profiler.EndSample(); 312 | } 313 | } 314 | 315 | } 316 | -------------------------------------------------------------------------------- /Assets/Scripts/DemoFramework.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66ebadbbd57728c43bfdf47856123a45 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/Scripts/RenderInWireframe.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | public class RenderInWireframe : MonoBehaviour 5 | { 6 | public bool wireframeMode = false; 7 | 8 | void OnPreRender() 9 | { 10 | GL.wireframe = wireframeMode; 11 | } 12 | void OnPostRender() 13 | { 14 | GL.wireframe = wireframeMode; 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Assets/Scripts/RenderInWireframe.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b2d8b80c5ad83684688f131b0be76222 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/Scripts/Utility.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fc48adf9267faa448a59b9b03a219444 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/Scripts/Utility/CUtility.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | 4 | namespace Assets.Scripts.Utility 5 | { 6 | public class CUtility 7 | { 8 | public static void SetTextureReadble(Texture2D tex, bool bReadble) 9 | { 10 | if (tex != null) 11 | { 12 | TextureImporterSettings settings = GetTextureImporterSettings(tex); 13 | if (settings != null) 14 | { 15 | settings.readable = bReadble; 16 | SetTextureImporterSettings(tex, settings); 17 | } 18 | } 19 | } 20 | 21 | 22 | public static void SetTextureImporterSettings(Texture2D tex, TextureImporterSettings settings) 23 | { 24 | if (tex != null 25 | && settings != null) 26 | { 27 | TextureImporter importer = GetTextureImporter(tex); 28 | if (importer != null) 29 | { 30 | importer.SetTextureSettings(settings); 31 | 32 | string assetPath = AssetDatabase.GetAssetPath(tex); 33 | AssetDatabase.ImportAsset(assetPath, ImportAssetOptions.ForceSynchronousImport | ImportAssetOptions.ForceUpdate); 34 | } 35 | } 36 | } 37 | 38 | 39 | public static TextureImporterSettings GetTextureImporterSettings(Texture2D tex) 40 | { 41 | TextureImporterSettings settings = null; 42 | if (tex != null) 43 | { 44 | TextureImporter importer = GetTextureImporter(tex); 45 | if (importer != null) 46 | { 47 | settings = new TextureImporterSettings(); 48 | importer.ReadTextureSettings(settings); 49 | } 50 | } 51 | return settings; 52 | } 53 | 54 | public static TextureImporter GetTextureImporter(Texture2D texture) 55 | { 56 | string assetPath = AssetDatabase.GetAssetPath(texture); 57 | return AssetImporter.GetAtPath(assetPath) as TextureImporter; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Assets/Scripts/Utility/CUtility.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 34647050babe0664493d32a27561a5be 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Backup/Scripts_Roughess.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seed-XL/QuadTreeTerrainDemo_Unity/fe394574a3fb7c76328f788da23d4b72b6d8c13d/Backup/Scripts_Roughess.rar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuadTreeTerrainDemo_Unity 2 | 3 | ### 目的:尝试实现一个地形LOD算法中的QuadTree 4 |   5 | #### 目前借鉴《Focus On 3D Terrain Programming》书上的代码实现了。 6 | 1.随机生成高度图 7 | 8 | 2.混合纹理的功能 9 | 10 | 3.四叉树动态生成LOD(T型接缝的问题已经解决,但是划分四叉树的算法看起来效果不太对)。 11 | 12 | #### 下一步 13 | 1.调整四叉树划分的效果 14 | 15 | 2.加入视野裁剪的功 16 | 17 | 3.还有读取高度图生成Mesh的功能。 18 | 19 | 4.加入光照、阴影。 20 |   21 | #### 效果Gif 22 | ![效果Gif](http://wx3.sinaimg.cn/mw690/6b98bc8agy1flt2fqpnqtg20fk06o4ja.gif) 23 | 24 | 25 | 26 | 27 | --------------------------------------------------------------------------------