Code archives/3D Graphics - Misc/Realtime Procedural Planet Generator
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
This is my own planet texture generator. It is a fake, but a very fast one with nice results. It calculates a unique texture by blending 4 pre-rendered 3D Perlin Noise planets together using image filters. And it is done within the main loop and calculates only a few pixels per loop, so it consumes nearly no CPU power and the planet can be calculated on-the-fly, e.g. while moving toward a planet or a solar system. In this demo, i am using a 512x256 texture, but it is even possible to create 1024x512 or 2048x1024 textures in realtime (the pre-rendering takes a lot of time but it is worth waiting for the results!). The planet texture maps perfect on a sphere because it is distorted to the poles. In the demo, press SPACE to calculate a new surface texture and use the mouse and the arrow keys to move while it is forming. IMPORTANT: first you must create the pre-rendered planet images by using this code (execute it in the same directory as the planet generator code): prepareplanet.bb | |||||
; Realtime Procedural Planet Generator ; by Krischan webmaster(at)jaas.de ; ; creates a planet out of a combination of 4 pre-rendered planets out of 10 and three blending filters ; it is possible to create ten thousands of unique planet surfaces in 50-100 milliseconds! ; this is done by calculating the planet step by step so it doesn't consume lots of power and whole ; solar systems can be created while moving towards the system or even the planet Graphics3D 800,600,32,2 ; Declarations Dim GradientR%(0),GradientG%(0),GradientB%(0),Prozent%(0),Rot%(0),Gruen%(0),Blau%(0) Dim images%(9) Dim Array%(0) Global maxx%=512 Global maxy%=256 Global max%=maxx*maxy Global pixelstep%=16384 Global movespeed#=0.1 ; Randomize SeedRnd MilliSecs() ; Create Color Gradient Restore ClassMT : CreateGradient(9,255) ; Read source images For i%=0 To 9 : images(i)=LoadBankImage("planet"+i+".img",maxx,maxy) : Next ; Create random array CreateRandomArray(max) ; Create target texture output=CreateTexture(maxx,maxy,16+32) texbuff=TextureBuffer(output) ; Base color of texture (water) SetBuffer texbuff Color 17, 82,112 Rect 0,0,maxx,maxy,1 Color 255,255,255 SetBuffer BackBuffer() ; Planet planet=CreateSphere(32) ScaleEntity planet,10,10,10 PositionEntity planet,0,0,10 EntityFX planet,2 EntityTexture planet,output,0,1 TextureBlend output,2 EntityShininess planet,0.25 ; Light light=CreateLight(1) AmbientLight 8,8,8 ; Camera camera=CreateCamera() PositionEntity camera,20,0,0 CameraRange camera,0.1,1000 ; Cursor centered, cam points to planet MoveMouse GraphicsWidth()/2,GraphicsHeight()/2 HidePointer() PointEntity camera,planet ; World creation flag newworld=True ; Time measurement start time=MilliSecs() ; Main Loop While Not KeyHit(1) ; FPS measurement FPS_C=FPS_C+1 : If fms<MilliSecs() Then fms=MilliSecs()+1000 : FPS=FPS_C : FPS_C=0 ; Frame tweening Tween#=Float(MilliSecs()-FrameTime)/10.0 : FrameTime=MilliSecs() ; Simple Steering mxs#=MouseXSpeed() mys#=MouseYSpeed() RotateEntity camera,EntityPitch(camera)+(mys#/5),EntityYaw(camera)-(mxs#/5),0 If KeyDown(200) Then MoveEntity camera,0,0,movespeed*Tween If KeyDown(208) Then MoveEntity camera,0,0,-movespeed*Tween MoveMouse GraphicsWidth()/2,GraphicsHeight()/2 ; Current time ms=MilliSecs() ; SPACE and Newworld-Flag False? create a new map! If KeyHit(57) Then ; Measure start time starttime=ms ; Reset pixel counters i=0 water%=0 land%=0 ; Select 4 planet maps as base map$=ZERO(Rand(0,9999),4) ; Extract Planet Codes i1=Int(Left(map,1)) i2=Int(Mid(map,1,1)) i2=Int(Mid(map,2,1)) i4=Int(Right(map,1)) ; set Newworld-Flag True newworld=True EndIf ; Turn Planet and Clouds a little bit TurnEntity planet,0,-0.1*Tween,0 ; SPACE, 30ms gone and Newworld-Flag true? go on! If ms>time And newworld Then roundstart=ms ; Increase time time=ms+30 ; Calculate number of pixels to set adder%=(pixelstep/Tween) ; Start/End for current loop start=i ende=i+adder ; End greater than pixels? end=pixels If ende>max-1 Then ende=max-1 ; Lock Texturebuffer LockBuffer texbuff ; Current Pixel loop For j=start To ende ; Pixelincrement i=i+1 ; Pixel greater than pixel amount? End! If i>max-1 Then newworld=False Goto skip EndIf ; Calculate Pixel X/Y-Position from the randomized array y=Int(Array(i)/maxx) x=Array(i)-(y*maxx) ; get current pixels from bank offset=(y*maxx)+x r1=PeekByte(images(i1),offset) r2=PeekByte(images(i2),offset) r3=PeekByte(images(i3),offset) r4=PeekByte(images(i4),offset) ; Mix with Image Filter r=Average(r1,r2) r=Lighten(r,r3) r=HardLight(r,r4) ; Legend of usesful Filter combinations: ; ; S = Softlight ; M = Multiply ; L = Lighten ; A = Average ; H = Hardlight ; O = Overlay ; E = exclusion ; D = Difference ; N = Negation ; ; SSM = ~ 99% Land ; SSS = 25-75% Land ; SSL = 75-25% Land, flat ; ALH = 35-65% Land, continental ; AMM = 100% Land, mountaineous ; OLS = 25-75% Land ; OLC = <10% Land, oceancic ; MMM = 100% Land, snowy mountains ; EDN = different, earthlike, Archipelagos ; MNS = different, earthlike, Archipelagos ; Above 128: below water, otherwise Land If r>=128 Then water=water+1 Else land=land+1 ; Target Color from Gradient rgb=GradientR(r)*$10000+GradientG(r)*$100+GradientB(r) ; Write to Texturebuffer WritePixelFast x,y,rgb,texbuff Next .skip endtime=MilliSecs() ; Unlock Texturebuffer UnlockBuffer texbuff ; Calculate amount of used time midtime=(midtime+(endtime-roundstart))/2.0 EndIf RenderWorld ; Statistics Text 0, 0,"Planet Source Maps: "+map Text 0, 15,"Pixels blended....: "+(i*100)/max+"%" Text 0, 30,"Transition Time...: "+(endtime-starttime)+"ms" Text 0, 45,"Used ms per cycle.: "+midtime+"ms" Text 0, 60,"Pixels per cycle..: "+adder Text 0, 75,"Water coverage....: "+(water*100.0)/max+"%" Text 0, 90,"Land coverage.....: "+(land*100.0)/max+"%" Text 0,105,"FPS...............: "+FPS Text 0,120,"Tris rendered.....: "+TrisRendered() Flip 0 Wend End ; Soft Light Filter Function SoftLight(a%,b%) Local c% c=a*b Shr 8 Return (c+a*(255-((255-a)*(255-b) Shr 8)-c) Shr 8) End Function ; Hard Light Filter Function HardLight(a%,b%) If b<128 Then Return (a*b) Shr 7 Else Return 255-((255-b)*(255-a) Shr 7) End Function ; Difference Filter Function Difference(a%,b%) Return Abs(a-b) End Function ; Multiply Filter Function Multiply(a%,b%) Return (a*b) Shr 8 End Function ; Average Filter Function Average(a%,b%) Return (a+b) Shr 1 End Function ; Screen Filter Function Screen(a%,b%) Return 255-((255-a)*(255-b) Shr 8) End Function ; Lighten Filter Function Lighten(a%,b%) If a>b Then Return a Else Return b End Function ; Darken Filter Function Darken(a%,b%) If a<b Then Return a Else Return b End Function ; Negative Filter Function Negation(a%,b%) Return 255-Abs(255-a-b) End Function ; Exclusion Filter Function Exclusion(a%,b%) Return a+b-(a*b Shr 7) End Function ; Overlay Filter Function Overlay(a%,b%) If a<128 Then Return (a*b) Shr 7 Else Return 255-((255-a)*(255-b) Shr 7) End Function ; Color Burn Filter Function ColorDodge(a%,b%) If b=255 Then Return 255 Else Local c%=Floor((a Shl 8)/(255-b)) If c>255 Then Return 255 Else Return c EndIf End Function ; Create Gradient Function CreateGradient(colors%,steps%) Dim GradientR%(steps),GradientG%(steps),GradientB%(steps),Prozent%(colors),Rot%(colors),Gruen%(colors),Blau%(colors) Local i%,pos1%,pos2%,pdiff% Local rdiff%,gdiff%,bdiff% Local rstep#,gstep#,bstep# Local counter%=1 ; read color codes For i=1 To colors : Read Prozent(i),Rot(i),Gruen(i),Blau(i) : Next ; calculate gradient While counter<colors ; transform percent value into step position pos1=Prozent(counter)*steps/100 pos2=Prozent(counter+1)*steps/100 ; calculate position difference pdiff=pos2-pos1 ; calculate color difference rdiff%=Rot(counter)-Rot(counter+1) gdiff%=Gruen(counter)-Gruen(counter+1) bdiff%=Blau(counter)-Blau(counter+1) ; calculate color steps rstep#=rdiff*1.0/pdiff gstep#=gdiff*1.0/pdiff bstep#=bdiff*1.0/pdiff ; calculate "in-between" color codes For i=0 To pdiff GradientR(pos1+i)=Int(Rot(counter)-(rstep*i)) GradientG(pos1+i)=Int(Gruen(counter)-(gstep*i)) GradientB(pos1+i)=Int(Blau(counter)-(bstep*i)) Next ; increment counter counter=counter+1 Wend End Function ; create a random array where each value appears only once Function CreateRandomArray(size%) ; Redim Array Dim Array(size) ; Fill with values For i = 0 To size-1 : Array(i) = i : Next ; play dice For N% = 0 To size-2 M% = Rand( N%, size - 1) Z% = Array(N%) Array(N%) = Array(M%) Array(M%) = Z% Next End Function ; Fill a String with prefix Zeros Function ZERO$(number%,lenght%=2) Local r$="" For i=1 To lenght-Len(Str(number)) r$=r$+"0" Next Return r$+Str(number) End Function ; Load a raw Image Function LoadBankImage(filename$,width%,height%) Local f%=OpenFile(filename$) Local bank%=CreateBank(width*height) ReadBytes(bank,f,0,BankSize(bank)) CloseFile f Return bank End Function ; Color codes for an earthlike Planet .ClassMT Data 0,255,255,255 Data 5,179,179,179 Data 10,153,143, 92 Data 25,115,128, 77 Data 48, 42,102, 41 Data 50, 69,108,118 Data 52, 17, 82,112 Data 75, 9, 62, 92 Data 100, 2, 43, 68 |
Comments
| ||
Very nice, thank you. One problem though, rather than increase in size and maintain a good level of detail, the larger images (2048x1024) seem to be very pixelated combined with a blur filter? |
| ||
Hi Ian, sorry I forgot to mention. In my example with 512x256 i am using 6 noise octaves which is sufficient there. In higher resolutions you should increase the octaves, too. Just search for this line: ; get the perlin result For that part h = DBInt(perl(x#,y#,z#,6) * 255) and change it to ; get the perlin result For that part h = DBInt(perl(x#,y#,z#,7) * 255) Or use a variable placeholder for that. 7 looks nice, if you want more detail use higher dimensions. For my purpose, 1024x512 is adequate. You can play with the pre-rendered results by using more or less octaves or change the crispness, which you can switch in this line: prepare_perlin(planet+1,0.75) The first variable is the seed (here planet+1), the second the crispness, 0.0 is totally blurry and 1.0 is very sharp. It even has multisampling (variable multi%=1), but I don't use it because it increases the calculation time vastly. Hint: if you're going to use 2048x1024 or even higher texture levels you should decrease the pixelstep% variable in the demo from 16384 to e.g. 4096 or lower. Although it takes more time to "pixelate" the result but uses less CPU power then. |
| ||
Ah I see, thanks for that Christian. I really like the last few code archive posts you have made, all interesting and unique, well done. :) |
| ||
can you tell me how you are distorting the pole? I made a random map generator using the divided fault method but it doesn't distort the poles or blend the edges to match the other side. Very nice, CTP |
| ||
I only ported the code to BB, dunno exactly how it works. I only found out that it creates a 3D Perlin Noise point cloud and the sphere gets cut off like the skin of an orange out of this cloud, imagine the cloud like that:![]() |
| ||
I know I'm a bit late... but that is absolutely awesome. |
Code Archives Forum