细胞阴影轮廓:边缘网格书写器不定义所有所需的边缘

Cell-Shading Outlines: edge mesh writer does not define all desired edges

本文关键字:边缘 定义 书写器 阴影 轮廓 网格 细胞      更新时间:2023-10-16

我正在编写的程序接受3D网格的顶点数据,执行一系列计算(请原谅我的模糊,稍后我会尝试更详细地解释),并输出一个二进制文件,该文件定义了网格上的边缘位置。然后我的程序在边缘的位置画一条彩色的线。如果没有适当的顶点着色器,这看起来就像一个普通的三角网格,但是一旦应用了适当的顶点着色器,只有"锋利"的边缘(它们法线的点积大于接近于零的东西)才会在它们上面画上线,以及在图形外部的边缘。我对轮廓的实现是不正确的,因为我假设如果一个边缘不在边缘后面,并且没有定义一个锋利的边缘,它将是一个轮廓边缘。我在其他地方没有找到一个令人满意的答案,我不想依靠旧的技巧,将网格重新绘制为纯色,并渲染它比原始网格略大。这种方法是完全基于数学的,只依赖于网格的顶点数据。我正在编写一个使用以下顶点着色器的程序:

uniform mat4 worldMatrix;
uniform mat4 projMatrix;
uniform mat4 viewProjMatrix;
uniform vec4 eyepos;
attribute vec3 a;
attribute vec3 b;
attribute vec3 n1;
attribute vec3 n2;
attribute float w;
void main()
{
    float a_vertex = dot(eyepos.xyz - a, n1);
    float b_vertex = dot(eyepos.xyz - a, n2);
    if (a_vertex * b_vertex > 0.0) // signs are different, edge is behind the object
    {
        gl_Position = vec4(2.0,2.0,2.0,1.0);
    }
    else // the outline of the figure
    {
        if(w == 0.0)
        {
            vec4 p = vec4(a.x, a.y, a.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }
        else
        {
            vec4 p = vec4(b.x, b.y, b.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }
    }
    if(dot(n1, n2) <= 0.2) // there is a sharp edge
    {
        if(w == 0.0)
        {
            vec4 p = vec4(a.x, a.y, a.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }
        else
        {
            vec4 p = vec4(b.x, b.y, b.z, 1.0);
            p = p * worldMatrix * viewProjMatrix;
            gl_Position = p;
        }
    }
}

…从用c++编写的二进制文件中获取信息:

#include <iostream>
#include "llgl.h"
#include <fstream>
#include <vector>
#include "SuperMesh.h"
using namespace std;
using namespace llgl;
struct Vertex
{
    float x,y,z,w;
    float s,t,p,q;
    float nx,ny,nz,nw;
};
bool isFileAlright(string fName)
{
    ifstream in(fName.c_str());
    if(!in.good())
        return false;
    return true;
}
int main(int argc, char* argv[])
{
    // INPUT FILE NAME //
    string fName;
    cout << "Enter the path to your spec.mesh file here: ";
    cin >> fName;
    while(!isFileAlright(fName))
    {
        cout << "Enter the path to your spec.mesh file here: ";
        cin >> fName;
    }
    SuperMesh* Model = new SuperMesh(fName.c_str());
    // END INPUT //
    Model->load();
    Model->draw();
    string fname = Model->fname;
    string FileName = fname.substr(0, fname.size() - 10); // supposed to slash the last 10 characters off of the string, removing ".spec.mesh"...
    FileName = FileName + ".bin"; //... and then we make it a .bin file*/
    cout << FileName << endl;
    ofstream out(FileName.c_str(), ios::binary);
    for (unsigned w = 0; w < Model->m.size(); w++)
    {
        vector<float> &vdata = Model->m[w]->vdata;
        vector<char> &idata = Model->m[w]->idata;

        //Create a vertex and index variable, a map for Edge Mesh, perform two loops to analyze all triangles on a mesh and write out their vertex values to a file.//
        Vertex* V = (Vertex*)(&vdata[0]);
        unsigned short* I16 = (unsigned short*)(&idata[0]);
        unsigned char* I8 = (unsigned char*)(&idata[0]);
        unsigned int* I32 = (unsigned int*)(&idata[0]);
        map<set<int>, vector<vec3> > EM;
        for(unsigned i = 0; i < Model->m[w]->ic; i += 3) // 3 because we're looking at triangles //
        {
            Mesh* foo = Model->m[w];
            int i1;
            int i2;
            int i3;
            if( Model->m[w]->ise == GL_UNSIGNED_BYTE)
            {
                i1 = I8[i];
                i2 = I8[i + 1];
                i3 = I8[i + 2];
            }
            else if( Model->m[w]->ise == GL_UNSIGNED_SHORT)
            {
                i1 = I16[i];
                i2 = I16[i + 1];
                i3 = I16[i + 2];
            }
            else
            {
                i1 = I32[i];
                i2 = I32[i + 1];
                i3 = I32[i + 2];
            }
            vec3 p = vec3(V[i1].x, V[i1].y, V[i1].z); // to represent the point in 3D space of each vertex on every triangle on the mesh
            vec3 q = vec3(V[i2].x, V[i2].y, V[i2].z);
            vec3 r = vec3(V[i3].x, V[i3].y, V[i3].z);
            vec3 v1 = p - q;
            vec3 v2 = r - q;
            vec3 n = cross(v2,v1); //important to make sure the order is correct here, do VERTEX TWO dot VERTEX ONE//
            set<int> tmp;
            tmp.insert(i1); tmp.insert(i2);
            EM[tmp].push_back(n);
            set<int> tmp2;
            tmp2.insert(i2); tmp2.insert(i3);
            EM[tmp2].push_back(n);
            set<int> tmp3;
            tmp3.insert(i3); tmp3.insert(i1);
            EM[tmp3].push_back(n);
            //we have now pushed every needed point into our edge map
        }
        int edgeNumber = 0;
        cout << "There should be 12 edges on a lousy cube." << endl;
        for(map<set<int>, vector<vec3> >::iterator it = EM.begin(); it != EM.end(); ++it)
        {
            //Now we will take our edge map and write its data to the file!//
            /* Information is written to the file in this form:
            Vertex One, Vertex Two, Normal One, Normal Two, r (where r, depending on its value, determines whether one edge is on top of the other in the case
                                                                                    where two edges are aligned with one another)
            */
            set<int>::iterator tmp = it->first.begin();
            int pi = *tmp;
            tmp++;
            int qi = *tmp;
            Vertex One = V[pi];
            Vertex Two = V[qi];
            vec3 norm1 = it->second[0];
            vec3 norm2;
            if(it->second.size() == 1)
                norm2 = -1 * norm1;
            else
                norm2 = it->second[1];
            out.write((char*) &One, 12);
            out.write((char*) &Two, 12);
            out.write((char*) &norm1, 12);
            out.write((char*) &norm2, 12);
            float r = 0;
            out.write((char*) &r, 4);
            out.write((char*) &One, 12);
            out.write((char*) &Two, 12);
            out.write((char*) &norm1, 12);
            out.write((char*) &norm2, 12);
            r = 1;
            out.write((char*) &r, 4);
            edgeNumber++;
            cout << "Wrote edge #" << edgeNumber << endl;
        }
    }
    return 0;
}

这个程序的问题是,在测试用例中,它既没有做这两件重要的事情,我用它来画一个简单的轮廓框:

  1. 不绘制轮廓。顶点着色器不足以确定物体边缘的位置。实现这一点的二进制文件是在一个单独的程序中使用上面发布的第二个代码片段预先计算的,然后它与它所属的网格资源一起保存为.bin文件。然而,原始顶点数据只会带我到目前为止,我寻求一种方法,在网格外部绘制一条线,而不使用更传统的方法。

  2. 它没有画出我需要的所有边。在我的测试用例中,有两条边丢失了,我怎么也弄不清楚为什么。我想我一定是在写边图时做错了什么。

关于上面代码的几点注意事项:

  • llgl是一个OpenGL包装器,我用它来简化OpenGL的许多元素。它在这里没有被广泛使用,而是在别处完成的网格创建。

  • 像Mesh和SuperMesh(一个刚体的网格集合)这样的东西意味着在我的场景中是3D对象。在我的测试用例中,我的场景中只有一个Mesh,并且定义单个Mesh的SuperMesh本质上只是创建单个Mesh。

  • 第二个代码片段中的"draw"调用,它预先计算网格的边缘映射,实际上并不绘制任何东西。有必要访问网格的顶点数据。

  • 变量"ise"取自SuperMesh中的单个网格,并且是通过从原始Blender . obj文件中读取它而发现的变量。这与应该使用多少内存来存储重要的顶点数据有关。使用Blender的朋友和导师告诉我,为这些值分配比所需更多的空间通常不是一个好主意。

它没有被很好地注释,因为我不是唯一一个研究过这段代码的人,不幸的是,我对第二个代码片段如何迭代网格上的所有三角形并不知怎么错过最后两个边的理解有限。一旦我更好地理解了这段代码在正确编写时应该做什么,我计划对其进行大量注释,并在未来的应用程序中使用它。

矩阵与向量的乘法顺序不可计算,因此你的顶点着色器必须输出投影*模型*顶点,而不是相反。

我通过在代码的不同部分分配更多空间来编写顶点数据来解决未绘制线的奥秘。至于我的其他问题,虽然我的顶点着色器中的乘法顺序实际上是正确的,但我混淆了向量数学的另一个基本概念。两个面法线的点积将是一个负数,当法线夹角为钝角时…就像我模型上的尖头一样。此外,上面有一个错误的逻辑,基本上说,如果脸是可见的,画所有的线在它上面。我重写了我的着色器,首先测试一个面是否可见,然后在相同的条件块中测试锐利的边缘。现在,如果一个脸是可见的,但它没有创建一个尖锐的边缘,着色器将忽略这个边缘。此外,轮廓现在出现,只是不完美。下面是上面顶点着色器的修改版本:

uniform mat4 worldMatrix; /* the matrix that defines how to project a point from 
object space to world space.*/
uniform mat4 viewProjMatrix; // the view (pertaining to screen size) matrix times the projection (how to project points to 3D) matrix.
uniform vec4 eyepos; // the position of the eye, given by the program.
attribute vec3 a; // one vertex on an edge, having an x,y,z, and w coordinate.
attribute vec3 b; // the other edge vertex.
attribute vec3 n1; // the normal of the face the edge is on.
attribute vec3 n2; // another normal in the case that an edge shares two faces... otherwise, this is the same as n1.
attribute float w; // an attribute given to make a binary choice between two edges when they draw on top of one another.
void main()
{
    // WORLD SPACE ATTRIBUTES //
    vec4 eye_world = eyepos * worldMatrix;
    vec4 a_world = vec4(a.x, a.y,a.z,1.0) * worldMatrix;
    vec4 b_world = vec4(b.x, b.y,b.z,1.0) * worldMatrix;
    vec4 n1_world = normalize(vec4(n1.x, n1.y,n1.z,0.0) * worldMatrix);
    vec4 n2_world = normalize(vec4(n2.x, n2.y,n2.z,0.0) * worldMatrix);
    // END WORLD SPACE ATTRIBUTES //
    // TEST CASE ATTRIBUTES //
    float a_vertex = dot(eye_world - a_world, n1_world);
    float b_vertex = dot(eye_world - b_world, n2_world);
    float normalDot = dot(n1_world.xyz, n2_world.xyz);
    float vertProduct = a_vertex * b_vertex;
    float hardness = 0.0; // this would be the value for an object made of sharp angles, like a box. Take a look at its use below.
    // END TEST CASE ATTRIBUTES //
    gl_Position = vec4(2.0,2.0,2.0,1.0); // if all else fails, keeping this here will discard unwanted data.
    if (vertProduct >= 0.1) // NOTE: face is behind the viewable portion of the object, normally uses 0.0 when not checking for silhouette
    {
            gl_Position = vec4(2.0,2.0,2.0,1.0);
    }
    else if(vertProduct < 0.1 && vertProduct >= -0.1) // NOTE: face makes almost a right angle with the eye vector
    {
        if(w == 0.0)
        {
            vec4 p = vec4(a_world.x, a_world.y, a_world.z, 1.0);
            p = p  * viewProjMatrix;
            gl_Position = p;
        }
        else
        {
            vec4 p = vec4(b_world.x, b_world.y, b_world.z, 1.0);
            p = p * viewProjMatrix;
            gl_Position = p;
        }
    }
    else // NOTE: this is the case where you can very clearly see a face.
    { // NOTE: the number that normalDot compares to should be its "hardness" value. The more negative the value, the smoother the surface.
       // a.k.a. the less we care about hard edges (when the normals of the faces make an obtuse angle) on the object, the more negative
       // hardness becomes on a scale of 0.0 to -1.0.
        if(normalDot <= hardness) // NOTE: the dot product of the two normals is obtuse, so we are looking at a sharp edge.
        {
            if(w == 0.0)
            {
                vec4 p = vec4(a_world.x, a_world.y, a_world.z, 1.0);
                p = p * viewProjMatrix;
                gl_Position = p;
            }
            else
            {
                vec4 p = vec4(b_world.x, b_world.y, b_world.z, 1.0);
                p = p * viewProjMatrix;
                gl_Position = p;
            }
        }
        else // NOTE: not sharp enough, just throw the vertex away
        {
            gl_Position = vec4(2.0,2.0,2.0,1.0);
        }
    }
}