OpenGL: отладка "однопроходного рендеринга каркаса"
Я пытаюсь реализовать документ "Однопроходный каркасный рендеринг", который кажется довольно простым, но он дает мне то, что я ожидаю, насколько толстые, темные значения.
В бумаге не давался точный код, чтобы определить высоту, поэтому я сделал это, как я считал нужным. Код должен проецировать три вершины в пространство просмотра, получать их "высоты" и отправлять их в шейдер фрагмента.
Фрагментный шейдер определяет расстояние ближайшего края и генерирует значение edgeIntensity. Я не уверен, что я должен делать с этим значением, но поскольку он должен масштабироваться между [0,1], я умножаю инверсию на мой исходящий цвет, но он очень слаб.
У меня было несколько вопросов, на которые я не уверен, рассматриваются в газетах. Во-первых, следует ли вычислять высоту в 2D вместо 3D? Во-вторых, они размещают функции DirectX, где DirectX имеет различный z-диапазон в пространстве viewport-space, правильно? Это имеет значение? Я преувеличиваю расстояние исходящих высот по w-значению координат пространства вида, поскольку они рекомендуют корректировать перспективную проекцию.
изображение, пытающееся скорректировать перспективную проекцию
нет коррекции (не превзойдя на w-значение)
Неправильное изображение, похоже, имеет четкие проблемы, не исправляющие перспективы на более отдаленных сторонах, но исправленный с точки зрения перспективы имеет очень слабые значения.
Может ли кто-нибудь увидеть, что не так с моим кодом или как его отладить отсюда?
мой вершинный код в GLSL...
float altitude(in vec3 a, in vec3 b, in vec3 c) { // for an ABC triangle
vec3 ba = a - b;
vec3 bc = c - b;
vec3 ba_onto_bc = dot(ba,bc) * bc;
return(length(ba - ba_onto_bc));
}
in vec3 vertex; // incoming vertex
in vec3 v2; // first neighbor (CCW)
in vec3 v3; // second neighbor (CCW)
in vec4 color;
in vec3 normal;
varying vec3 worldPos;
varying vec3 worldNormal;
varying vec3 altitudes;
uniform mat4 objToWorld;
uniform mat4 cameraPV;
uniform mat4 normalToWorld;
void main() {
worldPos = (objToWorld * vec4(vertex,1.0)).xyz;
worldNormal = (normalToWorld * vec4(normal,1.0)).xyz;
//worldNormal = normal;
gl_Position = cameraPV * objToWorld * vec4(vertex,1.0);
// also put the neighboring polygons in viewport space
vec4 vv1 = gl_Position;
vec4 vv2 = cameraPV * objToWorld * vec4(v2,1.0);
vec4 vv3 = cameraPV * objToWorld * vec4(v3,1.0);
altitudes = vec3(vv1.w * altitude(vv1.xyz,vv2.xyz,vv3.xyz),
vv2.w * altitude(vv2.xyz,vv3.xyz,vv1.xyz),
vv3.w * altitude(vv3.xyz,vv1.xyz,vv2.xyz));
gl_FrontColor = color;
}
и код фрагмента...
varying vec3 worldPos;
varying vec3 worldNormal;
varying vec3 altitudes;
uniform vec3 cameraPos;
uniform vec3 lightDir;
uniform vec4 singleColor;
uniform float isSingleColor;
void main() {
// determine frag distance to closest edge
float d = min(min(altitudes.x, altitudes.y), altitudes.z);
float edgeIntensity = exp2(-2.0*d*d);
vec3 L = lightDir;
vec3 V = normalize(cameraPos - worldPos);
vec3 N = normalize(worldNormal);
vec3 H = normalize(L+V);
//vec4 color = singleColor;
vec4 color = isSingleColor*singleColor + (1.0-isSingleColor)*gl_Color;
//vec4 color = gl_Color;
float amb = 0.6;
vec4 ambient = color * amb;
vec4 diffuse = color * (1.0 - amb) * max(dot(L, N), 0.0);
vec4 specular = vec4(0.0);
gl_FragColor = (edgeIntensity * vec4(0.0)) + ((1.0-edgeIntensity) * vec4(ambient + diffuse + specular));
}
Ответы
Ответ 1
Я реализовал идею свиней, и результат является идеальным, вот мой скриншот:
![enter image description here]()
struct MYBUFFEREDVERTEX {
float x, y, z;
float nx, ny, nz;
float u, v;
float bx, by, bz;
};
const MYBUFFEREDVERTEX g_vertex_buffer_data[] = {
-1.0f, -1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 0.0f,
0.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f, 0.0f,
};
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
vertex shader:
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
uniform mat4 u_mvp_matrix;
uniform vec3 u_light_direction;
attribute vec3 a_position;
attribute vec3 a_normal;
attribute vec2 a_texcoord;
attribute vec3 a_barycentric;
varying vec2 v_texcoord;
varying float v_light_intensity;
varying vec3 v_barycentric;
void main()
{
// Calculate vertex position in screen space
gl_Position = u_mvp_matrix * vec4(a_position, 1.0);
// calculate light intensity, range of 0.3 ~ 1.0
v_light_intensity = max(dot(u_light_direction, a_normal), 0.3);
// Pass texture coordinate to fragment shader
v_texcoord = a_texcoord;
// Pass bary centric to fragment shader
v_barycentric = a_barycentric;
}
фрагмент шейдера:
#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif
uniform sampler2D u_texture;
varying vec2 v_texcoord;
varying float v_light_intensity;
varying vec3 v_barycentric;
void main()
{
float min_dist = min(min(v_barycentric.x, v_barycentric.y), v_barycentric.z);
float edgeIntensity = 1.0 - step(0.005, min_dist);
// Set diffuse color from texture
vec4 diffuse = texture2D(u_texture, v_texcoord) * vec4(vec3(v_light_intensity), 1.0);
gl_FragColor = edgeIntensity * vec4(0.0, 1.0, 1.0, 1.0) + (1.0 - edgeIntensity) * diffuse;
}
Ответ 2
во-первых, ваша функция heightitude() ошибочна, ba_onto_bc вычисляется неправильно, потому что bc не является единицей длины (либо нормализовать bc, либо делить ba_onto_bc на точку (bc, bc), которая равна квадрату длины - вы сохраняете вычисление квадратного корня).
Высоты должны рассчитываться в 2D, если вы хотите края постоянной толщины, или в 3D, если вы хотите перспективные края.
Было бы намного проще использовать барицентрические координаты в качестве отдельного атрибута вершины (т.е. вершина 0 треугольника получила бы (1 0 0), вторую вершину (0 1 0) и последнюю вершину (0 0 1 )). В флеш-шейдере вы вычисляете минимум и используете step() или smoothstep() для вычисления крайности.
Для этого потребуется только 1 атрибут вместо двух текущих, а также исключить необходимость вычисления высоты в вершинном шейдере (хотя это может быть полезно, если вы хотите предварительно масштабировать барицентрические координаты, чтобы у вас были однородно толстые линии, но вычислить его в автономном режиме). Он также должен работать практически мгновенно, поэтому было бы хорошей отправной точкой для достижения желаемого поведения.