Files
test/source/blender/draw/intern/shaders/draw_curves_lib.glsl
Clément Foucault fe213f80a4 GPU: Shader: Make info files generated
This is the first step of moving the create infos
back inside shader sources.

All info files are now treated as source files.
However, they are not considered in the include tree
yet. This will come in another following PR.

Each shader source file now generate a `.info` file
containing only the create info declarations.

This renames all info files so that they do not
conflict with their previous versions that were
copied (non-generated).

Pull Request: https://projects.blender.org/blender/blender/pulls/146676
2025-09-25 10:57:02 +02:00

287 lines
8.8 KiB
GLSL

/* SPDX-FileCopyrightText: 2018-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "draw_object_infos_infos.hh"
#include "gpu_shader_math_constants_lib.glsl"
#include "gpu_shader_math_matrix_conversion_lib.glsl"
#include "gpu_shader_math_matrix_transform_lib.glsl"
/**
* Library to create hairs dynamically from control points.
* This is less bandwidth intensive than fetching the vertex attributes
* but does more ALU work per vertex. This also reduces the amount
* of data the CPU has to precompute and transfer for each update.
*/
/* Avoid including hair functionality for shaders and materials which do not require hair.
* Required to prevent compilation failure for missing shader inputs and uniforms when hair library
* is included via other libraries. These are only specified in the ShaderCreateInfo when needed.
*/
#ifdef CURVES_SHADER
# ifndef DRW_HAIR_INFO
# error Ensure createInfo includes draw_hair.
# endif
namespace curves {
struct Segment {
/* Index of this segment. Used to load indirection buffer. */
uint id;
/* Vertex index inside this segment. */
uint v_idx;
/* Restart triangle strip if true. Only for cylinder topology. */
bool is_end_of_segment;
};
/* Indirection buffer indexing. */
Segment segment_get(uint vertex_id)
{
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
Segment segment;
segment.id = vertex_id / drw_curves.vertex_per_segment;
segment.v_idx = vertex_id % drw_curves.vertex_per_segment;
segment.is_end_of_segment = is_cylinder && (segment.v_idx == drw_curves.vertex_per_segment - 1);
if (is_cylinder && !segment.is_end_of_segment && (segment.id & 1u) == 0u) {
/* The topology is not actually restarted and the winding order changes (because we skip an odd
* number of triangles). So we have to manually reverse the winding so that is stays
* consistent.
*/
segment.v_idx ^= 1u;
}
return segment;
}
/* Result of interpreting the indirection buffer.
* The indirection buffer maps drawn segments back to their curves and curve segments.
* This is needed for attribute loading. */
struct Indirection {
/* Can be equal to INT_MAX with ribbon draw type. */
int curve_id;
/* Segment ID starting at 0 at curve start. */
int curve_segment;
/* Restart triangle strip if true. Only for ribbon topology. */
bool is_end_of_curve;
/* Does these vertices correspond to the last point of a cyclic curve (duplicate of start). */
bool is_cyclic_point;
};
Indirection indirection_get(Segment segment)
{
const auto &curves_indirection_buf = sampler_get(draw_curves, curves_indirection_buf);
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
Indirection ind;
ind.is_cyclic_point = false;
ind.is_end_of_curve = false;
ind.curve_id = 0;
constexpr int cyclic_endpoint_pivot = INT_MAX / 2;
constexpr int end_of_curve = INT_MAX;
int indirection_value = texelFetch(curves_indirection_buf, int(segment.id)).r;
if (indirection_value == end_of_curve) {
ind.curve_segment = 0;
ind.is_end_of_curve = true;
}
else if (indirection_value >= 0) {
/* This is start of curve. The indirection value is the curve ID. */
ind.curve_segment = 0;
ind.curve_id = indirection_value;
}
else if (indirection_value <= -cyclic_endpoint_pivot) {
/* This is the last segment of a cyclic curve. The indirection value is the offset to the start
* of the curve offsetted by cyclic_endpoint_pivot. */
ind.curve_segment = -indirection_value - cyclic_endpoint_pivot;
ind.is_cyclic_point = true;
}
else {
/* This is a normal segment. The indirection value is the offset to the start of the curve. */
ind.curve_segment = -indirection_value;
}
if (ind.curve_segment != 0) {
ind.curve_id = texelFetch(curves_indirection_buf, int(segment.id) - ind.curve_segment).r;
}
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
if (is_cylinder) {
bool is_end_of_segment = (segment.v_idx & 1u) != 0u;
ind.curve_segment += int(is_end_of_segment);
/* Only the end of the segment is to be considered the cyclic point. */
if (!is_end_of_segment) {
ind.is_cyclic_point = false;
}
}
return ind;
}
int point_id_get(Segment segment, Indirection indirection)
{
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
if (is_cylinder) {
return int(segment.id) + indirection.curve_id + int(segment.v_idx & 1u);
}
return int(segment.id) - indirection.curve_id;
}
float azimuthal_offset_get(Segment segment)
{
const auto &drw_curves = buffer_get(draw_curves_infos, drw_curves);
float offset;
const bool is_strand = drw_curves.half_cylinder_face_count == 0u;
const bool is_cylinder = drw_curves.half_cylinder_face_count > 1u;
if (is_strand) {
offset = 0.5f;
}
else if (is_cylinder) {
offset = float(segment.v_idx >> 1) / float(drw_curves.half_cylinder_face_count);
}
else {
offset = float(segment.v_idx);
}
return offset * 2.0f - 1.0f;
}
float4 point_position_and_radius_get(uint point_id)
{
const auto &curves_pos_rad_buf = buffer_get(draw_curves, curves_pos_rad_buf);
return texelFetch(curves_pos_rad_buf, int(point_id));
}
float3 point_position_get(uint point_id)
{
const auto &curves_pos_rad_buf = buffer_get(draw_curves, curves_pos_rad_buf);
return texelFetch(curves_pos_rad_buf, int(point_id)).rgb;
}
struct Point {
/* Position of the evaluated curve point (not the shape / cylinder point). */
float3 P;
/* Tangent vector going from the root to the tip of the curve. */
float3 T;
float radius;
/* Lateral/Azimuthal offset from the center of the curve's width. Range [-1..1]. */
float azimuthal_offset;
int point_id;
int curve_id;
int curve_segment;
};
/* Return data about the curve point. */
Point point_get(uint vertex_id)
{
Segment segment = segment_get(vertex_id);
Indirection indirection = indirection_get(segment);
Point pt;
pt.point_id = point_id_get(segment, indirection);
pt.curve_id = indirection.curve_id;
pt.curve_segment = indirection.curve_segment;
float4 pos_rad = point_position_and_radius_get(pt.point_id);
bool restart_strip = indirection.is_end_of_curve || segment.is_end_of_segment;
pt.P = (restart_strip) ? float3(NAN_FLT) : pos_rad.xyz;
pt.radius = pos_rad.w;
pt.azimuthal_offset = azimuthal_offset_get(segment);
if (pt.curve_segment == 0) {
/* Hair root. */
pt.T = point_position_get(pt.point_id + 1) - pt.P;
}
else if (indirection.is_cyclic_point) {
/* Cyclic end point must match start point. */
pt.T = point_position_get(pt.point_id - pt.curve_segment + 1) - pt.P;
}
else {
pt.T = pt.P - point_position_get(pt.point_id - 1);
}
return pt;
}
Point object_to_world(Point pt, float4x4 object_to_world)
{
pt.P = transform_point(object_to_world, pt.P);
pt.T = normalize(transform_direction(object_to_world, pt.T));
pt.radius *= length(to_scale(object_to_world)) * M_SQRT1_3;
return pt;
}
struct ShapePoint {
/* Curve tangent space. */
float3 curve_N;
float3 curve_T;
float3 curve_B;
/* Position on the curve shape. */
float3 P;
/* Shading normal at the position on the curve shape. */
float3 N;
};
/**
* Return the position of the expanded position in world-space.
* \arg pt : world space curve point.
* \arg V : world space view vector (toward viewer) at `pt.P`.
*/
ShapePoint shape_point_get(const Point pt, const float3 V)
{
const bool is_strand = buffer_get(draw_curves_infos, drw_curves).half_cylinder_face_count == 0u;
ShapePoint shape;
/* Shading tangent is inverted because of legacy reason. */
/* TODO(fclem): Change user code. */
shape.curve_T = -pt.T;
shape.curve_B = normalize(cross(pt.T, V));
shape.curve_N = cross(shape.curve_B, pt.T);
/* Point in curve azimuthal space. */
const float2 lP = float2(pt.azimuthal_offset, sin_from_cos(abs(pt.azimuthal_offset)));
shape.N = shape.curve_B * lP.x + shape.curve_N * lP.y;
shape.P = pt.P + shape.N * (is_strand ? 0.0f : pt.radius);
return shape;
}
float get_customdata_float(const int curve_id, const samplerBuffer cd_buf)
{
return texelFetch(cd_buf, curve_id).x;
}
float2 get_customdata_vec2(const int curve_id, const samplerBuffer cd_buf)
{
return texelFetch(cd_buf, curve_id).xy;
}
float3 get_customdata_vec3(const int curve_id, const samplerBuffer cd_buf)
{
return texelFetch(cd_buf, curve_id).xyz;
}
float4 get_customdata_vec4(const int curve_id, const samplerBuffer cd_buf)
{
return texelFetch(cd_buf, curve_id).xyzw;
}
float3 get_curve_root_pos(const int point_id, const int curve_segment)
{
const auto &curves_pos_rad_buf = buffer_get(draw_curves, curves_pos_rad_buf);
int curve_start = point_id - curve_segment;
return texelFetch(curves_pos_rad_buf, curve_start).xyz;
}
} // namespace curves
#endif