Jump to content

Shader Families

Josh

392 views

I started to implement quads for tessellation, and at that point the shader system reached the point of being unmanageable. Rendering an object to a shadow map and to a color buffer are two different processes that require two different shaders. Turbo introduces an early Z-pass which can use another shader, and if variance shadow maps are not in use this can be a different shader from the shadow shader. Rendering with tessellation requires another set of shaders, with one different set for each primitive type (isolines, triangles, and quads). And then each one of these needs a masked and opaque option, if alpha discard is enabled.

All in all, there are currently 48 different shaders a material could use based on what is currently being drawn. This is unmanageable.

To handle this I am introducing the concept of a "shader family". This is a JSON file that lists all possible permutations of a shader. Instead of setting lots of different shaders in a material, you just set the shader family one:

shaderFamily: "PBR.json"

Or in code:

material->SetShaderFamily(LoadShaderFamily("PBR.json"));

The shader family file is a big JSON structure that contains all the different shader modules for each different rendering configuration: Here are the partial contents of my PBR.json file:

{
	"turboShaderFamily" :
	{
		"OPAQUE":
		{
			"default":
			{
				"base":
				{
					"vertex": "Shaders/PBR.vert.spv",
					"fragment": "Shaders/PBR.frag.spv"
				},
				"depthPass":
				{
					"vertex": "Shaders/Depthpass.vert.spv"
				},
				"shadow":
				{
					"vertex": "Shaders/Shadow.vert.spv"
				}
			},
			"isolines":
			{
				"base":
				{
					"vertex": "Shaders/PBR_Tess.vert.spv",
					"tessellationControl": "Shaders/Isolines.tesc.spv",
					"tessellationEvaluation": "Shaders/Isolines.tese.spv",
					"fragment": "Shaders/PBR_Tess.frag.spv"
				},
				"shadow":
				{
					"vertex": "Shaders/DepthPass_Tess.vert.spv",
					"tessellationControl": "Shaders/DepthPass_Isolines.tesc.spv",
					"tessellationEvaluation": "Shaders/DepthPass_Isolines.tese.spv"
				},
				"depthPass":
				{
					"vertex": "Shaders/DepthPass_Tess.vert.spv",
					"tessellationControl": "DepthPass_Isolines.tesc.spv",
					"tessellationEvaluation": "DepthPass_Isolines.tese.spv"
				}
			},			
			"triangles":
			{
				"base":
				{
					"vertex": "Shaders/PBR_Tess.vert.spv",
					"tessellationControl": "Shaders/Triangles.tesc.spv",
					"tessellationEvaluation": "Shaders/Triangles.tese.spv",
					"fragment": "Shaders/PBR_Tess.frag.spv"
				},
				"shadow":
				{
					"vertex": "Shaders/DepthPass_Tess.vert.spv",
					"tessellationControl": "Shaders/DepthPass_Triangles.tesc.spv",
					"tessellationEvaluation": "Shaders/DepthPass_Triangles.tese.spv"
				},
				"depthPass":
				{
					"vertex": "Shaders/DepthPass_Tess.vert.spv",
					"tessellationControl": "DepthPass_Triangles.tesc.spv",
					"tessellationEvaluation": "DepthPass_Triangles.tese.spv"
				}
			},
			"quads":
			{
				"base":
				{
					"vertex": "Shaders/PBR_Tess.vert.spv",
					"tessellationControl": "Shaders/Quads.tesc.spv",
					"tessellationEvaluation": "Shaders/Quads.tese.spv",
					"fragment": "Shaders/PBR_Tess.frag.spv"
				},
				"shadow":
				{
					"vertex": "Shaders/DepthPass_Tess.vert.spv",
					"tessellationControl": "Shaders/DepthPass_Quads.tesc.spv",
					"tessellationEvaluation": "Shaders/DepthPass_Quads.tese.spv"
				},
				"depthPass":
				{
					"vertex": "Shaders/DepthPass_Tess.vert.spv",
					"tessellationControl": "DepthPass_Quads.tesc.spv",
					"tessellationEvaluation": "DepthPass_Quads.tese.spv"
				}
			}
		}
	}
}

A shader family file can indicate a root to inherit values from. The Blinn-Phong shader family pulls settings from the PBR file and just switches some of the fragment shader values.

{
	"turboShaderFamily" :
	{
		"root": "PBR.json",
		"OPAQUE":
		{
			"default":
			{
				"base":
				{
					"fragment": "Shaders/Blinn-Phong.frag.spv"
				}
			},
			"isolines":
			{
				"base":
				{
					"fragment": "Shaders/Blinn-Phong_Tess.frag.spv"
				}
			},
			"triangles":
			{
				"base":
				{
					"fragment": "Shaders/Blinn-Phong_Tess.frag.spv"
				}
			},
			"quads":
			{
				"base":
				{
					"fragment": "Shaders/Blinn-Phong_Tess.frag.spv"
				}
			}
		}
	}
}

If you want to implement a custom shader, this is more work because you have to define all your changes for each possible shader variation. But once that is done, you have a new shader that will work with all of these different settings, which in the end is easier. I considered making a more complex inheritance / cascading schema but I think eliminating ambiguity is the most important goal in this and that should override any concern about the verbosity of these files. After all, I only plan on providing a couple of these files and you aren't going to need any more unless you are doing a lot of custom shaders. And if you are, this is the best solution for you anyways.

Consequently, the baseShader, depthShader, etc. values in the material file definition are going away. Leadwerks .mat files will always use the Blinn-Phong shader family, and there is no way to change this without creating a material file in the new JSON material format.

The shader class is no longer derived from the Asset class because it doesn't correspond to a single file. Instead, it is just a dumb container. A ShaderModule class derived from the Asset class has been added, and this does correspond with a single .spv file. But you, the user, won't really need to deal with any of this.

The result of this is that one material will work with tessellation enabled or disabled, quad, triangle, or line meshes, and animated meshes. I also added an optional parameter in the CreatePlane(), CreateBox(), and CreateQuadSphere() commands that will create these primitives out of quads instead of triangles. The main reason for supporting quad meshes is that the tessellation is cleaner when quads are used. (Note that Vulkan still displays quads in wireframe mode as if they are triangles. I think the renderer probably converts them to normal triangles after the tessellation stage.)

tris.png.924dbf1f8023ab6142f6c6ad5fcfc2f2.png

quads.png.a7632c4712016a13b721ea264422a538.png

I also was able to implement PN Quads, which is a quad version of the Bezier curve that PN Triangles add to tessellation.

original.thumb.png.1e8450693214116ddf623a1d82d4de9b.png

tess.thumb.png.37d043cbdf82795bfa7ace7bed1f48f6.png

PNQuads.thumb.png.45be40c442274d05a4ab0d00c353d721.png

Basically all the complexity is being packed into the shader family file so that these decisions only have to be made once instead of thousands of times for each different material.

  • Like 2


0 Comments


Recommended Comments

There are no comments to display.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Blog Entries

    • By tipforeveryone in tipforeveryone's Blog 5
      II. Implement advanced actions
      Previous tutorial guided you to create your own FPS Character Controller with basic actions: Move, Look.
      This tutorial will help you add some advanced actions like Jump, Run, Crouch, and Lean
      *Note: Below steps will use code from previous tutorial (CharacterController.lua)
      1. Jump / Run / Crouch
      Firstly, build obtacle with space under it to test crouching, the space height shoud be at least 120cm (leadwerks editor will show you this height in other viewports) You can not crouch and pass space under 120cm, this was hardcoded


      Next, modify the code.
      Add new variables under --Character movement variables comment. Note that putting script variables at the top of script will allow you to modify character stat easier and faster than finding variables deep inside script's functions
      --Character movement variables Script.playerSpeed = 2 --higer = faster Script.playerRunSpeedMultipier = 2 --if this variable < 1, you can make slower movement Script.playerJumpForce = 7 --This defines how high you can jump Add new funtion to smooth movement of character components function Script:SmoothPosition(position,entity,rate) local smoothX = Math:Curve(position.x,entity:GetPosition().x,rate) local smoothY = Math:Curve(position.y,entity:GetPosition().y,rate) local smoothZ = Math:Curve(position.z,entity:GetPosition().z,rate) entity:SetPosition(smoothX,smoothY,smoothZ) end Replace code in Character_Movement() function by this code function Script:Character_Movement() local playerSpeed, playerJumpForce, playerCrouch --Press Space bar to Jump if window:KeyHit(Key.Space) then playerJumpForce = self.playerJumpForce else playerJumpForce = 0 end --Hold Ctrl key to Crouch if window:KeyDown(Key.ControlKey) then playerCrouch = true else if self.playerHeadBlocked then playerCrouch = true else playerCrouch = false end end --Hold Shift key + AWSD to Run if window:KeyDown(Key.Shift) then playerSpeed = self.playerSpeed * self.playerRunSpeedMultipier else playerSpeed = self.playerSpeed end local playerMove = ((window:KeyDown(Key.W) and 1 or 0) - (window:KeyDown(Key.S) and 1 or 0)) * playerSpeed local playerStrafe = ((window:KeyDown(Key.D)and 1 or 0) - (window:KeyDown(Key.A) and 1 or 0)) * playerSpeed --Using local playerSpeed varialbe instead of self.playerSpeed in the old code local playerTurn = self.playerNeck:GetRotation(true).y self.playerBase:SetInput(playerTurn,playerMove,playerStrafe,playerJumpForce,playerCrouch) end Replace code in Bind_Character_Components_Together() function too function Script:Bind_Character_Components_Together() --Must use this reposition process because playerBase is not playerNeck's parent, they are indipendent. local basePos = self.playerBase:GetPosition(true) local height if window:KeyDown(Key.ControlKey) then height = basePos.y + (self.playerHeight - self.neckLength) / 2 --You can adjust this variable to get desired crouch height for playerNeck (and playerEyes too) else if self.playerHeadBlocked then height = basePos.y + (self.playerHeight - self.neckLength) / 2 else height = basePos.y + self.playerHeight - self.neckLength end end self:SmoothPosition(Vec3(basePos.x,height,basePos.z),self.playerNeck,10) end Add a new function to script, this will keep player crouching if something block above character when release Ctrl key, prevent from being pushed around by obtacle when standing. function Script:Check_Head_Block() --We can use a raycast to check if something block character head when crouching local pickInfo = PickInfo() local point1 = self.playerNeck:GetPosition(true) local point2 = self.playerBase:GetPosition(true) + Vec3(0,self.playerHeight,0) if world:Pick(point1,point2,pickInfo,0.3,true) then self.playerHeadBlocked = true else self.playerHeadBlocked = false end end Update Script:UpdateWorld() function with new Check_Head_Block() function function Script:UpdateWorld() self:Bind_Character_Components_Together() self:Check_Head_Block() self:Character_Look() self:Character_Movement() end Now, you can crouch under obtacle, release Ctrl Key and you are still crouching. Continue moving for auto standing. 2. Leaning
      In Character_Movement() function, add this code after Crouching code
      --Hold Q/E to lean Left/Right local leanDistance = 0.025 --leanDistance should be < 0.025 or camera will pass through wall when you get too close if window:KeyDown(Key.Q) then self.playerEyes:Move(-leanDistance,-0.01,0) end if window:KeyDown(Key.E) then self.playerEyes:Move(leanDistance,-0.01,0) end And add this line at the bottom of Bind_Character_Components_Together() function self:SmoothPosition(Vec3(0,self.neckLength,0),self.playerEyes,10) You can lean now, so smooth. Final script file was attached to this entry. You can apply it to a pivot point (PlayerControl).
      CharacterController.lua
    • By 💎Yue💎 in The shock absorbers 2
      Coming to the end of my prototype of a 3d game, and with background music by Hanz Zimmer ( Time ). I saw my progress in many aspects, always something to learn, always something to improve, I didn't intend to make a game, that has never been the goal. 
      Rather, the effort and dedication immeasurably, was to improve on something learned. And here I was with the powerful leadwerks engine, where his greatest power lay in making everything very easy.  
      About the project
      The prototype is very simple, a third person character goes through a stage, an orbital camera that follows him with many interesting things when scripting.  A character who runs, walks, ducks, jumps, and suffers damage when he falls from different heights. 
      The interesting thing about all this is that just like when you're little and learn to write, repetition is key to learning to program, understand concepts and improve.  So as I've always said, you learn to program by programming, although I sincerely think that lua script is not programming, but the experts say it is, so I go into that elitism of those of us who think we are programmers. 
      The final part of the project consists of creating a death animation, this will be activated when the player falls from a high part and separates the legs (that is very cruel). 
      But that feeling of improvement is the same that I feel when in the mornings when I have a coffee I solve a riddle of the newspaper, but I don't know when I'm going to stop, it turns out and it happens that technology advances very fast and this continues and continues without stopping. Starting with LE 5, and the only thing that can happen is that I die or that my old computer doesn't work anymore. 
      Translated with www.DeepL.com/Translator





    • By 💎Yue💎 in The shock absorbers 1
      It's interesting that when you become an expert on something, you're not sparing any effort to see how something works, but rather you're focusing on creating something. And so everything becomes easier.
      At this point of learning there is a glimpse of a low idea of creating a game, but the secret of all this is to keep it simple and to be very clear that a game is a game, and not an exact simulation of the real world. For example anyone who has a low idea of the red planet, will understand no matter the colors of the scene that is a terrain of Mars, even if it is not very real what is transmitted, a game, that's just it.
      At this point I already have an astronaut character who runs from one place to another on a very large 4096 x 4046 terrain that would surely take a long walk. My previous prototype projects involve a vehicle, but I didn't get the best implementation prospect in that time and I always found performance problems in my machine, something that isn't happening with the character controller for a third person player. 
      As always, I think I'm a scavenger looking for game resources, that's where this community exposes links to websites with interesting hd textures, and one or another model searched on the net, but what I've greatly improved is learning to write code, I have a better workflow, writing Lua code focused on the paradigm of object programming.



      Something interesting is the system of putting rocks, all very nice from the point of implementing them. And it works very well with the character controller if you put collision in cube form.
      I've been thinking about implementing a car system, I think it would be necessary in such a large terrain, but I think it's not the time, my previous experience, involves deterioration in performance and something I think is the physics of the car with respect to the terrain and rocks that in the previous project involve deterioration in the fps. Although if you implement a car would have an option would be to remove the rocks, but I prefer not to have a car and if you have rocks. 
       
       
       
       
×
×
  • Create New...