Silhouette-Outlined着色器

时间:2017-10-06 10:39:05

标签: opengl glsl shader vertex-shader

我正在尝试实施 GLSL 着色器,该着色器会突出显示渲染的 3D 网格的外边缘。问题是我无权访问 OpenGL 客户端代码,因此必须仅在 GLSL 着色器中执行此操作。

我的第一次尝试是使用/采用 Unity 中的this着色器,并在 OpenGL GLSL 中执行此操作。它应该如何看待:

enter image description here

这就是我得到的:

enter image description here

我不确定如果我正确地计算了这些东西,但是你可以看到输出远远超出我的预期。

这是食人魔材料

material Chassis 
    {
    technique
        {    
        pass standard
            {
            cull_software back         
            scene_blend zero one
            }
        pass psssm
            {         
            cull_software front 
            scene_blend src_alpha one_minus_src_alpha         
            vertex_program_ref reflection_cube_specularmap_normalmap_vs100 
                {
                param_named_auto modelViewProjectionMatrix worldviewproj_matrix
                param_named_auto normalMatrix inverse_transpose_world_matrix
                param_named_auto modelView worldview_matrix
                param_named_auto camera_world_position camera_position
                param_named_auto inverse_projection_matrix inverse_projection_matrix
                param_named_auto  projection_matrix projection_matrix
                param_named_auto  p_InverseModelView inverse_worldview_matrix
                }
            fragment_program_ref reflection_cube_specularmap_normalmap_fs100
                {                
                }    
            }
        }
    }

这是顶点着色器

#version 140
#define lowp
#define mediump
#define highp

in vec4 vertex;
in vec3 normal;   

uniform mat4 normalMatrix;
uniform mat4 modelViewProjectionMatrix;
uniform mat4 modelView;
uniform vec3 camera_world_position;
uniform mat4 projection_matrix;
uniform mat4 inverse_projection_matrix;
void main()
   {        
   vec4 pos = modelViewProjectionMatrix * vertex;
   mat4 modelView = inverse_projection_matrix * modelViewProjectionMatrix;

   vec4 norm   =   inverse(transpose(modelView)) * vec4(normal, 0.0);
   vec2 offset =   vec2( norm.x * projection_matrix[0][0], norm.y * projection_matrix[1][1] );

   pos.xy += offset * pos.z * 0.18;
   gl_Position = pos;
   } 

编辑:我添加了食人魔使用的材质脚本,并添加了顶点着色器代码。

1 个答案:

答案 0 :(得分:4)

我假设单个复杂的 3D 网格。我会用2遍渲染来做到这一点:

  1. 清除屏幕

    (0,0,0)使用清晰的颜色。

  2. 渲染网格

    禁用深度输出,测试(或之后清除)。不要仅使用某些预定义颜色使用着色填充,例如(1,1,1)让我们为简单的多维数据集执行此操作:

    1st pass

  3. 读取帧缓冲区并将其用作纹理

    所以要么使用 FBO 并渲染到#1,#2 的纹理,要么使用glReadPixels并将其作为一些纹理加载回 GPU (我知道它速度较慢,但​​也适用于英特尔)。有关详细信息,请参阅此处的答案:

  4. 清除背景色的屏幕

  5. <强>呈现

    所以要么渲染GL_QUAD覆盖整个屏幕,要么用阴影渲染你的网格以及你想要的东西。您还需要将上一步中的纹理传递给 GLSL

    片段中照常渲染...但最后还要添加:

    扫描当前片段屏幕位置周围的所有纹素,其距离等于上一步中纹理中的轮廓粗细。如果在其中找到任何黑色像素,则使用轮廓颜色覆盖输出的颜色。你甚至可以用最小的黑色调制它。

    这与此非常相似:

    但更简单。结果如下:

    2nd pass

  6. 我采用了我的Analysis of a shader in VR示例并将其转换为:

    <强>片段:

    // Fragment
    #version 400 core
    #extension GL_ARB_explicit_uniform_location : enable
    layout(location =64) uniform vec3 lt_pnt_pos;// point light source position [GCS]
    layout(location =67) uniform vec3 lt_pnt_col;// point light source color&strength
    layout(location =70) uniform vec3 lt_amb_col;// ambient light source color&strength
    in vec3 LCS_pos;        // fragment position [LCS]
    in vec3 pixel_pos;      // fragment position [GCS]
    in vec3 pixel_col;      // fragment surface color
    in vec3 pixel_nor;      // fragment surface normal [GCS]
    out vec4 col;
    
    // outline
    uniform sampler2D txr;  // texture from previous pass
    uniform int thickness;  // [pixels] outline thickness
    uniform float xs,ys;    // [pixels] texture/screen resolution
    void main()
        {
        // standard rendering
        float li;
        vec3 c,lt_dir;
        lt_dir=normalize(lt_pnt_pos-pixel_pos); // vector from fragment to point light source in [GCS]
        li=dot(pixel_nor,lt_dir);
        if (li<0.0) li=0.0;
        c=pixel_col*(lt_amb_col+(lt_pnt_col*li));
    
        // outline effect
        if (thickness>0)            // thickness effect in second pass
            {
            int i,j,r=thickness;
            float xx,yy,rr,x,y,dx,dy;
            dx=1.0/xs;              // texel size
            dy=1.0/ys;
            x=gl_FragCoord.x*dx;
            y=gl_FragCoord.y*dy;
            rr=thickness*thickness;
            for (yy=y-(float(thickness)*dy),i=-r;i<=r;i++,yy+=dy)
             for (xx=x-(float(thickness)*dx),j=-r;j<=r;j++,xx+=dx)
              if ((i*i)+(j*j)<=rr)
               if ((texture(txr,vec2(xx,yy)).r)<0.01)
                {
                c=vec3(1.0,0.0,0.0);    // outline color
                i=r+r+1;
                j=r+r+1;
                break;
                }
            }
        else c=vec3(1.0,1.0,1.0);   // render with white in first pass
    
        // output color
        col=vec4(c,1.0);
        }
    

    顶点着色器无变化:

    // Vertex
    #version 400 core
    #extension GL_ARB_explicit_uniform_location : enable
    layout(location = 0) in vec3 pos;
    layout(location = 2) in vec3 nor;
    layout(location = 3) in vec3 col;
    layout(location = 0) uniform mat4 m_model;  // model matrix
    layout(location =16) uniform mat4 m_normal; // model matrix with origin=(0,0,0)
    layout(location =32) uniform mat4 m_view;   // inverse of camera matrix
    layout(location =48) uniform mat4 m_proj;   // projection matrix
    out vec3 LCS_pos;       // fragment position [LCS]
    out vec3 pixel_pos;     // fragment position [GCS]
    out vec3 pixel_col;     // fragment surface color
    out vec3 pixel_nor;     // fragment surface normal [GCS]
    
    void main()
        {
        LCS_pos=pos;
        pixel_col=col;
        pixel_pos=(m_model*vec4(pos,1)).xyz;
        pixel_nor=(m_normal*vec4(nor,1)).xyz;
        gl_Position=m_proj*m_view*m_model*vec4(pos,1);
        }
    

    CPU 端代码如下所示:

    //---------------------------------------------------------------------------
    #include <vcl.h>
    #pragma hdrstop
    #include "Unit1.h"
    #include "gl_simple.h"
    //---------------------------------------------------------------------------
    #pragma package(smart_init)
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    GLfloat lt_pnt_pos[3]={+2.5,+2.5,+2.5};
    GLfloat lt_pnt_col[3]={0.8,0.8,0.8};
    GLfloat lt_amb_col[3]={0.2,0.2,0.2};
    GLuint txrid=0;
    GLfloat animt=0.0;
    //---------------------------------------------------------------------------
    // https://stackoverflow.com/q/46603878/2521214
    //---------------------------------------------------------------------------
    void gl_draw()
        {
        // load values into shader
        GLint i,id;
        GLfloat m[16];
        glUseProgram(prog_id);
    
        GLfloat x,y,z,d=0.25;
    
        id=glGetUniformLocation(prog_id,"txr"); glUniform1i(id,0);
        id=glGetUniformLocation(prog_id,"xs"); glUniform1f(id,xs);
        id=glGetUniformLocation(prog_id,"ys"); glUniform1f(id,ys);
    
        id=64; glUniform3fv(id,1,lt_pnt_pos);
        id=67; glUniform3fv(id,1,lt_pnt_col);
        id=70; glUniform3fv(id,1,lt_amb_col);
        glGetFloatv(GL_MODELVIEW_MATRIX,m);
        id=0; glUniformMatrix4fv(id,1,GL_FALSE,m);
        m[12]=0.0; m[13]=0.0; m[14]=0.0;
        id=16; glUniformMatrix4fv(id,1,GL_FALSE,m);
        for (i=0;i<16;i++) m[i]=0.0; m[0]=1.0; m[5]=1.0; m[10]=1.0; m[15]=1.0;
        id=32; glUniformMatrix4fv(id,1,GL_FALSE,m);
        glGetFloatv(GL_PROJECTION_MATRIX,m);
        id=48; glUniformMatrix4fv(id,1,GL_FALSE,m);
    
    
        // draw VAO cube (no outline)
        id=glGetUniformLocation(prog_id,"thickness"); glUniform1i(id,0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        vao_draw(); // render cube
    
        // copy frame buffer to CPU memory and than back to GPU as Texture
        BYTE *map=new BYTE[xs*ys*4];
        glReadPixels(0,0,xs,ys,GL_RGB,GL_UNSIGNED_BYTE,map);    // framebuffer -> map[]
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D,txrid);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, xs, ys, 0, GL_RGB, GL_UNSIGNED_BYTE, map); // map[] -> texture txrid
        delete[] map;
    
        // draw VAO cube (outline)
        id=glGetUniformLocation(prog_id,"thickness"); glUniform1i(id,5);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        vao_draw(); // render cube
        glDisable(GL_TEXTURE_2D);
    
        // turn of shader
        glUseProgram(0);
    
        // rotate the cube to see animation
        glMatrixMode(GL_MODELVIEW);
    //  glRotatef(1.0,0.0,1.0,0.0);
    //  glRotatef(1.0,1.0,0.0,0.0);
    
        glFlush();
        SwapBuffers(hdc);
        }
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
        {
        gl_init(Handle);
    
        glGenTextures(1,&txrid);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D,txrid);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_COPY);
        glDisable(GL_TEXTURE_2D);
    
    
        int hnd,siz; char vertex[4096],fragment[4096];
        hnd=FileOpen("normal_shading.glsl_vert",fmOpenRead); siz=FileSeek(hnd,0,2); FileSeek(hnd,0,0); FileRead(hnd,vertex  ,siz); vertex  [siz]=0; FileClose(hnd);
        hnd=FileOpen("normal_shading.glsl_frag",fmOpenRead); siz=FileSeek(hnd,0,2); FileSeek(hnd,0,0); FileRead(hnd,fragment,siz); fragment[siz]=0; FileClose(hnd);
        glsl_init(vertex,fragment);
    //  hnd=FileCreate("GLSL.txt"); FileWrite(hnd,glsl_log,glsl_logs); FileClose(hnd);
    
        int i0,i;
        mm_log->Lines->Clear();
        for (i=i0=0;i<glsl_logs;i++)
         if ((glsl_log[i]==13)||(glsl_log[i]==10))
            {
            glsl_log[i]=0;
            mm_log->Lines->Add(glsl_log+i0);
            glsl_log[i]=13;
            for (;((glsl_log[i]==13)||(glsl_log[i]==10))&&(i<glsl_logs);i++);
            i0=i;
            }
        if (i0<glsl_logs) mm_log->Lines->Add(glsl_log+i0);
    
        vao_init();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
        {
        glDeleteTextures(1,&txrid);
        gl_exit();
        glsl_exit();
        vao_exit();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormResize(TObject *Sender)
        {
        gl_resize(ClientWidth,ClientHeight-mm_log->Height);
        glMatrixMode(GL_PROJECTION);
        glTranslatef(0,0,-15.0);
    
        glMatrixMode(GL_MODELVIEW);
        glRotatef(-15.0,0.0,1.0,0.0);
        glRotatef(-125.0,1.0,0.0,0.0);
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormPaint(TObject *Sender)
        {
        gl_draw();
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Timer1Timer(TObject *Sender)
        {
        gl_draw();
        animt+=0.02; if (animt>1.5) animt=-0.5;
        Caption=animt;
        }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormMouseWheel(TObject *Sender, TShiftState Shift, int WheelDelta, TPoint &MousePos, bool &Handled)
        {
        GLfloat dz=2.0;
        if (WheelDelta<0) dz=-dz;
        glMatrixMode(GL_PROJECTION);
        glTranslatef(0,0,dz);
        gl_draw();
        }
    //---------------------------------------------------------------------------
    

    像往常一样,代码使用/基于此:

    <强> [注释]

    如果您有多个对象,则在#2 中为每个对象使用不同的颜色。然后在#5 中扫描任何不同的颜色,然后扫描当前位置的纹素,而不是扫描黑色。

    这也可以在2D图像上完成,而不是使用网格。你只需要知道背景颜色。所以你也可以使用预渲染/抓取/截图图像。

    您可以添加discard和/或更改最终if逻辑以更改行为(就像您只想要轮廓而内部没有网格等...)。或者你可以添加轮廓颜色来渲染颜色,而不是直接指定它来获得高亮的印象......而不是着色

    options

    参见a),b),c)修改片段中的选项:

    // Fragment
    #version 400 core
    #extension GL_ARB_explicit_uniform_location : enable
    layout(location =64) uniform vec3 lt_pnt_pos;// point light source position [GCS]
    layout(location =67) uniform vec3 lt_pnt_col;// point light source color&strength
    layout(location =70) uniform vec3 lt_amb_col;// ambient light source color&strength
    in vec3 LCS_pos;        // fragment position [LCS]
    in vec3 pixel_pos;      // fragment position [GCS]
    in vec3 pixel_col;      // fragment surface color
    in vec3 pixel_nor;      // fragment surface normal [GCS]
    out vec4 col;
    
    // outline
    uniform sampler2D txr;  // texture from previous pass
    uniform int thickness;  // [pixels] outline thickness
    uniform float xs,ys;    // [pixels] texture/screen resolution
    void main()
        {
        // standard rendering
        float li;
        vec3 c,lt_dir;
        lt_dir=normalize(lt_pnt_pos-pixel_pos); // vector from fragment to point light source in [GCS]
        li=dot(pixel_nor,lt_dir);
        if (li<0.0) li=0.0;
        c=pixel_col*(lt_amb_col+(lt_pnt_col*li));
    
        // outline effect
        if (thickness>0)            // thickness effect in second pass
            {
            int i,j,r=thickness;
            float xx,yy,rr,x,y,dx,dy;
            dx=1.0/xs;              // texel size
            dy=1.0/ys;
            x=gl_FragCoord.x*dx;
            y=gl_FragCoord.y*dy;
            rr=thickness*thickness;
            for (yy=y-(float(thickness)*dy),i=-r;i<=r;i++,yy+=dy)
             for (xx=x-(float(thickness)*dx),j=-r;j<=r;j++,xx+=dx)
              if ((i*i)+(j*j)<=rr)
               if ((texture(txr,vec2(xx,yy)).r)<0.01)
                {
                c =vec3(1.0,0.0,0.0);   // a) assign outline color
    //          c+=vec3(1.0,0.0,0.0);   // b) add outline color
                i=r+r+1;
                j=r+r+1;
                r=0;
                break;
                }
    //      if (r!=0) discard; // c) do not render inside
            }
        else c=vec3(1.0,1.0,1.0);   // render with white in first pass
    
        // output color
        col=vec4(c,1.0);
        }
    

    [Edit1]光滑边缘的单遍方法

    由于您无法访问客户端代码,因此此方法仅适用于着色器。对于平滑(弯曲)边缘形状,曲面法线几乎垂直于摄像机视轴(z)。所以它们之间的dot接近于零。这可以直接利用...这里着色器的更新:

    <强>顶点

    // Vertex
    #version 400 core
    #extension GL_ARB_explicit_uniform_location : enable
    layout(location = 0) in vec3 pos;
    layout(location = 2) in vec3 nor;
    layout(location = 3) in vec3 col;
    layout(location = 0) uniform mat4 m_model;  // model matrix
    layout(location =16) uniform mat4 m_normal; // model matrix with origin=(0,0,0)
    layout(location =32) uniform mat4 m_view;   // inverse of camera matrix
    layout(location =48) uniform mat4 m_proj;   // projection matrix
    out vec3 pixel_pos;     // fragment position [GCS]
    out vec3 pixel_col;     // fragment surface color
    out vec3 pixel_nor;     // fragment surface normal [GCS]
    out vec3 view_nor;     // surface normal in camera [LCS]
    
    void main()
        {
        pixel_col=col;
        pixel_pos=(m_model*vec4(pos,1)).xyz;
        pixel_nor=(m_normal*vec4(nor,1)).xyz;
    
        mat4 m;
        m=m_model*m_view;                   // model view matrix
        m[3].xyz=vec3(0.0,0.0,0.0);         // with origin set to (0,0,0)
        view_nor=(m*vec4(nor,1.0)).xyz;     // object local normal to camera local normal
    
        gl_Position=m_proj*m_view*m_model*vec4(pos,1);
        }
    

    <强>片段

    // Fragment
    #version 400 core
    #extension GL_ARB_explicit_uniform_location : enable
    layout(location =64) uniform vec3 lt_pnt_pos;// point light source position [GCS]
    layout(location =67) uniform vec3 lt_pnt_col;// point light source color&strength
    layout(location =70) uniform vec3 lt_amb_col;// ambient light source color&strength
    in vec3 pixel_pos;      // fragment position [GCS]
    in vec3 pixel_col;      // fragment surface color
    in vec3 pixel_nor;      // fragment surface normal [GCS]
    out vec4 col;
    
    // outline
    in vec3 view_nor;     // surface normal in camera [LCS]
    
    void main()
        {
        // standard rendering
        float li;
        vec3 c,lt_dir;
        lt_dir=normalize(lt_pnt_pos-pixel_pos); // vector from fragment to point light source in [GCS]
        li=dot(pixel_nor,lt_dir);
        if (li<0.0) li=0.0;
        c=pixel_col*(lt_amb_col+(lt_pnt_col*li));
    
        // outline effect
        if (abs(dot(view_nor,vec3(0.0,0.0,1.0)))<=0.5) c=vec3(1.0,0.0,0.0);
    
        // output color
        col=vec4(c,1.0);
        }
    

    这里预览:

    preview

    正如你所看到的,它适用于光滑的物体,但对于像立方体这样的尖锐边缘,这根本不起作用......你可以使用与之前方法相同的组合(a,b,c)。

    m保存模型视图矩阵,其原点设置为(0,0,0)。这使它能够进行矢量转换(无翻译)。有关详细信息,请参阅Understanding 4x4 homogenous transform matrices

    点积结果0.5中的if是轮廓的粗细。 0.0表示没有大纲,1.0表示整个对象都是大纲。