Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6996
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Beitrag von STARGÅTE »

Hallo Leute,

hier mal eine kleine Spielerei zur Mandelbrot- und Juliamenge.
Mit zwei OpenGLGadgets kann man sich in der Mandelbrot mit Maus und Mausrad bewegen und sich parallel dazu die Juliamenge des jeweiligen Orts angucken. Dazu habe ich einen Fragment Shader geschrieben der die Iterationen durchführt und das sogar/immerhin mit double-Genauigkeit.
Bild

Code: Alles auswählen

EnableExplicit


;{ OpenGL
;  https://www.purebasic.fr/english/viewtopic.php?p=576628#p576628

#GL_VERTEX_SHADER = $8B31
#GL_FRAGMENT_SHADER = $8B30

Prototype glCreateShader(type.l)
Prototype glCreateProgram()
Prototype glDeleteShader(shader.l)
Prototype glCompileShader(shader.l)
Prototype glLinkProgram(shader.l)
Prototype glUseProgram(shader.l)
Prototype glAttachShader(Program.l, shader.l)
Prototype glShaderSource(shader.l, numOfStrings.l, *strings, *lenOfStrings) : 
Prototype glGetUniformLocation(Program.i, name.p-ascii)
Prototype glUniform1i(location.i, v0.i)
Prototype glUniform2i(location.i, v0.i, v1.i)
Prototype glUniform1f(location.i, v0.f)
Prototype glUniform1d(location.i, v0.d)
Prototype glUniform2f(location.i, v0.f, v1.f)
Prototype glUniform2d(location.i, v0.d, v1.d)
Prototype glGetShaderInfoLog(shader.i, bufSize.l, *length_l, *infoLog)


Procedure InitOpenGL()
	
	Global glCreateShader.glCreateShader             = wglGetProcAddress_("glCreateShader")
	Global glCreateProgram.glCreateProgram           = wglGetProcAddress_("glCreateProgram")
	Global glDeleteShader.glDeleteShader             = wglGetProcAddress_("glDeleteShader")
	Global glCompileShader.glCompileShader           = wglGetProcAddress_("glCompileShader")
	Global glLinkProgram.glLinkProgram               = wglGetProcAddress_("glLinkProgram")
	Global glUseProgram.glUseProgram                 = wglGetProcAddress_("glUseProgram")
	Global glAttachShader.glAttachShader             = wglGetProcAddress_("glAttachShader")
	Global glShaderSource.glShaderSource             = wglGetProcAddress_("glShaderSource")
	Global glGetUniformLocation.glGetUniformLocation = wglGetProcAddress_("glGetUniformLocation")
	Global glUniform1i.glUniform1i                   = wglGetProcAddress_("glUniform1i")
	Global glUniform2i.glUniform2i                   = wglGetProcAddress_("glUniform2i")
	Global glUniform1f.glUniform1f                   = wglGetProcAddress_("glUniform1f")
	Global glUniform1d.glUniform1d                   = wglGetProcAddress_("glUniform1d")
	Global glUniform2f.glUniform2f                   = wglGetProcAddress_("glUniform2f")
	Global glUniform2d.glUniform2d                   = wglGetProcAddress_("glUniform2d")
	Global glGetShaderInfoLog.glGetShaderInfoLog     = wglGetProcAddress_("glGetShaderInfoLog")
	
EndProcedure

;}


;{ Renderer

Structure Uniform
	Mode.i
	Iterations.i
	Scale.i
	Position.i
	Origin.i
	Center.i
	Rotation.i
EndStructure

Structure Scene
	Mode.i
	Iterations.i
	Scale.d
	Rotation.f
	PositionX.d
	PositionY.d
	OriginX.d
	OriginY.d
	CenterX.i
	CenterY.i
EndStructure

Global Uniform.Uniform
Global Mandelbrot.Scene
Global Julia.Scene


Procedure Compile(Gadget.i)
	
	Protected VertexShader.i, VertexShaderCode.s
	Protected FragmentShader.i, FragmentShaderCode.s
	Protected Program.i
	Protected *Buffer, Length.i
	Protected ErrorText.s
	
	VertexShaderCode = "#version 410" + #LF$ + 
	                   "in vec3 position;" + #LF$+
	                   "void main() {" + #LF$ + 
	                   "	gl_Position = vec4( position, 1.0 );" + #LF$ +
	                   "};"
	
	FragmentShaderCode = "#version 410" + #LF$ +
	                     "uniform int iterations;" + #LF$ +
	                     "uniform int mode;" + #LF$ +
	                     "uniform float rotation;" + #LF$ +
	                     "uniform double scale;" + #LF$ +
	                     "uniform dvec2 position;" + #LF$ +
	                     "uniform dvec2 origin;" + #LF$ +
	                     "uniform ivec2 center;" + #LF$ +
	                     "// Gradient Color" + #LF$ +
	                     "const int  gradientLength = 12;" + #LF$ +
	                     "const vec4 gradientColor[gradientLength] = vec4[gradientLength](" + #LF$ +
	                     "	vec4(0.000, 0.000, 0.000, 0.00000)," + #LF$ +
	                     "	vec4(0.125, 0.000, 0.250, 0.06250)," + #LF$ +
	                     "	vec4(0.500, 0.125, 0.250, 0.15625)," + #LF$ +
	                     "	vec4(0.875, 0.250, 0.000, 0.25000)," + #LF$ +
	                     "	vec4(1.000, 0.500, 0.000, 0.34375)," + #LF$ +
	                     "	vec4(1.000, 0.750, 0.000, 0.40625)," + #LF$ +
	                     "	vec4(1.000, 1.000, 1.000, 0.50000)," + #LF$ +
	                     "	vec4(0.625, 0.875, 0.000, 0.59375)," + #LF$ +
	                     "	vec4(0.250, 0.750, 0.000, 0.68750)," + #LF$ +
	                     "	vec4(0.000, 0.500, 0.625, 0.78125)," + #LF$ +
	                     "	vec4(0.000, 0.250, 0.500, 0.87500)," + #LF$ +
	                     "	vec4(0.000, 0.000, 0.000, 1.00000)" + #LF$ +
	                     ");" + #LF$ +
	                     "vec4 gradient( float value ) {" + #LF$ +
	                     "	" + #LF$ +
	                     "	for (int i=1; i<gradientLength; i++) {" + #LF$ +
	                     "		if (value < gradientColor[i].a)" + #LF$ +
	                     "			return vec4( mix( gradientColor[i-1].rgb, gradientColor[i].rgb, (value-gradientColor[i-1].a)/(gradientColor[i].a-gradientColor[i-1].a) ), 1.0); " + #LF$ +
	                     "	}" + #LF$ +
	                     "	" + #LF$ +
	                     "}" + #LF$ +
	                     "// Mandelbrot & Julia" + #LF$ +
	                     "dvec2 rotate( vec2 position, float angle ) {" + #LF$ +
	                     "	return dvec2(position.x*cos(angle)-position.y*sin(angle), position.x*sin(angle)+position.y*cos(angle));" + #LF$ +
	                     "};" + #LF$ +
	                     "void main( void ) {" + #LF$ +
	                     "	" + #LF$ +
	                     "	float x;" + #LF$ +
	                     "	dvec2 c;" + #LF$ +
	                     "	dvec2 z;" + #LF$ +
	                     "	" + #LF$ +
	                     "	if (mode == 0) {" + #LF$ +
	                     "		c = rotate(vec2(gl_FragCoord.x-center.x, gl_FragCoord.y-center.y), rotation) / (center.y*scale) + position;" + #LF$ +
	                     "		z = dvec2(0.0, 0.0);" + #LF$ +
	                     "	}" + #LF$ +
	                     "	else {" + #LF$ +
	                     "		c = origin;" + #LF$ +
	                     "		z = rotate(vec2(gl_FragCoord.x-center.x, gl_FragCoord.y-center.y), rotation) / (center.y*scale) + position;" + #LF$ +
	                     "	}" + #LF$ +
	                     "	dvec2 s;" + #LF$ +
	                     "	for (int i = 1; i<iterations; i++) {" + #LF$ +
	                     "		s = z*z;" + #LF$ +
	                     "		if (s.x+s.y > 64) {" + #LF$ +
	                     "			x = (i - log(log(float(s.x+s.y))*0.5*1.44269502162933)*1.44269502162933);" + #LF$ +
	                     "			//x = x*0.02;" + #LF$ +
	                     "			x = log(x+1)*sqrt(x)/50;" + #LF$ +
	                     "			break;" + #LF$ +
	                     "		}" + #LF$ +
	                     "		z = dvec2(s.x-s.y+c.x, 2.0*z.x*z.y+c.y);" + #LF$ +
	                     "	}" + #LF$ +
	                     "	gl_FragColor = gradient(mod(x, 1.0));" + #LF$ +
	                     "};"
	
	SetGadgetAttribute(Gadget, #PB_OpenGL_SetContext, #True)
	
	VertexShader = glCreateShader(#GL_VERTEX_SHADER)
	*Buffer = Ascii(VertexShaderCode)
	glShaderSource(VertexShader, 1, @*Buffer, #Null)
	glCompileShader(VertexShader)
	FreeMemory(*Buffer)
	*Buffer = AllocateMemory(1024)
	glGetShaderInfoLog(VertexShader, 1024, @Length, *Buffer)
	ErrorText + PeekS(*Buffer, Length, #PB_Ascii)
	FreeMemory(*Buffer)
	
	FragmentShader = glCreateShader(#GL_FRAGMENT_SHADER)
	*Buffer = Ascii(FragmentShaderCode)
	glShaderSource(FragmentShader, 1, @*Buffer, #Null)
	glCompileShader(FragmentShader)
	FreeMemory(*Buffer)
	*Buffer = AllocateMemory(1024)
	glGetShaderInfoLog(FragmentShader, 1024, @Length, *Buffer)
	ErrorText + PeekS(*Buffer, Length, #PB_Ascii)
	FreeMemory(*Buffer)
	
	Program = glCreateProgram()
	glAttachShader(Program, VertexShader)
	glAttachShader(Program, FragmentShader)
	glLinkProgram(Program)
	glUseProgram(Program)
	
	glDeleteShader(VertexShader)
	glDeleteShader(FragmentShader)
	
	With Uniform
		\Mode       = glGetUniformLocation(Program, "mode")
		\Iterations = glGetUniformLocation(Program, "iterations")
		\Scale      = glGetUniformLocation(Program, "scale")
		\Rotation   = glGetUniformLocation(Program, "rotation")
		\Position   = glGetUniformLocation(Program, "position")
		\Origin     = glGetUniformLocation(Program, "origin")
		\Center     = glGetUniformLocation(Program, "center")
	EndWith
	
	If ErrorText
		OpenConsole()
		PrintN(ErrorText)
	EndIf
	
EndProcedure


Procedure Render(Gadget.i)
	
	Protected *Scene.Scene = GetGadgetData(Gadget)
	
	SetGadgetAttribute(Gadget, #PB_OpenGL_SetContext, #True)
	
	glUniform1i(Uniform\Mode, *Scene\Mode)
	glUniform1i(Uniform\Iterations, *Scene\Iterations)
	glUniform1d(Uniform\Scale, *Scene\Scale)
	glUniform1f(Uniform\Rotation, *Scene\Rotation)
	glUniform2d(Uniform\Position, *Scene\PositionX, *Scene\PositionY)
	glUniform2d(Uniform\Origin, *Scene\OriginX, *Scene\OriginY)
	glUniform2i(Uniform\Center, *Scene\CenterX, *Scene\CenterY)
	
 	glBegin_(#GL_QUADS)
		glVertex2f_(-1,-1) 
		glVertex2f_( 1,-1) 
		glVertex2f_( 1, 1) 
		glVertex2f_(-1, 1) 
	glEnd_()           
	
	SetGadgetAttribute(Gadget, #PB_OpenGL_FlipBuffers, #True)
	
EndProcedure

;}


;{ Main Program

Enumeration
	#Window
	#File
	#Gadget_OpenGL_Mandelbrot
	#Gadget_OpenGL_Julia
	#Gadget_Editor
	#Gadget_Error
EndEnumeration


Procedure Callback_Size()
	
	SetGadgetAttribute(#Gadget_OpenGL_Julia, #PB_OpenGL_SetContext, #True)
	ResizeGadget(#Gadget_OpenGL_Julia, WindowWidth(#Window)/2, 0, WindowWidth(#Window)-WindowWidth(#Window)/2, WindowHeight(#Window))
	SetGadgetAttribute(#Gadget_OpenGL_Mandelbrot, #PB_OpenGL_SetContext, #True)
	ResizeGadget(#Gadget_OpenGL_Mandelbrot, 0, 0, WindowWidth(#Window)/2, WindowHeight(#Window))
	Mandelbrot\CenterX = GadgetWidth(#Gadget_OpenGL_Mandelbrot) / 2
	Mandelbrot\CenterY = GadgetHeight(#Gadget_OpenGL_Mandelbrot) / 2
	Julia\CenterX = GadgetWidth(#Gadget_OpenGL_Julia) / 2
	Julia\CenterY = GadgetHeight(#Gadget_OpenGL_Julia) / 2
	Render(#Gadget_OpenGL_Julia)
	Render(#Gadget_OpenGL_Mandelbrot)
	
EndProcedure


OpenWindow(#Window, 0, 0, 1600, 800, "Vector Canvas Gadget", #PB_Window_MaximizeGadget|#PB_Window_MaximizeGadget|#PB_Window_SizeGadget|#PB_Window_ScreenCentered)
OpenGLGadget(#Gadget_OpenGL_Mandelbrot, 0, 0, WindowWidth(#Window)/2, WindowHeight(#Window))
SetGadgetData(#Gadget_OpenGL_Mandelbrot, @Mandelbrot)
OpenGLGadget(#Gadget_OpenGL_Julia, WindowWidth(#Window)/2, 0, WindowWidth(#Window)/2, WindowHeight(#Window))
SetGadgetData(#Gadget_OpenGL_Julia, @Julia)

BindEvent(#PB_Event_SizeWindow, @Callback_Size(), #Window)

With Mandelbrot
	\Mode       = 0
	\Iterations = 512
	\PositionX  = -0.7
	\PositionY  = 0.0
	\Scale      = 0.5
	\Rotation   = 0.0
	\CenterX    = GadgetWidth(#Gadget_OpenGL_Mandelbrot) / 2
	\CenterY    = GadgetHeight(#Gadget_OpenGL_Mandelbrot) / 2
EndWith

With Julia
	\Mode       = 1
	\Iterations = 512
	\PositionX  = 0.0
	\PositionY  = 0.0
	\OriginX    = Mandelbrot\PositionX
	\OriginY    = Mandelbrot\PositionY
	\Scale      = 0.5
	\Rotation   = 0.0
	\CenterX    = GadgetWidth(#Gadget_OpenGL_Julia) / 2
	\CenterY    = GadgetHeight(#Gadget_OpenGL_Julia) / 2
EndWith

InitOpenGL()
Compile(#Gadget_OpenGL_Mandelbrot)
Compile(#Gadget_OpenGL_Julia)

Define Time.i, Text.s
Define *Scene.Scene
Define OldRotation.d, OldPositionX.d, OldPositionY.d, OldMouseX.i, OldMouseY.i
Define MouseX.i, MouseY.i

Repeat
	
	Select WaitWindowEvent(1)
		
		Case #PB_Event_CloseWindow
			Break
		Case #PB_Event_Gadget
			Select EventGadget()
				Case #Gadget_OpenGL_Mandelbrot, #Gadget_OpenGL_Julia
					*Scene = GetGadgetData(EventGadget())
					MouseX = GetGadgetAttribute(EventGadget(), #PB_OpenGL_MouseX)
					MouseY = GetGadgetAttribute(EventGadget(), #PB_OpenGL_MouseY)
					Select EventType()
						Case #PB_EventType_MouseWheel
							If GetGadgetAttribute(EventGadget(), #PB_OpenGL_Modifiers) & #PB_OpenGL_Control
								*Scene\Iterations * Pow(2, GetGadgetAttribute(EventGadget(), #PB_OpenGL_WheelDelta))
							Else
								*Scene\PositionX + (Cos(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Sin(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / *Scene\Scale/*Scene\CenterY
								*Scene\PositionY - (-Sin(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Cos(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / *Scene\Scale/*Scene\CenterY
								*Scene\Scale * Pow(2, GetGadgetAttribute(EventGadget(), #PB_OpenGL_WheelDelta)*0.25)
								*Scene\PositionX - (Cos(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Sin(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / *Scene\Scale/*Scene\CenterY
								*Scene\PositionY + (-Sin(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Cos(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / *Scene\Scale/*Scene\CenterY
							EndIf
						Case #PB_EventType_LeftButtonDown
							OldPositionX = *Scene\PositionX  :  OldPositionY = *Scene\PositionY
							OldMouseX = MouseX               :  OldMouseY = MouseY
						Case #PB_EventType_RightButtonDown
							OldRotation = ATan2(MouseX-*Scene\CenterX, MouseY-*Scene\CenterY) - *Scene\Rotation
							OldMouseX = MouseX               :  OldMouseY = MouseY
						Case #PB_EventType_LeftDoubleClick
							*Scene\PositionX + (Cos(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Sin(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / *Scene\Scale/*Scene\CenterY
							*Scene\PositionY - (-Sin(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Cos(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / *Scene\Scale/*Scene\CenterY
						Case #PB_EventType_MouseMove
							If GetGadgetAttribute(EventGadget(), #PB_OpenGL_Buttons) & #PB_OpenGL_LeftButton
								*Scene\PositionX = OldPositionX - (Cos(*Scene\Rotation)*(MouseX-OldMouseX) + Sin(*Scene\Rotation)*(MouseY-OldMouseY)) / *Scene\Scale/*Scene\CenterY
								*Scene\PositionY = OldPositionY + (-Sin(*Scene\Rotation)*(MouseX-OldMouseX) + Cos(*Scene\Rotation)*(MouseY-OldMouseY)) / *Scene\Scale/*Scene\CenterY
							ElseIf GetGadgetAttribute(EventGadget(), #PB_OpenGL_Buttons) & #PB_OpenGL_RightButton
								*Scene\Rotation  = ATan2(MouseX-*Scene\CenterX, MouseY-*Scene\CenterY) - OldRotation
							EndIf
							If EventGadget() = #Gadget_OpenGL_Mandelbrot
								Julia\OriginX = Mandelbrot\PositionX + (Cos(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Sin(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / Mandelbrot\Scale/*Scene\CenterY
								Julia\OriginY = Mandelbrot\PositionY - (-Sin(*Scene\Rotation)*(MouseX-*Scene\CenterX) + Cos(*Scene\Rotation)*(MouseY-*Scene\CenterY)) / Mandelbrot\Scale/*Scene\CenterY
							EndIf
						Case #PB_EventType_MouseLeave
							If EventGadget() = #Gadget_OpenGL_Mandelbrot
								Julia\OriginX = Mandelbrot\PositionX
								Julia\OriginY = Mandelbrot\PositionY
							EndIf
					EndSelect
				Case #Gadget_OpenGL_Julia
					Select EventType()
						Case #PB_EventType_MouseWheel
							Julia\Scale * Pow(2, GetGadgetAttribute(#Gadget_OpenGL_Julia, #PB_OpenGL_WheelDelta)*0.25)
					EndSelect
			EndSelect
		Case #PB_Event_None
			Time = ElapsedMilliseconds()
			Render(#Gadget_OpenGL_Mandelbrot)
			Render(#Gadget_OpenGL_Julia)
			Text = "Position: "+StrD(Julia\OriginX,17)+" + "+StrD(Julia\OriginY,17)+"i"
			Text + "  |  Iterations: "+Str(Mandelbrot\Iterations)+" ; "+Str(Julia\Iterations)
			Text + "  |  Render time: "+Str(ElapsedMilliseconds()-Time)+" ms"
			Text + "  |  Zoom: mouse wheel; move: left mouse button; rotate: right mouse button; iteration depth: STRG + mouse wheel; center view: double left click"
			SetWindowTitle(#Window, Text)
			
	EndSelect
	
ForEver

End

;}
Edit: Rotation mit rechter Maustaste hinzugefügt.

Insider: Theoretisch habe ich auch schon einen passenden Code geschrieben, um mit noch höherer Genauigkeit zu rechnen, allerdings bricht dann die Renderzeit selbst auf der GPU schnell zusammen wenn man sehr tief zoom und viele Iterationen hat. Somit ist dieses Beispiel nur mit Doubles.
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Lord
Beiträge: 313
Registriert: 21.01.2008 19:11

Re: Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Beitrag von Lord »

Hallo!

Schönes Programm für schöne Bilder, aber ...
... Zoom funktioniert bei mir nur, wenn ich beide OpenGLGadget() mit #PB_OpenGL_Keyboard öffne.
Bild
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8675
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Beitrag von NicTheQuick »

Funktioniert bei mir nicht. wglGetProcAddress_() ist anscheinend eine Windows-API.
Bild
Benutzeravatar
STARGÅTE
Kommando SG1
Beiträge: 6996
Registriert: 01.11.2005 13:34
Wohnort: Glienicke
Kontaktdaten:

Re: Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Beitrag von STARGÅTE »

Hallo Nic,
guck mal ob dieser Code hier funktioniert: https://www.purebasic.fr/english/viewto ... 73#p577873
PB 6.01 ― Win 10, 21H2 ― Ryzen 9 3900X, 32 GB ― NVIDIA GeForce RTX 3080 ― Vivaldi 6.0 ― www.unionbytes.de
Aktuelles Projekt: Lizard - Skriptsprache für symbolische Berechnungen und mehr
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8675
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Re: Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Beitrag von NicTheQuick »

Jup, der tut einwandfrei. :allright:
Bild
Antworten