Я помешан на максимизации производительности и уменьшении занимаемой памяти. Я пишу вершинный шейдер GLSL (нацеленный на OpenGL 3.0, GLSL 1.3 является эквивалентной версией GLSL, если кто-то не уверен) с намерением внедрить скиннинг наиболее экономичным способом.
Я читал, что OpenGL гарантирует не менее 16 КБ памяти Uniform Buffer (в изобилии за то, что я хочу сделать, здесь нет проблем). Поэтому я решил снабдить свой скин-вершинный шейдер преобразованиями костей через единообразные регистры.
Я проиллюстрирую это некоторым кодом:
#version 130
#define NUMBONES 16 /* 16 mat4x4 = 1KiB */
#define FLOATMAXINT 16777216
/* To map from model-space to world-space */
uniform mat4x4 u_mapWorld;
/* To map from model-space straight to screen space(?).
= projection * view * world (using column-major btw) */
uniform mat4x4 u_mapProj; /* 'Projection' */
/* To map from model-space to light space.
I use shadow-mapping. */
uniform mat4x4 u_mapLight;
/* For transforming normals */
uniform mat3x3 u_mapNorm;
/* Array of bone transforms for armature animation (skinning).
This array should total to 1KiB in width? */
uniform mat4x4 u_mapBones [NUMBONES];
/* Array of (corresponding) bone transforms for mapping vertex normals.
Probably 1KiB in width, in order to honour alignment requirements.
Desirable width would be 0.56KiB, though. */
uniform mat3x3 u_mapBoneNorms [NUMBONES];
/* Width of two arrays = 2KiB, optimised for speed.
Fine for what I want to do. */
Я делегирую матричные вычисления ЦПУ, чтобы снять нагрузку с матричной инверсии (для вычисления вершинных нормальных преобразований) с графического процессора, тем более что этот шаг, строго говоря, не нужен и лучше всего избегать. Я не возражаю против дополнительной памяти, которая потребляет это, если это означает более быструю обработку вершин.
Далее у меня есть вершинные входы, которые следующие:
in vec3 v_pos;
in vec3 v_norm;
in vec2 v_uv;
in float v_weights; /* Actually four weights */
in float v_indices; /* Actually four indices */
У меня есть планы по поддержке версий GL начиная с 2.0, и я могу расширить его до 1.4, поэтому v_weights
и v_indices
являются числами с плавающей точкой, а не упакованными целыми числами. Версии ниже 3.0 указываются как без поддержки для целочисленных входов (и процедуры glVertexAttribIPointer
). Я прочитал (и был впечатлен, узнав), числа с плавающей точкой IEEE одинарной (32-битной) точности могут надежно представлять целые числа до диапазона 16 777 216 до того, как точность понизится. Это 24-битная надежная емкость индекса, которую я решил разделить между четырьмя весами (24/4 = 6), что дает каждому весовому индексу 6 битов ширины (индексы варьируются от 0 до 63).
Моя программа передает индексы веса в OpenGL путем построения упакованного целого числа:
std::uint32_t ui_indices =
((w0 & 0x3F) << 0) + ((w1 & 0x3F) << 6) +
((w2 & 0x3F) << 12) + ((w3 & 0x3F) << 18);
и затем преобразует (конвертирует, вы понимаете, что я имею в виду) в число с плавающей точкой, полагая, что точность не теряется :
float f_indices = static_cast<float>(ui_indices);
, который затем передается в OpenGL через glVertexAttribPointer()
.
В моем вершинном шейдере у меня есть следующий код, который распакует индексы:
/* NONE OF THIS CODE IS YET TESTED */
uint ii; /* Int-converted Index */
uint i[4]; /* Unpacked indices (can we use uint8?) */
/* Extract indices */
fmod(v_indices, ii);
/* Unpack indices.
I've kept shift-by-zero for presentation. */
i[0] = (ii>> 0) & 0x3F;
i[1] = (ii>> 6) & 0x3F;
i[2] = (ii>>12) & 0x3F;
i[3] = (ii>>18) & 0x3F;
И, наконец, на мой вопрос:
Предполагая, что концепция / реализация не ошибочна (ничего из этого еще не проверено) - будет ли GLSL оптимизировать это утверждение? Кроме того, лучше вместо этого реализовать маскирование в виде серии битовых сдвигов (НЕ БИТОВОЕ ВРАЩЕНИЕ), например:
i[0] = (ii<<18)>>18;
i[1] = (ii<<12)>>18;
i[2] = (ii<< 6)>>18;
i[3] = (ii<< 0)>>18;
P.S. Извиняюсь, если на этот вопрос ответили в другом месте, и я пропустил это. Также извините, если я дал слишком много контекста для небольшого вопроса.