Seite 1 von 1

Mandelbrot- und Juliamenge mit dem OpenGLGadget und einem Fragment Shader

Verfasst: 05.12.2021 14:17
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.

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

Verfasst: 06.12.2021 11:56
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.

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

Verfasst: 06.12.2021 12:09
von NicTheQuick
Funktioniert bei mir nicht. wglGetProcAddress_() ist anscheinend eine Windows-API.

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

Verfasst: 06.12.2021 12:20
von STARGÅTE
Hallo Nic,
guck mal ob dieser Code hier funktioniert: https://www.purebasic.fr/english/viewto ... 73#p577873

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

Verfasst: 06.12.2021 15:57
von NicTheQuick
Jup, der tut einwandfrei. :allright: