OpenGL:调试“单遍线框渲染”

4
我正在尝试实现论文“单遍线框渲染”,这似乎很简单,但是它给出了我预期的粗暗值。论文没有给出确切的代码来计算高度,所以我按照我认为合适的方式进行了计算。代码应该将三个顶点投影到视口空间中,获取它们的“高度”并将其发送到片段着色器中。片段着色器确定最近边缘的距离并生成边缘强度。我不确定我应该如何处理这个值,但是由于它应该在 [0,1] 范围内缩放,所以我将其与我的输出颜色相乘的倒数,但它仅仅非常微弱。
我有一些问题,我不确定论文是否解决了这些问题。首先,高度是否应该在2D中计算而不是3D中?其次,他们引用了DirectX功能,其中DirectX具有不同的视口空间z范围,对吗?这有关系吗?我正在预乘视口空间坐标的w值,因为他们建议这样可以纠正透视投影。 image trying to correct for perspective projection

无校正(未乘以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));
}
2个回答

5
我已经实现了swine的想法,结果很完美,这是我的截图:

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);

顶点着色器:

#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;
}

您可以使用 fwidth(min_dist) 作为1像素的厚度,以距离边缘的单位计数,并使用 step(fwidth(min_dist),min_dist) 或更好的 smoothstep(fwidth(min_dist),2 * fwidth(min_dist),min_dist)。这消除了“魔法数字” 0.005 的需要。 - the swine

3
首先,您的函数altitude()存在缺陷,ba_onto_bc的计算有误,因为bc不是单位长度(要么归一化bc,要么将ba_onto_bc除以点积(bc, bc),即长度平方-这样可以避免计算平方根)。
如果您想要恒定厚度的边缘,则应在2D中计算高度,或者在3D中计算,如果您想要透视校正的边缘。
更容易的方法是将重心坐标用作单独的顶点属性(例如,三角形的顶点0将获得(1 0 0),第二个顶点(0 1 0)和最后一个顶点(0 0 1))。在片段着色器中,您将计算最小值并使用step()或smoothstep()来计算边缘性。
这只需要一个属性而不是当前的两个属性,并且还可以消除在顶点着色器中计算高度的需要(虽然如果您想要预缩放重心坐标以便拥有统一的线宽,则可能会有所帮助-但请离线计算)。它也应该立即起作用,因此它是达到所需行为的良好起点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接