Posted: 12th Jan 2023 11:49
Hello!

I decided to share a small shader that is designed to draw 3D lines on the screen. This is an alternative to the standard DrawLine method. Perhaps someone will find it useful.

This can be used to draw paths, or to display the wireframe of an object (if you get the coordinates of all vertices using a memblock).

Any suggestions to optimize or improve the shader code are welcome

Below is a small demo showing the shader in action. Shader files are generated directly from the AppGameKit code for convenience.

Transformation of 3D -> 2D coordinates is carried out in the shader itself. As initial data, we can set the coordinates of 2 points of the 3D line, as well as its color. It is also possible to set the line width in pixels, but I did not output this as an input parameter.

+ Code Snippet
SetErrorMode(2)
SetWindowTitle("test")
SetWindowSize(1024,768,0)
SetWindowAllowResize(1)
MaximizeWindow()
global scr_w as integer : scr_w=GetMaxDeviceWidth()
global scr_h as integer : scr_h=GetMaxDeviceHeight()
global scr_ratio# as float 
scr_ratio# = 1.0*scr_h/scr_w
if scr_ratio#>0.7:scr_w=1920:scr_h=1080:endif	
SetVirtualResolution(scr_w,scr_h) 
SetOrientationAllowed(1,1,1,1)
SetSyncRate(120,0)
SetScissor(0,0,0,0)
UseNewDefaultFonts(1)
SetPrintSize(28)
SetCameraRange(1,10,10000) 

#constant true     1
#constant false    0

type coord
     x          as float
     y          as float
     z          as float
     l          as float
     nx         as float
     ny         as float
     nz         as float
endtype   

type tcoord
     u as float
     v as float
endtype					
 
global mousex#          as float
global mousey#          as float

global curcamx#         as float
global curcamy#         as float
global curcamz#         as float
global cameraMove       as integer
global cam_f            as coord
global smes_spd#        as float
global camR_old         as float
global camR             as float
global camRMin          as float
global camRMax          as float
global DeltaCamR        as float
global camA             as float
global camB             as float
global camA_min         as float
global camA_max         as float
global camB_min         as float
global camB_max         as float
global camA_inn         as float
global camB_inn         as float
global camE             as float
global camD             as float
global camF             as float
global mmx#             as float
global mmy#             as float
global nmx#             as float
global nmy#             as float
global foc_bound_left   as float
global foc_bound_right  as float
global foc_bound_up     as float
global foc_bound_down   as float

global pick_dist        as float
global pick_dirX        as float
global pick_dirY        as float
global pick_dirZ        as float
global pickID           as integer 
global pick_stat        as integer 
global pick_state       as integer 

global time_before      as float
global time_begin       as float
global time_lost        as float
global time_curr        as float
global inner_speed      as float
	  
global obj_foc : obj_foc = CreateObjectBox(1, 1, 1)
SetObjectPosition(obj_foc,0,0,0)
SetObjectVisible(obj_foc, 0) 	  
	     	
cam_f.x  = 0
cam_f.y  = 0
cam_f.z  = 0
camRMin  = 1000
camRMax  = 50
camR_old = camRMin
camR     = camR_old
DeltaCamR= 50
camA     = 30
camB     = 0
camA_min = 0
camA_max = 89
camB_min = 0 
camB_max = 0 
camA_inn = camA
camB_inn = camB
camE     = 0 
camD     = 0 
camF     = 0 
mmx#     = 0 
mmy#     = 0 
nmx#     = 0 
nmy#     = 0 
foc_bound_left  =-520 
foc_bound_right = 520 
foc_bound_up    = 520 
foc_bound_down  =-520 
SetObjectPosition( obj_foc, cam_f.x,cam_f.y,cam_f.z)
SetCameraPosition(1, cam_f.x+(CamR*cos(270-CamB)*cos(CamA)), cam_f.y+(CamR*sin(CamA)),cam_f.z+(CamR*sin(270-CamB)*cos(CamA)))            
SetCameraLookAt(  1, cam_f.x, cam_f.y, cam_f.z, 0)   
 
 
createVSShaderFile()
createPSshaderFile()
shader=LoadShader("vertex_shader.vs","pixel_shader.ps")

ground = CreateObjectPlane(1000,1000) 
SetObjectRotation(ground,90,0,0)
SetObjectColor(ground,0,40,0,255)

global wp_dimx# as float = 200.0
global wp_dimy# as float = 50.0
global wp_dimz# as float = 200.0

obj3ID = CreateObjectBox(wp_dimx#,wp_dimy#,wp_dimz#)
SetObjectPosition(obj3ID,-150,25,150)
SetObjectCullMode(obj3ID,0)

obj2ID = CreateObjectSphere(60,24,24)
SetObjectPosition(obj2ID,-50,60,150)
setobjectcullmode(obj2ID,0)

global pla : pla = createobjectplane(5,5)
SetObjectRotation(pla,90,0,0)
SetObjectPosition(pla,0,100,0)
SetObjectShader(pla, shader)

global testob : testob = createobjectbox(5,5,100)
SetObjectPosition(testob,0,100,0)
SetObjectColor(testob,0,255,0,255)

global testob2 : testob2 = createobjectplane(5,5)
SetObjectRotation(testob2,90,0,0)
SetObjectPosition(testob2,100,100,0)
SetObjectShader(testob2, shader)
 
do
     
    time#=timer()
    time_begin = GetMilliseconds()
    time_curr = time_begin - time_before
    mousex# = GetPointerX()
    mousey# = GetPointerY()
    if ScreenFPS() > 30
       inner_speed = ScreenFPS()/15.0
    else
       inner_speed = 10
    endif      

    camera_control(1) 

    `SetObjectPosition(pla,GetCameraX(1),GetCameraY(1),GetCameraZ(1))
    `SetObjectRotation(pla,GetCameraAngleX(1),GetCameraAngleY(1),GetCameraAngleZ(1))
    `MoveObjectLocalZ(pla,50)

    SetShaderConstantByName(shader,"ViewPortSize",GetWindowWidth(),GetWindowHeight(),0,0)    

    p1x#=GetObjectX(testob)
    p1y#=GetObjectY(testob)
    p1z#=GetObjectZ(testob)-50
    p2x#=GetObjectX(testob)
    p2y#=GetObjectY(testob)
    p2z#=GetObjectZ(testob)+50
   
    SetObjectShaderConstantByName(pla,"point1",p1x#,p1y#,p1z#,0)
    SetObjectShaderConstantByName(pla,"point2",p2x#,p2y#,p2z#,0)    
    SetObjectShaderConstantByName(pla,"LineColor",255,0,0,0)

    p1x2#=GetObjectX(testob2)
    p1y2#=GetObjectY(testob2)
    p1z2#=GetObjectZ(testob2)-50
    p2x2#=GetObjectX(testob2)+50
    p2y2#=GetObjectY(testob2)
    p2z2#=GetObjectZ(testob2)+50
    SetObjectShaderConstantByName(testob2,"point1",p1x2#,p1y2#,p1z2#,0)
    SetObjectShaderConstantByName(testob2,"point2",p2x2#,p2y2#,p2z2#,0)    
    SetObjectShaderConstantByName(testob2,"LineColor",0,0,255,0)

    if GetRawKeyPressed(27) then end
  
    Print("To control the camera, use the mouse: LMB-shift, RMB-rotate, Scroll-zoom" )
    
    Print("FPS="+ str(ScreenFPS()) )

    Print("GetMaxDeviceWidth()="+ str(GetMaxDeviceWidth()) )
    Print("GetMaxDeviceHeight()="+ str(GetMaxDeviceHeight()) )
    Print("GetDeviceWidth()="+ str(GetDeviceWidth()) )
    Print("GetDeviceHeight()="+ str(GetDeviceHeight()) )
    Print("GetVirtualWidth()="+ str(GetVirtualWidth()) )
    Print("GetVirtualHeight()="+ str(GetVirtualHeight()) )
    Print("GetWindowWidth()="+ str(GetWindowWidth()) )
    Print("GetWindowHeight()="+ str(GetWindowHeight()) )
    
    Print("GetScreenBoundsLeft()="+ str(GetScreenBoundsLeft()) )
    Print("GetScreenBoundsRight()="+ str(GetScreenBoundsRight()) )
    Print("GetScreenBoundsTop()="+ str(GetScreenBoundsTop()) )
    Print("GetScreenBoundsBottom()="+ str(GetScreenBoundsBottom()) )
 
    Print("GetScreenBoundsSafeLeft()="+ str(GetScreenBoundsSafeLeft()) )
    Print("GetScreenBoundsSafeRight()="+ str(GetScreenBoundsSafeRight()) )
    Print("GetScreenBoundsSafeTop()="+ str(GetScreenBoundsSafeTop()) )
    Print("GetScreenBoundsSafeBottom()="+ str(GetScreenBoundsSafeBottom()) )

    Sync()
    time_lost = GetMilliseconds() - time_begin       
loop
 
  
function createVSShaderFile()
     
    fw = OpenToWrite("vertex_shader.vs")

    remstart
    WriteLine(fw,"attribute highp vec3 position;")
    WriteLine(fw,"attribute mediump vec3 normal;")
    WriteLine(fw,"attribute mediump vec2 uv;")
    WriteLine(fw,"varying highp vec3 posVarying;")
    WriteLine(fw,"varying mediump vec3 normalVarying;")
    WriteLine(fw,"varying mediump vec2 uvVarying;")
    WriteLine(fw,"varying mediump vec3 lightVarying;")
    WriteLine(fw,"uniform highp mat3 agk_WorldNormal;")
    WriteLine(fw,"uniform highp mat4 agk_World;")
    WriteLine(fw,"uniform highp mat4 agk_ViewProj;")
    WriteLine(fw,"uniform mediump vec4 uvBounds0;")
    WriteLine(fw,"mediump vec3 GetVSLighting( mediump vec3 normal, highp vec3 pos );")
    WriteLine(fw,"void main()")
    WriteLine(fw,"{") 
    WriteLine(fw,"    uvVarying = uv * uvBounds0.xy + uvBounds0.zw;")
    WriteLine(fw,"    highp vec4 pos = agk_World * vec4(position,1.0);")
    WriteLine(fw,"    gl_Position = agk_ViewProj * pos;")
    WriteLine(fw,"    mediump vec3 norm = normalize(agk_WorldNormal * normal);")
    WriteLine(fw,"    posVarying = pos.xyz;")
    WriteLine(fw,"    normalVarying = norm;")
    WriteLine(fw,"    lightVarying = GetVSLighting( norm, posVarying );")
    WriteLine(fw,"}")
    remend  

    WriteLine(fw,"attribute highp vec3 position;")   
    WriteLine(fw,"void main()")
    WriteLine(fw,"{")
    WriteLine(fw,"    gl_Position = vec4(position,1.0);")
    WriteLine(fw,"}")
       
    CloseFile(fw)
  
endfunction
 
 
function createPSshaderFile()
 
    fw=OpenToWrite("pixel_shader.ps")
    
    remstart 
    WriteLine(fw,"uniform sampler2D texture0;")
    WriteLine(fw,"varying highp vec3 posVarying;")
    WriteLine(fw,"varying mediump vec3 normalVarying;")
    WriteLine(fw,"varying mediump vec2 uvVarying;")
    WriteLine(fw,"varying mediump vec3 lightVarying;")
    WriteLine(fw,"mediump vec3 GetPSLighting( mediump vec3 normal, highp vec3 pos );")
    WriteLine(fw,"mediump vec3 ApplyFog( mediump vec3 color, highp vec3 pointPos );")
    WriteLine(fw,"void main()")
    WriteLine(fw,"{") 
    WriteLine(fw,"    mediump vec3 norm = normalize(normalVarying);")
    WriteLine(fw,"    mediump vec3 light = lightVarying + GetPSLighting( norm, posVarying );")
    WriteLine(fw,"    mediump vec3 color = texture2D(texture0, uvVarying).rgb * light;")
    WriteLine(fw,"    color = ApplyFog( color, posVarying );")
    WriteLine(fw,"    gl_FragColor = vec4(color,1.0);")
    WriteLine(fw,"}")
    remend

    
    WriteLine(fw,"uniform mediump vec2 ViewPortSize;")   // dimensions of the working area of the AGK window
    WriteLine(fw,"uniform mediump vec3 point1;")         // 3D coordinates of point ?1
    WriteLine(fw,"uniform mediump vec3 point2;")         // 3D coordinates of point ?2
    WriteLine(fw,"uniform mediump vec3 LineColor;")      // line color
    WriteLine(fw,"uniform highp mat4 agk_ViewProj;")     // combination of the View and Proj matrices transforms 3D world space into window space.
    // "draw" line function
    WriteLine(fw,"float drawLine2(vec2 p1, vec2 p2, vec2 uv, float aa)")    // aa - line thickness
    WriteLine(fw,"{")
    // if the current point is on the line according to the distance, it should be the same as the current uv coordinates
    WriteLine(fw,"  mediump float r = 1.0-floor(1.0-(aa/ViewPortSize.x)+distance(mix(p1, p2, clamp(distance(p1,uv)/distance(p1,p2), 0., 1.)), uv));")    
    WriteLine(fw,"return r;")
    WriteLine(fw,"}") 
    WriteLine(fw,"void main()")
    WriteLine(fw,"{")
    WriteLine(fw,"  mediump vec2 uv = gl_FragCoord.xy/ViewPortSize.xy;")    // current point of the screen
    WriteLine(fw,"  mediump vec4 vp1 = agk_ViewProj*vec4(point1,1);")       // convert the given 3D coordinates of point ?1 into 2D viewport coordinates
    WriteLine(fw,"  mediump vec4 vp2 = agk_ViewProj*vec4(point2,1);")       // convert the given 3D coordinates of point ?2 into 2D viewport coordinates
    WriteLine(fw,"  mediump vec2 p1 = (vp1.xy/vp1.w)*0.5+0.5;")             // transform from the interval [-1,1] to the interval [0,1]
    WriteLine(fw,"  mediump vec2 p2 = (vp2.xy/vp2.w)*0.5+0.5;")             // transform from the interval [-1,1] to the interval [0,1]
    WriteLine(fw,"  mediump float px = drawLine2(p1,p2,uv,1.0);")           // a pixel belonging to a line
    WriteLine(fw,"  if (px == 0.0) {")                                      // if pixel does not lie on line
    WriteLine(fw,"    discard;")                                            // don't draw it
    WriteLine(fw,"  } else {")                                              // if the pixel belongs to the line
    WriteLine(fw,"    gl_FragColor=vec4(px*normalize(LineColor),1);")       // draw it with color
    WriteLine(fw,"  }")
    WriteLine(fw,"}")
   
    
    CloseFile(fw)
        
endfunction

function CurveValue( ar_dest as float , ar_cur as float , ar_smoot as float )
         ar_cur = ar_cur + ( ( ar_dest - ar_cur ) / ar_smoot )
endfunction ar_cur

function lin_inp( arA1 as float , arA2 as float , arA3 as float , arB1 as float , arB3 as float )	
	     ret_val as float : ret_val = arB1 - ( arB1 - arB3 ) * ( arA2 - arA1 ) / ( arA3 - arA1 )	     
endfunction ret_val

function camera_control(mode as integer)
         curcamx# = getcamerax(1)
         curcamy# = getcameray(1)
         curcamz# = getcameraz(1)
		 if mousex# >= 0 and mousex# <= scr_w and mousey# >= 0 and mousey# <= scr_h
            if CameraMove > 0 and GetRawMouseLeftState() = 0 and GetRawMouseRightState() = 0 then CameraMove = 0                                   
            if GetRawMouseLeftPressed() = 1 or GetRawMouseRightPressed() = 1
               mmx# = GetRawMouseX()
               mmy# = GetRawMouseY()
            endif                                                 
            if pick_state = 0                
               if CameraMove = 0              
                  if (GetRawMouseLeftPressed() = 1 and GetRawMouseRightPressed() = 1) or (GetRawMouseLeftPressed() = 0 and GetRawMouseRightPressed() = 1)						 
                     if pick_stat = 0 then CameraMove = 1
                  endif                    
			      if GetRawMouseLeftPressed() = 1 and GetRawMouseRightPressed() = 0
                     if pick_stat = 0 then CameraMove = 2
                  endif                          
               endif               
            endif                                    
         endif                           
         if CameraMove = 1                   				 
            nmx# = GetRawMouseX()
            nmy# = GetRawMouseY()
            inc CamA, 0.1*(nmy#-mmy#)
            inc CamB, 0.1*(nmx#-mmx#)
            mmx# = nmx#
            mmy# = nmy#
         endif                  
         smes_spd# = 1.2 - (1.2 - 2.0) * (CamR - CamRMax) / (CamRMin - CamRMax)  
         if smes_spd# <= 1.2 then smes_spd# = 1.2
         if smes_spd# >= 2.0 then smes_spd# = 2.0                        
         if CameraMove = 2                                                                                     
            nmx#  = GetRawMouseX()
            nmy#  = GetRawMouseY()		       
            dnmx# = nmx#-mmx#
            dnmy# = nmy#-mmy#                                    
		    if (dnmx#>0 and cam_f.x>foc_bound_left) or (dnmx#< 0 and cam_f.x<foc_bound_right)	   
                if CamA<90
                   MoveObjectLocalX(obj_foc,-0.25*smes_spd#*dnmx#)
                else
				   MoveObjectLocalX(obj_foc, 0.25*smes_spd#*dnmx#)
				endif	   
            endif                                       
            if mode=0
               setobjectrotation(obj_foc,0,0,0)
		       if (dnmy#<0 and cam_f.y>foc_bound_down) or (dnmy#>0 and cam_f.y<foc_bound_up) 	    
                  if CamA >= -70 and CamA <= 70 then MoveObjectLocalY(obj_foc,cos(camA)*0.25*smes_spd#*dnmy#)
               endif                       
               cam_f.x = curvevalue(GetObjectX(obj_foc),cam_f.x,inner_speed)
               cam_f.y = curvevalue(GetObjectY(obj_foc),cam_f.y,inner_speed)                           
            else
               setobjectrotation(obj_foc,0,getcameraangley(1),0)
		       if (dnmy#<0 and cam_f.y>foc_bound_down) or (dnmy#>0 and cam_f.y<foc_bound_up) 	    
                  if CamA<90
                     MoveObjectLocalZ(obj_foc,0.25*smes_spd#*dnmy#)
                  else
					 MoveObjectLocalZ(obj_foc,-0.25*smes_spd#*dnmy#)
				  endif	     
               endif                       
               cam_f.x = curvevalue(GetObjectX(obj_foc),cam_f.x,inner_speed)
               cam_f.z = curvevalue(GetObjectZ(obj_foc),cam_f.z,inner_speed)                           
			endif	               
            mmx# = nmx#
            mmy# = nmy#                                                  
            main_status_camera_act = 1
         endif                                                     		                
         if GetRawMouseWheelDelta() < 0                                                  
            CamR_old = CamR_old + DeltaCamR
         endif                                                                         
         if GetRawMouseWheelDelta() > 0                                                  
            CamR_old = CamR_old - DeltaCamR
         endif                  
         if CamR_old <= CamRMax then CamR_old = CamRMax
         if CamR_old >= CamRMin then CamR_old = CamRMin
         if CamA < camA_min  then CamA = camA_min
         if CamA > camA_max  then CamA = camA_max         
         if camB_min = camB_max 
            if CamB < 0   then CamB = 360        
            if CamB > 360 then CamB = 0          
         else 
            if CamB < camB_min  then CamB = camB_min  
            if CamB > camB_max  then CamB = camB_max  
         endif
         if cam_f.x < foc_bound_left  then cam_f.x = foc_bound_left   
         if cam_f.x > foc_bound_right then cam_f.x = foc_bound_right  
         if mode=0
            if cam_f.y < foc_bound_down  then cam_f.y = foc_bound_down
            if cam_f.y > foc_bound_up    then cam_f.y = foc_bound_up  
         else
            if cam_f.z < foc_bound_down  then cam_f.z = foc_bound_down
            if cam_f.z > foc_bound_up    then cam_f.z = foc_bound_up  
		 endif	 
         CamR = curvevalue(CamR_old, CamR, inner_speed)
         camA_inn = CamA
         camB_inn = CamB
         CamE = CamR * cos(270-CamB_inn)*cos(CamA_inn) 
         CamD = CamR * sin(    CamA_inn)               
         CamF = CamR * sin(270-CamB_inn)*cos(CamA_inn)                          
         SetObjectPosition( obj_foc, curvevalue(cam_f.x,GetObjectX(obj_foc),inner_speed), curvevalue(cam_f.y,GetObjectY(obj_foc),inner_speed), curvevalue(cam_f.z,GetObjectZ(obj_foc),inner_speed))   
         SetCameraPosition(1, cam_f.x+CamE , cam_f.y+CamD , cam_f.z+CamF  )                     
         if CamA<90
            SetCameraLookAt(  1, cam_f.x, cam_f.y, cam_f.z, 0)        
		 else
            SetCameraLookAt(  1, cam_f.x, cam_f.y, cam_f.z, 180)        
		 endif
endfunction	
Posted: 12th Jan 2023 23:00
I suggest using SetObjectDepthReadMode(testob2,7) so that the object always passes the depth test.
And then you use a plane object and make it a fullscreen quad object with your shader, so I suggest using the built in quad object instead since it has fewer indices and only one face.
This way you can just use a fullscreen shader and ignore the vertex shader as it will then be automatically generated by AGK.
It's interesting to use it for wireframes because it doesn't need to be applied to the wireframe object itself but can be easily overlaid.
But I think it would be verry hard to match the the quality of an per object wireframe that way.
Thanks for sharing.
Posted: 12th Jan 2023 23:24
You could use GetScreenX/YFrom3D() and then use DrawLine()
Posted: 13th Jan 2023 10:28
Thanks Janbo!

Yes, I have already thought about how to display this image in Quad, although I have not worked with full-screen shaders before. I will need to read the help.
Posted: 13th Jan 2023 10:34
Thank you blink0k.

In my project (simulation of the CNC Machine) it is required to draw a large number of tool paths (thousands and tens of thousands of elements). Now this is implemented by 3D objects (extruded box). I'm looking for a more optimal and efficient method.

I understand that using this shader will not give the desired result, because for each trajectory, you will have to create your own instance of Quad. Or draw everything in one Quad object, but a very large array will have to be passed to the shader.

So this method, as well as DrawLine(), is not suitable for drawing paths...
Posted: 13th Jan 2023 11:57
And a Wireframe effect would also be pretty performance intensive to do it with a fullscreen shader on dynamic objects let alone on animated ones.
Posted: 18th Jan 2023 0:41
What I fail to understand is why an array can't be passed to the glsl shader.

an array with a glsl shader example
+ Code Snippet
struct MyStruct {
    vec3 color1;
    vec3 color2;
};

uniform MyStruct u_mystruct[2];

void main(void) {
    gl_FragColor = vec4(u_mystruct[0].color1 + u_mystruct[0].color2 + u_mystruct[1].color1+ u_mystruct[1].color2, 1.0);
}


ideally what ide like to see achieved where 2 could be left []
+ Code Snippet
struct MyStruct{
     float fX;
     float fY;
     float fZ;
};
uniform MyStruct u_mystruct[2](
//u_mystruct[0].fx,   u_mystruct[0].fy    u_mystruct[0]
//gl_fragcolor=
}
Posted: 19th Jan 2023 16:50
Arrays can be passed but not as a struct/type in AppGameKit only everywhere else it works ^^