diff options
Diffstat (limited to 'source/blender/draw/engines/eevee/shaders/hair_lib.glsl')
-rw-r--r-- | source/blender/draw/engines/eevee/shaders/hair_lib.glsl | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/source/blender/draw/engines/eevee/shaders/hair_lib.glsl b/source/blender/draw/engines/eevee/shaders/hair_lib.glsl new file mode 100644 index 00000000000..489ee44dd0f --- /dev/null +++ b/source/blender/draw/engines/eevee/shaders/hair_lib.glsl @@ -0,0 +1,330 @@ +#ifdef HAIR_SHADER_FIBERS + +#define M_PI 3.1415926535897932384626433832795 + +mat4 translate(vec3 co) +{ + return mat4(1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + co.x, co.y, co.z, 1.0); +} + +mat4 rotateX(float angle) +{ + float ca = cos(angle); + float sa = sin(angle); + return mat4(1.0, 0.0, 0.0, 0.0, + 0.0, ca, sa, 0.0, + 0.0, -sa, ca, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +mat4 rotateY(float angle) +{ + float ca = cos(angle); + float sa = sin(angle); + return mat4(ca, 0.0, sa, 0.0, + 0.0, 1.0, 0.0, 0.0, + -sa, 0.0, ca, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +mat4 rotateZ(float angle) +{ + float ca = cos(angle); + float sa = sin(angle); + return mat4(ca, sa, 0.0, 0.0, + -sa, ca, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0); +} + +/* Hair Displacement */ + +/* Note: The deformer functions below calculate a new location vector + * as well as a new direction (aka "normal"), using the partial derivatives of the transformation. + * + * Each transformation function can depend on the location L as well as the curve parameter t: + * + * Lnew = f(L, t) + * => dLnew/dt = del f/del L * dL/dt + del f/del t + * + * The first term is the Jacobian of the function f, dL/dt is the original direction vector. + * Some more information can be found here: + * https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch42.html + */ + +/* Hairs tend to stick together and run in parallel. + * The effect increases with distance from the root, + * as the stresses pulling fibers apart decrease. + */ +struct ClumpParams +{ + /* Relative strand thickness at the tip. + * (0.0, 1.0] + * 0.0 : Strand clumps into a single line + * 1.0 : Strand does not clump at all + * (> 1.0 is possible but not recommended) + */ + float thickness; +}; + +/* Hairs often don't have a circular cross section, but are somewhat flattened. + * This creates the local bending which results in the typical curly hair geometry. + */ +struct CurlParams +{ + /* Radius of the curls. + * >= 0.0 + */ + float radius; + /* Steepness of curls + * < 0.0 : Clockwise curls + * > 0.0 : Anti-clockwise curls + */ + float angle; +}; + +struct DeformParams +{ + /* Strand tapering with distance from the root. + * < 1.0 : Taper is concave (recommended) + * = 1.0 : Taper is linear + * > 1.0 : Taper is convex (not recommended) + */ + float taper; + + ClumpParams clump; + CurlParams curl; +}; + +void deform_taper(DeformParams params, float t, out float taper, out float dtaper) +{ + taper = pow(t, params.taper); + dtaper = (t > 0.0) ? taper * params.taper / t : 0.0; +} + +void deform_clump(DeformParams params, + float t, float tscale, mat4 target_matrix, + inout vec3 co, inout vec3 tang) +{ + float taper, dtaper; + deform_taper(params, t, taper, dtaper); + float factor = (1.0 - params.clump.thickness) * taper; + float dfactor = (1.0 - params.clump.thickness) * dtaper; + + vec3 target_co = target_matrix[3].xyz; + vec3 target_tang = target_matrix[0].xyz; + vec3 nco = co + (target_co - co) * factor; + vec3 ntang = normalize(tang + (target_tang - tang) * factor + (target_co - co) * dfactor); + + co = nco; + tang = ntang; +} + +void deform_curl(DeformParams params, + float t, float tscale, + inout mat4 target_matrix) +{ + float pitch = 2.0*M_PI * params.curl.radius * tan(params.curl.angle); + float turns = tscale / (params.curl.radius * tan(params.curl.angle)); + float angle = t * turns; + mat4 local_mat = rotateX(angle) * translate(vec3(0.0, params.curl.radius, 0.0)) * rotateY(params.curl.angle); + target_matrix = target_matrix * local_mat; +} + +void deform_fiber(DeformParams params, + float t, float tscale, mat4 target_matrix, + inout vec3 loc, inout vec3 tang) +{ + deform_curl(params, t, tscale, target_matrix); + deform_clump(params, t, tscale, target_matrix, loc, tang); +} + +/*===================================*/ +/* Hair Interpolation */ + +#define FIBER_RIBBON + +uniform sampler2D fiber_data; + +uniform int fiber_start; +uniform int strand_map_start; +uniform int strand_vertex_start; + +uniform float ribbon_width; +uniform vec2 viewport_size; + +#define INDEX_INVALID -1 + +vec2 read_texdata(int offset) +{ + ivec2 offset2 = ivec2(offset % HAIR_SHADER_TEX_WIDTH, offset / HAIR_SHADER_TEX_WIDTH); + return texelFetch(fiber_data, offset2, 0).rg; +} + +mat4 mat4_from_vectors(vec3 nor, vec3 tang, vec3 co) +{ + tang = normalize(tang); + vec3 xnor = normalize(cross(nor, tang)); + return mat4(vec4(tang, 0.0), vec4(xnor, 0.0), vec4(cross(tang, xnor), 0.0), vec4(co, 1.0)); +} + +void get_strand_data(int index, out int start, out int count) +{ + int offset = strand_map_start + index; + vec2 a = read_texdata(offset); + + start = floatBitsToInt(a.r); + count = floatBitsToInt(a.g); +} + +void get_strand_vertex(int index, out vec3 co, out vec3 nor, out vec3 tang) +{ + int offset = strand_vertex_start + index * 5; + vec2 a = read_texdata(offset); + vec2 b = read_texdata(offset + 1); + vec2 c = read_texdata(offset + 2); + vec2 d = read_texdata(offset + 3); + vec2 e = read_texdata(offset + 4); + + co = vec3(a.rg, b.r); + nor = vec3(b.g, c.rg); + tang = vec3(d.rg, e.r); +} + +void get_strand_root(int index, out vec3 co) +{ + int offset = strand_vertex_start + index * 5; + vec2 a = read_texdata(offset); + vec2 b = read_texdata(offset + 1); + + co = vec3(a.rg, b.r); +} + +void get_fiber_data(int fiber_index, out ivec4 parent_index, out vec4 parent_weight, out vec3 pos) +{ + int offset = fiber_start + fiber_index * 6; + vec2 a = read_texdata(offset); + vec2 b = read_texdata(offset + 1); + vec2 c = read_texdata(offset + 2); + vec2 d = read_texdata(offset + 3); + vec2 e = read_texdata(offset + 4); + vec2 f = read_texdata(offset + 5); + + parent_index = ivec4(floatBitsToInt(a.rg), floatBitsToInt(b.rg)); + parent_weight = vec4(c.rg, d.rg); + pos = vec3(e.rg, f.r); +} + +void interpolate_parent_curve_full(int index, float curve_param, out vec3 co, out vec3 nor, out vec3 tang, out vec3 rootco) +{ + int start, count; + get_strand_data(index, start, count); + + get_strand_root(start, rootco); + +#if 0 // Don't have to worry about out-of-bounds segment here, as long as lerpfac becomes 0.0 when curve_param==1.0 + float maxlen = float(count - 1); + float arclength = curve_param * maxlen; + int segment = min(int(arclength), count - 2); + float lerpfac = arclength - min(floor(arclength), maxlen - 1.0); +#else + float maxlen = float(count - 1); + float arclength = curve_param * maxlen; + int segment = int(arclength); + float lerpfac = arclength - floor(arclength); +#endif + + vec3 co0, nor0, tang0; + vec3 co1, nor1, tang1; + get_strand_vertex(start + segment, co0, nor0, tang0); + get_strand_vertex(start + segment + 1, co1, nor1, tang1); + + co = mix(co0, co1, lerpfac) - rootco; + nor = mix(nor0, nor1, lerpfac); + tang = mix(tang0, tang1, lerpfac); +} + +void interpolate_parent_curve(int index, float curve_param, out vec3 co, out vec3 tang) +{ + vec3 nor; + vec3 rootco; + interpolate_parent_curve_full(index, curve_param, co, nor, tang, rootco); +} + +void interpolate_vertex(int fiber_index, float curve_param, + out vec3 co, out vec3 tang, + out mat4 target_matrix) +{ + co = vec3(0.0); + tang = vec3(0.0); + target_matrix = mat4(1.0); + + ivec4 parent_index; + vec4 parent_weight; + vec3 rootco; + get_fiber_data(fiber_index, parent_index, parent_weight, rootco); + + if (parent_index.x != INDEX_INVALID) { + vec3 pco, pnor, ptang, prootco; + interpolate_parent_curve_full(parent_index.x, curve_param, pco, pnor, ptang, prootco); + co += parent_weight.x * pco; + tang += parent_weight.x * normalize(ptang); + + target_matrix = mat4_from_vectors(pnor, ptang, pco + prootco); + } + if (parent_index.y != INDEX_INVALID) { + vec3 pco, ptang; + interpolate_parent_curve(parent_index.y, curve_param, pco, ptang); + co += parent_weight.y * pco; + tang += parent_weight.y * normalize(ptang); + } + if (parent_index.z != INDEX_INVALID) { + vec3 pco, ptang; + interpolate_parent_curve(parent_index.z, curve_param, pco, ptang); + co += parent_weight.z * pco; + tang += parent_weight.z * normalize(ptang); + } + if (parent_index.w != INDEX_INVALID) { + vec3 pco, ptang; + interpolate_parent_curve(parent_index.w, curve_param, pco, ptang); + co += parent_weight.w * pco; + tang += parent_weight.w * normalize(ptang); + } + + co += rootco; + tang = normalize(tang); +} + +void hair_fiber_get_vertex(int fiber_index, float curve_param, mat4 ModelViewMatrix, out vec3 pos, out vec3 nor, out vec2 view_offset) +{ + vec3 target_loc; + mat4 target_matrix; + interpolate_vertex(fiber_index, curve_param, pos, nor, target_matrix); + + DeformParams deform_params; + deform_params.taper = 2.0; + deform_params.clump.thickness = 0.15; + deform_params.curl.radius = 0.1; + deform_params.curl.angle = 0.2; + // TODO define proper curve scale, independent of subdivision! + //deform_fiber(deform_params, curve_param, 1.0, target_matrix, pos, nor); + +#ifdef FIBER_RIBBON + float ribbon_side = (float(gl_VertexID % 2) - 0.5) * ribbon_width; + { + vec4 view_nor = ModelViewMatrix * vec4(nor, 0.0); + view_offset = vec2(view_nor.y, -view_nor.x); + float L = length(view_offset); + if (L > 0.0) { + view_offset *= ribbon_side / (L * viewport_size); + } + } +#else + view_offset = vec2(0.0); +#endif +} + +#endif /*HAIR_SHADER_FIBERS*/ |