Mastering Tessellation Shaders in Unity! Easy LoD, Curved Triangles, Height Maps | Game Dev Tutorial — Transcript
Full transcript
- 0:00hi i'm ned and i make games
- 0:03in this unity graphics programming
- 0:04tutorial i'll introduce you to
- 0:06tessellation shaders advanced shaders
- 0:08which can subdivide triangles adding
- 0:10details and smoothing out blocky models
- 0:13use them for automatic level of detail
- 0:15procedural models or height map-based
- 0:17terrain
- 0:18keep watching to learn how to add
- 0:20tessellation to any shader and any game
- 0:21yourself
- 0:23please don't let the length of this
- 0:24video scare you off i've prepared time
- 0:26stamps for all the sections of this
- 0:28tutorial so feel free to skip around
- 0:31i want to take a moment to thank all my
- 0:32patrons for helping make this video
- 0:34possible and give a big shout out to my
- 0:36next gen patron croobiedoobydoo
- 0:38thank you all so much
- 0:40also if you prefer reading tutorials i
- 0:42have a written version of this one
- 0:44linked in the video description
- 0:46this tutorial was tested in unity 2020.3
- 0:502021.1 and 2021.2
- 0:53if you're using a newer version than
- 0:54that check out the video description for
- 0:56any changes that you should know about
- 0:59also i'll be using the universal render
- 1:01pipeline for all the examples in this
- 1:02video however none of these techniques
- 1:04are urp specific
- 1:06the shader graph will not be featured
- 1:08here as even in hdrp it does not support
- 1:11all the required techniques
- 1:13if you're unfamiliar with hlsl shaders
- 1:15in urp or otherwise i'm writing a
- 1:17tutorial about that so check the video
- 1:19description
- 1:21this tutorial will explain what
- 1:22tessellation shaders are and how to
- 1:24write them in hlsl i will demonstrate
- 1:27several methods to optimize teslazation
- 1:30control the amount of subdivision per
- 1:31triangle
- 1:32smooth a model's geometry and silhouette
- 1:35and add details with height maps and
- 1:37procedural techniques
- 1:39this tutorial aims to explain the topic
- 1:41of tessellation not so much create a
- 1:43finished shader
- 1:45nevertheless i have provided an example
- 1:47shader in the video description with
- 1:49most of the topics in this video
- 1:50implemented
- 1:53as i mentioned earlier
- 1:54this tutorial is more advanced and we'll
- 1:56expect you to know how to write shaders
- 1:58in hlsl
- 2:00in addition it will make use of vector
- 2:02math so brush up on vector dot products
- 2:04and cross products as well as vector
- 2:06projection and reflection
- 2:11in mathematics teslazation is the
- 2:13process of fitting shapes together to
- 2:15form a larger surface
- 2:18however in graphics programming it
- 2:19usually refers to subdividing a shape
- 2:21into smaller pieces
- 2:23tessellation shaders do just that if you
- 2:25put a mesh in the gpu will subdivide all
- 2:28of its faces adding many more vertices
- 2:30now why is this useful well you can take
- 2:33these extra vertices and move them
- 2:34around smoothing out jagged low poly
- 2:37edges and adding fine position details
- 2:39not possible otherwise
- 2:41this enables a kind of reverse workflow
- 2:43for lod where you can create low poly
- 2:46models and use teslazation to add
- 2:48complexity testilization is also great
- 2:51for terrain
- 2:52you can use a flat mesh and tessellate
- 2:54it adjusting heights with a height map
- 2:56this allows you to adjust the shape
- 2:58using only a texture and render high
- 3:00detail only where the camera can see it
- 3:03generate a height map procedurally to
- 3:05visualize mathematical surfaces like
- 3:07sdfs or easily animate a mesh
- 3:11keep in mind that tessellation shaders
- 3:13do have a hefty performance cost the
- 3:15good news is that they're generally
- 3:17cheaper and have better hardware support
- 3:19than their cousins geometry shaders plus
- 3:22they do allow you to keep your mesh
- 3:23assets simpler which can help with
- 3:25batching if you're worried about
- 3:27performance be sure to test things out
- 3:29before committing to testilization
- 3:32okay so how do you add teslazation to a
- 3:35shader teslazation shaders have two
- 3:37additional programmable stages similar
- 3:39to the vertex and fragment stages we're
- 3:41all used to
- 3:42these are called the hull and domain
- 3:44stages they run in between the vertex
- 3:47and fragment stages and together with an
- 3:49unprogrammable stage called the
- 3:51tessellator control how the mesh is
- 3:53subdivided and refined
- 3:55let's take a look at the structure of
- 3:56these functions and then see how to add
- 3:58them to your shader code
- 4:00the whole function receives data in the
- 4:02form of patches which are simply lists
- 4:05of vertices
- 4:06what relation these vertices have to one
- 4:08another is configurable however in this
- 4:10tutorial i'll always stick to triangles
- 4:13so this patch is an array of three
- 4:15vertices that make up a triangle on the
- 4:16mesh the array contains output data from
- 4:19the vertex function corresponding to
- 4:21each vertex
- 4:22besides the patch the whole function
- 4:24also receives an index specifying which
- 4:27vertex in the patch the whole function
- 4:29must output data for it runs once per
- 4:31vertex in the patch and can look at all
- 4:33the other vertices in the patch to
- 4:35produce a new data structure for later
- 4:37on in the chain the whole stage is
- 4:39unique in that it also has another
- 4:41function that runs in parallel the patch
- 4:43constant function the separate function
- 4:46runs once per patch so it's very useful
- 4:48to calculate data that's shared between
- 4:50vertices of a triangle it also must
- 4:52output tessellation factors which
- 4:54determine how many times to subdivide
- 4:56the patch we'll talk about these
- 4:58extensively later on so to summarize the
- 5:01whole stage is made up of two functions
- 5:03the whole function and the patch
- 5:05constant function they receive a patch
- 5:07which is a collection of vertices
- 5:09usually forming a triangle
- 5:11the whole function runs once per vertex
- 5:13in the patch and the patch constant
- 5:15function runs once per patch and must
- 5:18output tessellation factors
- 5:20next up the non-programmable stage
- 5:23called the tessellator runs this takes
- 5:25patch data and the tessellation factors
- 5:27generating during the whole stage to
- 5:29subdivide each patch
- 5:31the tessellator generates something
- 5:32called barycentric coordinates for all
- 5:34the vertices of this new mesh
- 5:36barycentric coordinates are an easy way
- 5:38to describe a point inside of a triangle
- 5:41any point can be calculated as a
- 5:43weighted average of the three corner
- 5:44points and barycentric coordinates are
- 5:46the weights in that formula
- 5:48they're given as a float3 vector and the
- 5:50three components always sum to one
- 5:53besides position we can also use
- 5:55barycentric coordinates to calculate
- 5:56normals uvs or anything else that varies
- 5:59based on the triangle corners
- 6:02this brings us to the second
- 6:03programmable stage involved in
- 6:04teslalization the domain stage it
- 6:07consists of one function the domain
- 6:09function which runs once per each vertex
- 6:12on the tessellated mesh its job is to
- 6:15output the final data for the mesh and
- 6:17to do so it receives the barycentric
- 6:19coordinates for vertex and its
- 6:21originating patch this includes all of
- 6:23the data generated by the whole function
- 6:25and the patch constant function the
- 6:26domain function is where a lot of your
- 6:28logic will go most of what you'd usually
- 6:30put in the vertex stage should be
- 6:32calculated here instead including clip
- 6:34space positions crucially you can
- 6:36reposition vertices in the domain stage
- 6:39something that's essential for most of
- 6:40tessellation's use cases
- 6:43if you have a geometry function it would
- 6:45run next after the domain stage but
- 6:47usually the rasterization and fragment
- 6:49stages run next
- 6:50to summarize the vertex stage runs first
- 6:53the whole stage receives information
- 6:55about triangles on your mesh called
- 6:57patches and decides how to subdivide
- 6:59them the test later does the heavy
- 7:01lifting subdividing the mesh while the
- 7:03domain stage prepares vertices in the
- 7:05tessellated mesh for the fragment stage
- 7:07deciding where each vertex should appear
- 7:10on the screen
- 7:11now we know what the hull and domain
- 7:13functions do let's add them to a simple
- 7:15shader you can register hole and domain
- 7:17functions similarly to the other
- 7:18programmable stages using a pragma
- 7:21directive note that tessellation shaders
- 7:23require shader target 5.0 so adjust that
- 7:26too or unity will give you an error the
- 7:29vertex function is pretty plain now it
- 7:31simply converts positions and normals to
- 7:33world space the output structure will be
- 7:35fed into the whole stage and can contain
- 7:38basically any data that you'd need later
- 7:40on in the pipeline notably the position
- 7:42semantic is forbidden in this structure
- 7:45so use the internal test pause semantic
- 7:47instead
- 7:48the whole shader signature looks like
- 7:50this it has several attribute-like tags
- 7:53the domain determines the input patch
- 7:56type while the output topology and
- 7:58output control points determine the
- 8:00output patch type
- 8:01again we're always going to use
- 8:03triangles in this tutorial so these will
- 8:05all remain the same
- 8:06the patch constant function attribute
- 8:08registers the patch constant function
- 8:11and the partitioning attribute tells the
- 8:13test later which algorithm to use to
- 8:15subdivide triangles keep this one in
- 8:17mind for later
- 8:19the function itself receives the input
- 8:21patch using the special construct the
- 8:23vertex function output structure and
- 8:25number of vertices in the patch go
- 8:27inside the angle brackets
- 8:29you can access each structure in the
- 8:31patch like you would an array
- 8:33the hole function also receives the
- 8:35vertex index with the sv underscore
- 8:37output control point semantic it signals
- 8:40which vertex in the patch to output data
- 8:42for finally don't forget to set the
- 8:44return structure type in this example
- 8:46it's the same as the vertex output
- 8:48structure type but it could be unique
- 8:51there's no required fields in this data
- 8:53but once again use internal test pose
- 8:55instead of position in this example the
- 8:57hull function body is extremely simple
- 9:00only returning the correct vertex inside
- 9:02the patch
- 9:03the patch constant function has a much
- 9:05simpler signature it also receives the
- 9:08input patch and outputs its own data
- 9:10structure
- 9:11this structure should contain the
- 9:12tessellation factors specified per edge
- 9:15on the triangle using the sv test factor
- 9:17semantic edges are arranged opposite of
- 9:20the vertex with the same index in other
- 9:23words edge 0 lies between vertices 1 and
- 9:262.
- 9:27there's also a center tessellation
- 9:29factor tagged with the sv inside test
- 9:32factor semantic
- 9:33soon we'll visualize how all these
- 9:35factors affect the final tessellation
- 9:37pattern but for now realize that the
- 9:39edge factor is the number of times an
- 9:41edge will subdivide and the inside
- 9:43factor squared is roughly the number of
- 9:45times a new triangle will be created
- 9:48the patch constant function can also
- 9:50output other data but it must be tagged
- 9:52with a semantic like always
- 9:55this special bezier pose semantic is
- 9:58useful since it can take the form of a
- 10:00float3 array
- 10:01later we'll use this output control
- 10:03points for a bezier curved based
- 10:05smoothing algorithm but you can use it
- 10:07to store anything you need
- 10:10with that we're done with the whole
- 10:11stage let's move on to the domain stage
- 10:14the domain function also has a domain
- 10:16attribute which should match the whole
- 10:17function's output topology triangles in
- 10:20this case
- 10:21as arguments it receives the output of
- 10:23the whole function arranged into a patch
- 10:26as well as the output from the patch
- 10:27constant function
- 10:29finally it receives the barycentric
- 10:31coordinates of the vertex to work with
- 10:33tagged with sv underscore domain
- 10:35location
- 10:37the output structure is very similar to
- 10:40what you'd output from a vertex function
- 10:42it should contain the clip space
- 10:44position as well as any fields the
- 10:46fragment function needs for lighting
- 10:48also notice the barycentric interpolate
- 10:50macro it's really handy to interpolate
- 10:53any property in the patch structure
- 10:55using barycentric coordinates
- 10:58and that's it for the general structure
- 10:59of a tessellation shader let's take a
- 11:01closer look at partitioning modes and
- 11:03tessellation factors
- 11:05in the video description i've created a
- 11:07simple tessellation shader so you can
- 11:09test all this stuff out it has a
- 11:11property to assign tessellation factors
- 11:13as well as one to switch between
- 11:15partitioning modes using a keyword
- 11:17create a material for it and add it to
- 11:19the mesh
- 11:20to visualize the tessellation be sure to
- 11:22set the scene render mode to shaded
- 11:24wireframe and then play around with the
- 11:26factors
- 11:28you'll see that the edge factor
- 11:29corresponds to roughly the number of
- 11:31times edges will be subdivided while the
- 11:34inside factor adds complexity to the
- 11:36center
- 11:37also notice that setting any factor to 0
- 11:39or less will cause the mesh to disappear
- 11:42this will become important later
- 11:45now try setting the factors differently
- 11:46for each edge
- 11:48when we try more complicated algorithms
- 11:50it will be important that edges on
- 11:51adjoining triangles have the same
- 11:53tessellation factor if not you can get
- 11:55little holes in the mesh where the
- 11:56vertices don't match up to ensure that
- 11:58this doesn't happen try to restrict edge
- 12:01factors to depend only on vertices
- 12:03connected to that edge
- 12:05you may have also noticed some commented
- 12:07out properties in the shader uncomment
- 12:09these and change their patch constant
- 12:11function slightly to this
- 12:14is your mesh flickering even with
- 12:16positive factors why
- 12:18there's an oddity with the way the
- 12:20shader compiler handles tessellation
- 12:22factors
- 12:23in a bid to speed things up the compiler
- 12:25sometimes splits the patch constant
- 12:27function and calculates each factor in
- 12:29parallel
- 12:30this sometimes causes weird issues
- 12:33if you look at the frame debugger you'll
- 12:35see that the compiler stripped our two
- 12:37edge factor properties from the shader
- 12:39making them always equal to zero you can
- 12:41fix this by using a vector property with
- 12:43each component specifying one edge's
- 12:45factor so the compiler simply can't
- 12:48strip out anything in general if your
- 12:51tessellation factors are acting strange
- 12:53try rewriting this section of the patch
- 12:55constant function
- 12:58moving on to partitioning the modes are
- 13:00all pretty interesting
- 13:02the integer mode divides a number of
- 13:04times equal to the ceiling of the
- 13:05tessellation factor it has a generally
- 13:08nice pattern
- 13:13if you need tessellation factors to
- 13:14smoothly transition the fractional odd
- 13:17and fractional even modes will handle
- 13:19that they're so named because they can
- 13:21only fully subdivide on either odd or
- 13:23even numbers which is easier to
- 13:25understand when you see them in motion
- 13:29a quirk of the fractional even mode is
- 13:31that it always subdivides at least once
- 13:34since 2 is the lowest factor that it can
- 13:36handle
- 13:44the last pow2 mode seems to be identical
- 13:47to integer mode at least on my machine
- 13:50i would have guessed that it only
- 13:51subdivided when the factor is a power of
- 13:53two
- 13:54let me know how it works for you
- 14:00tessellation can be expensive but there
- 14:02are a few ways that we can speed it up
- 14:04since tessellation happens before the
- 14:06rasterization stage it cannot take
- 14:08advantage of the automatic frustum and
- 14:10winding culling that happens there
- 14:12thankfully we can implement it ourselves
- 14:14and avoid tessellating triangles that
- 14:16will just be thrown out later
- 14:19it's easy to call a triangle in the
- 14:20patch constant function just set the
- 14:22tessellation factors to zero and the
- 14:24tessellator will just ignore that patch
- 14:27first let's tackle frustum culling where
- 14:30we test each point of the triangle to
- 14:31see if it's out of bounds of the view
- 14:33frustum
- 14:34to do that we can use clip space
- 14:36positions of the triangle corners
- 14:39be sure to calculate it in the vertex
- 14:40function and pass it to the whole stage
- 14:44above the patch constant function write
- 14:46this function to test if a patch should
- 14:48be called passing the clip space
- 14:50positions of the triangle just return
- 14:52false for now
- 14:53above that write is out of bounds to
- 14:55check if a point is outside the bounds
- 14:57defined by upper and lower vectors and
- 15:00should frustum call to calculate these
- 15:02bounding vectors
- 15:04in clip space the w component of the
- 15:06position contains the outer bounds of
- 15:08the viewing frustum the camera viewable
- 15:11area so we can use that to calculate the
- 15:13bounding vectors the logic slightly
- 15:16differs between graphics api since some
- 15:18anchor the viewing preston at 0 and some
- 15:21at negative w
- 15:22luckily unity provides a constant with
- 15:24the correct value
- 15:26returning to should clip patch calls
- 15:29should frustum call on each point if
- 15:31they're all true the triangle is
- 15:33entirely outside the viewing area and
- 15:35should be called
- 15:37moving on to winding culling which is
- 15:39also called back face culling we need to
- 15:41calculate which side of the triangle is
- 15:43facing the camera culling only if the
- 15:45back side of the triangle is visible do
- 15:48that by calculating a normal vector for
- 15:50the plane containing the triangle and
- 15:52testing if it's roughly pointing towards
- 15:54the camera
- 15:55to find the normal vector we need two
- 15:57vectors tangent to the plane vectors
- 15:59pointing between the three triangle
- 16:00corners will do nicely their cross
- 16:02product is the normal
- 16:04and since we're working in clip space we
- 16:06need to normalize the position and apply
- 16:09perspective by dividing by the w
- 16:11component of each
- 16:12this gives roughly screen space
- 16:14positions
- 16:16use a dot product of the view direction
- 16:18and the triangle normal to find out if
- 16:20they're roughly pointing in the same
- 16:21direction
- 16:23since the camera points along the z-axis
- 16:25in clip space we can simplify all this
- 16:27to a comparison of the normal's
- 16:29z-coordinate
- 16:31i did say that the camera points along
- 16:32the z-axis but which way
- 16:35it turns out this depends again on your
- 16:37graphics api
- 16:38usually the view direction is in the
- 16:40negative z direction however this is
- 16:42flipped in opengl
- 16:44use this keyword to apply the correct
- 16:46comparison either way
- 16:47[Music]
- 16:48finally in should clip patch call should
- 16:51backface call as well
- 16:53then in the patch constant function if
- 16:55should clip patch returns true set all
- 16:58edge vectors to zero
- 17:01back in unity you might notice that the
- 17:03shader culls some faces of your mesh
- 17:05when it shouldn't even if you don't see
- 17:07that now you certainly will later on
- 17:09when adding vertex displacement
- 17:11add some leeway to these calculations by
- 17:14introducing frustum and winding cold
- 17:15tolerance properties
- 17:18for frustum culling add the tolerance to
- 17:20each bound while for winding culling
- 17:22compare the normal z-axis with a
- 17:24tolerance instead of zero
- 17:27adjust these properties as needed while
- 17:28you're adding features
- 17:33another way to optimize tessellation is
- 17:35to lower factors when and where a mesh
- 17:37doesn't need to be subdivided
- 17:39there are a few ways to go about this
- 17:42say we're working with a mesh that has
- 17:43some large faces but many smaller ones
- 17:46we really only need to tessellate the
- 17:48large faces
- 17:49one way to do this is to calculate
- 17:51tessellation factors proportionate to
- 17:53the edge length
- 17:55above the patch constant function define
- 17:58this function to calculate the
- 17:59tessellation factor for an edge bound by
- 18:01two vertices pass the world space
- 18:04position of each vertex as well as a
- 18:06scale and bias value set the factor to
- 18:08the scale plus the bias making sure that
- 18:11the result is never less than 1 so it
- 18:13doesn't get cold
- 18:15this creates a constant edge factor
- 18:17now to add world space edge length set
- 18:20the factor to the distance between the
- 18:21vertex positions divided by the scale
- 18:24in this scheme the edge subdivides
- 18:26aiming to keep the divided edge lengths
- 18:28roughly equal to the scale value add
- 18:31shader properties for the scale and bias
- 18:33values and back in the patch constant
- 18:35function call this new function for each
- 18:38edge factor passing in the new
- 18:39properties and the appropriate vertex
- 18:41positions
- 18:43remember edges are arranged across from
- 18:45the vertex sharing its index in the
- 18:47array
- 18:48the inside factor should be an average
- 18:50of all the edge factors
- 18:52this code worked just fine for me but if
- 18:54the compiler is not playing nice and
- 18:56you're seeing inconsistent edge factors
- 18:58just call edge tessellation factor again
- 19:00instead of using previously cached
- 19:02values
- 19:08alright neat
- 19:09but maybe we can test light based on an
- 19:11edge's length in screen space
- 19:14due to culling we already have the clip
- 19:16space positions so it shouldn't be so
- 19:18difficult to do
- 19:20in edge tessellation factor add
- 19:22arguments for each vertex's clip space
- 19:24position then calculate the factor by
- 19:27using clip space positions instead of
- 19:29world space positions
- 19:31make two adjustments
- 19:32first apply perspective by dividing the
- 19:35positions by their w component next
- 19:38multiply by underscore screen params.y
- 19:41which contains the height of the screen
- 19:43in pixels
- 19:44now we can specify the scale in pixels
- 19:46which just makes sense for screen space
- 19:49in the patch constant function pass the
- 19:51clip space positions along with the
- 19:52world space positions
- 19:55this looks good too but sometimes not
- 19:57quite right
- 19:59what if we use the distance to the
- 20:00camera somehow to help things out
- 20:03in edge tessellation factor find the
- 20:06length in world space between the two
- 20:07vertices then calculate the distance
- 20:10from the center of this edge to the
- 20:11camera the camera position is different
- 20:14in the various render pipelines but you
- 20:16can use this function to get its
- 20:17position in urp
- 20:19divide the length by the scale
- 20:21multiplied by the distance to the camera
- 20:23which effectively lowers the scale when
- 20:25close to the screen
- 20:27i prefer the effect with a quadratic
- 20:28curve but it's up to you
- 20:31i found that this approach gives me the
- 20:33best results but your mileage may vary
- 20:36you can use a keyword to switch between
- 20:38algorithms if you'd like
- 20:39so these heuristics try to guess the
- 20:41appropriate tessellation factors for
- 20:43your mesh but if you have an idea of how
- 20:46the mesh should tessellate yourself try
- 20:48storing tessellation factor multipliers
- 20:50in the mesh's data this is useful if you
- 20:52have an area with large flat faces where
- 20:55you'll never need to add detail
- 20:57for demonstration purposes i'll store
- 20:59these multipliers in the green channel
- 21:01of the meshes vertex colors but you can
- 21:04use text cords or anything else
- 21:06in blender or your modeling program of
- 21:08choice paint the area that you don't
- 21:10want to tessellate black
- 21:13in your shader pass the vertex colors
- 21:15down to your whole input structure
- 21:18in the patch constant function calculate
- 21:20a multiplier for each edge by averaging
- 21:22the green channel of connecting vertices
- 21:24and then pass it as a new multiplier
- 21:26into the edge tessellation factor
- 21:28function
- 21:29multiply that into the final calculation
- 21:33then check it out in unity
- 21:39here's another useful technique to use
- 21:41with some type of deforming force field
- 21:43or sdf
- 21:45in this example i deform a plane based
- 21:47on proximity to these little spheres
- 21:50i know that if the vertex is far enough
- 21:52away from all the spheres it doesn't
- 21:53deform so we don't need to tessellate
- 21:56connected triangles for now just ignore
- 21:58the actual deforming logic that will
- 22:01come later in the tutorial focus on
- 22:03calculating the tessellation factors
- 22:06in your patch constant function evaluate
- 22:09the deformation amount for each vertex
- 22:11if it results in a deformation then we
- 22:13should pass a multiplier of one
- 22:15otherwise past zero
- 22:21[Music]
- 22:23an easy way to add detail to a mesh is
- 22:26through high resolution textures
- 22:28for instance normal maps vary normal
- 22:30vectors per pixel which affect the
- 22:33apparent shape of a surface however this
- 22:35technique does not really change any
- 22:37mesh geometry nowhere as is more
- 22:39apparent than on a mesh's silhouette
- 22:42zoom up close and even 4k textures can't
- 22:45hide a jagged and pointy edge
- 22:47in this section i'll describe a few
- 22:49algorithms to smooth mesh geometry out
- 22:52using tessellation all of these
- 22:54strategies involve offsetting vertexes
- 22:56in the domain function
- 22:59through simple barycentric interpolation
- 23:02all new vertices are limited to the
- 23:03original triangle's plane however what
- 23:06if we use the corner's normal vector to
- 23:08construct a curved triangle
- 23:10the simplest technique to achieve this
- 23:12is called phong tessellation
- 23:14you might have heard of phong shading
- 23:16which is the smooth shading technique of
- 23:18linearly interpolated normal vectors
- 23:20fong tessellation tries to recapture
- 23:22that simplicity and efficiency while
- 23:24positioning tessellated points
- 23:27it works like this
- 23:28first calculate the flat very
- 23:30centrically interpolated position for a
- 23:32point
- 23:33then imagine three tangent planes
- 23:34emanating from each triangle corner
- 23:36normal to their respective normal vector
- 23:42next project the flat position onto each
- 23:45of these planes which is equivalent to
- 23:47finding the nearest point on that plane
- 23:50finally compute the barycentrically
- 23:52interpolated position again using these
- 23:54new projected points
- 23:57the math behind this isn't too
- 23:59complicated we already know how to deal
- 24:01with barycentric coordinates and to
- 24:03project a point onto a plane find the
- 24:05difference between the point and any
- 24:07other in the plane the triangle corners
- 24:09will work for this
- 24:10then project that vector onto the
- 24:12plane's normal vector and subtract the
- 24:14result from the original point here's
- 24:17the algorithm for fong tessellation in
- 24:19full
- 24:20now add this to your domain function
- 24:22make sure to use this new adjusted
- 24:24position when calculating clip space
- 24:28try it out on a model at first it may
- 24:30look a little too puffed up that's
- 24:32pretty common with fong tesselization we
- 24:34can improve this by adding a smoothing
- 24:36factor property
- 24:38interpolate between the flat position
- 24:40and the fong tessellated position using
- 24:42this
- 24:44factor which helps quite a bit
- 24:51[Music]
- 24:52some models may need a little touching
- 24:54up as well
- 24:56if your model has sharp edges try adding
- 24:59edge loops very close to the sharp edge
- 25:02making long thin faces
- 25:04looking at the font tessellation
- 25:06algorithm you can see why this works if
- 25:08the normal vectors of each vertex are
- 25:10very close to parallel the font position
- 25:13will be very close to the flat position
- 25:14as well
- 25:16another technique you can try is baking
- 25:18smoothing factors into your mesh's data
- 25:20for example in the red channel of its
- 25:22vertex colors
- 25:24simply paint the red channel black in
- 25:26areas that you don't want to bend
- 25:28pass the vertex colors all the way down
- 25:30to the domain function calculate the
- 25:32barycentric interpolation of the red
- 25:34vertex color channel and multiply that
- 25:37with a smoothing factor
- 25:38[Music]
- 25:40fong testosterone gives pretty good
- 25:42results and is also pretty cheap all
- 25:44things considered however if you need
- 25:46higher quality smoothing there's another
- 25:48option p in triangles
- 25:50this technique construct curved
- 25:52triangles similar to bezier curves it's
- 25:55quite a bit more expensive than the fong
- 25:57method but let's try it out
- 25:59we can save a lot of time by
- 26:01pre-computing bezier control points for
- 26:03use when positioning tessellated points
- 26:05in the domain function
- 26:06control points are constant per triangle
- 26:09so the patch constant function is
- 26:11perfect for this use we'll need 10
- 26:13control points the triangle corners
- 26:16a pair along each triangle edge
- 26:19and one in the triangle center
- 26:21let's take a look at calculating each
- 26:23control point
- 26:24the corners remain as they are they'll
- 26:26help ensure that the triangle never
- 26:28escapes its original position too much
- 26:31for the edge pairs use a similar
- 26:33algorithm to fong tesselization take
- 26:36this point one third along the edge from
- 26:38corner a to b to calculate its position
- 26:41first project b onto the plane defined
- 26:44by a's normal then take the average of
- 26:47this new point in a waiting a twice
- 26:50for the other point on the edge do the
- 26:52same operation mirroring a and b
- 26:56then continue with the other two edge
- 26:58pairs
- 27:00finally for the center point find the
- 27:02average of all of these new edge pair
- 27:05control points which i'll call e
- 27:07as well as the average of the triangle
- 27:09corners i'll call t
- 27:11the center control point is e plus the
- 27:14difference of e and t halved which gives
- 27:17a nice rounded center
- 27:20using these control points it's possible
- 27:22to compute any point on this bendy
- 27:24triangle using barycentric coordinates
- 27:27this is a formula which looks similar to
- 27:30a cubic bezier curve formula notice how
- 27:32the barycentric coordinates appear in
- 27:34terms with their corresponding corners
- 27:37the center point being an even
- 27:39combination of all three
- 27:41if you'd like to learn more about bezier
- 27:42curves i've linked some excellent
- 27:44resources in the video description
- 27:46[Music]
- 27:48to program all this store the control
- 27:50points in the patch constant output
- 27:52structure using the bezier pose semantic
- 27:55tag a seven element float3 array with it
- 27:58in the patch constant output struct
- 28:00why only seven well the patch already
- 28:02contains a triangle corner position so
- 28:04there's no reason to waste memory with
- 28:06them
- 28:06this function calculates the bezier
- 28:08control points using the algorithm
- 28:10described earlier call that in the patch
- 28:12constant function but only if the
- 28:14triangle isn't culled
- 28:16calculate the final point in the domain
- 28:18stage this function implements the
- 28:20bezier curve calculation using the
- 28:22control points from the whole stage i've
- 28:24also added an interpolation between the
- 28:26curved position and the flat position
- 28:28like we did with fong tesselization
- 28:30substitute this function for fong
- 28:32tesselization in your domain function
- 28:34[Music]
- 28:36back in the scene editor you'll see that
- 28:38this does give really good results
- 28:40usually slightly cleaner than fong
- 28:42teslazation it's up to you if the added
- 28:44complexity is worth it
- 28:46throughout all this we haven't touched
- 28:48normal vectors at all interpolating
- 28:50normal vectors linearly is usually okay
- 28:53but if your mesh has many divots and
- 28:55inflections shading might be improved by
- 28:58interpolating normals differently
- 29:00perhaps quadratically we'll use another
- 29:02bezier curve-like algorithm for this
- 29:04at first it might seem strange to use a
- 29:07bezier curve for normal vectors but as
- 29:09long as we normalize the final results
- 29:11it will work just fine
- 29:13quadratic bezier curves only need three
- 29:16control points so you only have to
- 29:18compute one on the middle of each
- 29:19triangle edge
- 29:21again the triangle corners will retain
- 29:23their original normal vectors to compute
- 29:25a control vector for the point halfway
- 29:28between the corners a and b follow these
- 29:30steps
- 29:31first find the average normal of a and b
- 29:33second construct a plane perpendicular
- 29:36to the edge connecting a and b
- 29:38finally reflect the average vector
- 29:40across this plane
- 29:42notice that when the normals are similar
- 29:44but slanted relative to the triangle
- 29:46plane the control normal points in the
- 29:48opposite direction this will create
- 29:50bumpy shading as if the surface is
- 29:52warping
- 29:53calculate the control vectors for the
- 29:55remaining edges
- 29:58[Music]
- 30:02to add this algorithm to your shader
- 30:04first add three more slots to the bezier
- 30:06control point array
- 30:08then call this function in your patch
- 30:10constant function which implements the
- 30:12formula explained before
- 30:14in your domain function calculate a
- 30:16quadratic bezier formula similarly to
- 30:18the position
- 30:20apply the smoothing factor to
- 30:21interpolate with the flat normal and be
- 30:23sure to normalize the final result
- 30:27there's one other thing to consider with
- 30:28all this the tangent vector it must
- 30:31always be perpendicular to the normal
- 30:33but if we change the normal vector it
- 30:35might not be
- 30:36to fix this find the barycentrically
- 30:38interpolated tangent vector and then
- 30:40take its cross product with the
- 30:42barycentrically interpolated normal
- 30:45then take that vector's cross product
- 30:47with the smooth normal the resultant
- 30:50tangent vector is once again orthogonal
- 30:52to the normal vector as well as the
- 30:54original meshes by tangent
- 30:57this should preserve tangent space
- 30:59nicely
- 31:02and that brings us to the end of this
- 31:03section on silhouette smoothing and
- 31:05bezier triangles
- 31:06this is a real magic behind teslazation
- 31:09and it makes it a powerful tool when
- 31:11combined with appropriately designed
- 31:12models
- 31:14experiment with all this and see what
- 31:16you can create
- 31:23another of teslazation's most common
- 31:25uses is adding extra geometric details
- 31:28to a mesh
- 31:29say you have a rough surface with mini
- 31:31bumps traditionally an artist would use
- 31:33a normal map to approximate the lighting
- 31:34for such a bumpy surface
- 31:36the model itself is not actually bumpy
- 31:39as you can clearly see if you view the
- 31:41surface's profile or shadow
- 31:43with teslazation however it's possible
- 31:46to modify the mesh to more closely
- 31:48resemble a complex surface
- 31:50the most common way to do this is using
- 31:52a height map
- 31:53also known as a bump map these grayscale
- 31:55textures encode height offsets in its
- 31:58color data
- 31:59the idea is simple read a height from
- 32:01the texture and offset vertices along
- 32:03their normal vectors by this height to
- 32:06implement that in a shader add a texture
- 32:08property for the height map and then
- 32:10sample it in the domain function
- 32:12remember sample texture 2d is only
- 32:14available in the fragment stage because
- 32:17it uses partial derivatives use sample
- 32:19texture 2d lod here accordingly you can
- 32:22turn off mipmaps for height maps used
- 32:24this way
- 32:26regardless add the sampled value to the
- 32:28vertex's world position by using it to
- 32:30scale the normal vector you can combine
- 32:33this with the smoothing techniques
- 32:34discussed above or just use the flat
- 32:37interpolated position in normal either
- 32:39way the algorithm is really that simple
- 32:42add an altitude property to adjust the
- 32:44height
- 32:45you might notice that if you use a
- 32:46height map without a matching normal map
- 32:49the mesh will look quite flat
- 32:51this is because the height map does not
- 32:53affect normal vectors
- 32:55although i would still recommend using a
- 32:57normal map it is possible to calculate
- 32:59lower quality tangent space normals from
- 33:02the height map alone
- 33:04in this case turn mipmaps back on for
- 33:06your height map since we'll sample it
- 33:08again in the fragment stage
- 33:11in the shader add this variable which
- 33:13holds the size of one texel or pixel on
- 33:16a texture in uv units make sure that the
- 33:19name matches a texture in your shader
- 33:20and unity will automagically calculate
- 33:23and set it for you then write this
- 33:25function which samples the height map in
- 33:27each neighboring pixel around a given uv
- 33:30coordinate from this we can calculate a
- 33:32tangent space normal with a little
- 33:34algebra divide the change in height of
- 33:36pixels across from one another with the
- 33:38change in uv space this gives us the
- 33:41slope in the u and the v directions
- 33:42which correspond to the x and y
- 33:44components of the tangent space normal
- 33:47multiply these with a scaling factor to
- 33:49adjust the overall strength of this
- 33:51improvised normal map
- 33:53and then normalize the final result
- 33:55convert this to world space like any
- 33:57other tangent space vector
- 34:00you'll probably need to adjust the
- 34:01normal strength until you get something
- 34:03that looks good and even then you might
- 34:05notice that these normals are not quite
- 34:07as detailed as a normal map it can't
- 34:09really be helped without taking more
- 34:11texture samples and that really starts
- 34:13to add up
- 34:14for this reason only use this technique
- 34:16if you're in a pinch there are many
- 34:18tools online which you can use to
- 34:19generate normal maps from a height map
- 34:21anyway
- 34:23height maps can also take the form of a
- 34:24function like purlin noise or an sdf in
- 34:28these cases evaluate the height function
- 34:30instead of sampling a height map
- 34:32here we're forced to calculate a normal
- 34:34vector and it can be a little tricky to
- 34:36figure out the math it differs per
- 34:38function but on the plus side they're
- 34:40often mathematically exact
- 34:43in this example i created a height map
- 34:45from perlin noise this has well-defined
- 34:47partial derivatives so i was able to
- 34:49calculate the normal vector like this
- 34:52if you scale the noise in any way be
- 34:54sure to also scale the resulting normal
- 34:56vector just remember that normals must
- 34:58be scaled inversely to geometry meaning
- 35:01divide instead of multiply
- 35:05you could calculate the normal along
- 35:06with the position in the domain function
- 35:08and then use that interpolated value in
- 35:10the fragment function
- 35:12however you can also just calculate the
- 35:14normal in the fragment function which
- 35:16gives a nicer more detailed result
- 35:19in this other example i created a simple
- 35:21sdf or distance function from points
- 35:24centered on these three little spheres
- 35:26when the sdf passes a threshold at any
- 35:29point on this mesh i deform the point
- 35:31backwards
- 35:33to calculate the normal in this
- 35:34situation i had to use another trick
- 35:38create two new points offset slightly
- 35:40from the original point along its
- 35:42tangent and by tangent vectors
- 35:44calculate the sdf at all three points
- 35:47and then apply the offsets
- 35:49form a triangle with these deformed
- 35:51points and calculate the normal vector
- 35:53of the plane containing it using the
- 35:55cross product this gives something nice
- 35:57for lighting
- 35:59this method usually is quite good for
- 36:01continuous functions determined solely
- 36:03by position so keep that in mind
- 36:10as you can see teslazation is a
- 36:13complicated subject but it opens up many
- 36:15doors in the world of graphics
- 36:16programming
- 36:18although it can be expensive well-placed
- 36:20teslazation can really polish up your
- 36:22models and give your game that final
- 36:24push over the finish line
- 36:26i hope i've shown off a lot of
- 36:27interesting uses for teslazation
- 36:30including advanced culling per triangle
- 36:33math
- 36:34smooth silhouettes level of detail
- 36:36quadratic normal vectors real height
- 36:39maps and procedural geometry
- 36:41personally i'll be using teslazation for
- 36:44level of detail and wind effects in my
- 36:46upcoming grass system
- 36:48if you liked this tutorial and you'd
- 36:50like to watch more like it please
- 36:51consider subscribing to my channel i
- 36:54want to take another moment to thank all
- 36:55of my patrons for helping make this
- 36:57video possible and give a big shout out
- 36:59to my next gen patron creepy dooby doo
- 37:02thank you all for your support
- 37:04it means a lot to me
- 37:05if anyone would like to download all the
- 37:07example shaders from this tutorial and
- 37:09all of my other tutorials in fact
- 37:11consider joining my patreon
- 37:13you can also really help me out by
- 37:15liking this video and leaving a comment
- 37:18did you learn anything in this tutorial
- 37:20how do you want to make use of
- 37:21teslazation in your project let's chat
- 37:23about it
- 37:24thanks so much for watching and make
- 37:26games
- 37:32[Music]
- 37:46you
About this transcript
This page contains the full transcript of Mastering Tessellation Shaders in Unity! Easy LoD, Curved Triangles, Height Maps | Game Dev Tutorial by Ned Makes Games, generated from the public captions YouTube serves with the video. The transcript has 6,167 words across 1,050 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to translate the transcript into 100+ languages.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, translation to 100+ languages, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.