轮廓轮廓着色器 [英] Silhouette-Outlined shader

查看:174
本文介绍了轮廓轮廓着色器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现 GLSL 着色器,该着色器将突出显示渲染的 3D 网格的外部边缘.问题是我无权访问 OpenGL 客户端代码,因此只能在 GLSL 着色器中完成.

I'm trying to implement GLSL shader which would highlight the outer edges of rendered 3D mesh. The problem is that I do not have access to the OpenGL client side code so this must be done only in GLSL shaders.

我的第一个尝试是使用/采用着色器团结,并在 OpenGL GLSL 中进行.这里看起来应该是这样:

My first attempt was to use/adopt this shader from Unity and do it in OpenGL GLSL. Here how it should look:

这就是我得到的:

我不确定我是否可以正确地计算出东西,但是如您所见,输出与我的预期相差无几.

I'm not sure If I compute the stuff correctly but as you can see the output is nowhere near my expectations.

这是食人魔材料

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

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

EDIT: I have added the material script which ogre uses and I have added the vertex shader code.

推荐答案

我假设使用单个复杂的 3D 网格.我会通过2次渲染来做到这一点:

I assume single complex 3D mesh. I would do this with 2 pass rendering:

  1. 清晰的屏幕

(0,0,0)作为清晰的颜色.

渲染网格

禁用深度输出,测试(或之后清除).不要仅使用一些预定义的颜色作为阴影填充,例如(1,1,1)让我们对简单的立方体执行以下操作:

disable depth output,test (or clear it afterwards). Do not use shading fill just with some predefined color for example (1,1,1) Lets do this for simple cube:

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

因此,可以使用 FBO 并为#1,#2 渲染为纹理,或者使用glReadPixels并将其作为某些纹理加载回 GPU (我知道它的速度较慢,但​​在Intel上也可以使用).有关更多信息,请参见此处的两个答案:

So either use FBO and render to texture for #1,#2 or use glReadPixels instead and load it as some texture back to GPU (I know it slower but works also on Intel). For more info see both answers in here:

具有背景色的清晰屏幕

渲染

因此可以渲染GL_QUAD覆盖整个屏幕,也可以使用阴影和所需内容渲染网格.您还需要将上一步的纹理传递到 GLSL .

so either render GL_QUAD covering whole screen or render your mesh with shading and what ever you want. You need to pass also the texture from previous step into GLSL.

片段中像往常一样呈现...但是最后还要添加:

In fragment render as usual ... but at the end also add this:

扫描当前片段屏幕位置周围的所有纹理像素,直到距离上一步中纹理轮廓线的距离为止.如果在其中找到任何黑色像素,则用轮廓色覆盖输出的颜色.您甚至可以以最小的距离将其调制为黑色.

Scan all texels around current fragment screen position up to distance equal to outline thickness in the texture from previous step. If any black pixel found in it override outputted color with your outline color. You can even modulate it with the smallest distance to black color.

这非常类似于:

但简单得多.结果如下:

but much simpler. Here result:

我以这个示例分析我的VR中的着色器,并将其转换为:

I took this example Analysis of a shader in VR of mine and converted it to this:

片段:

// 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();
    }
//---------------------------------------------------------------------------

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

As usual the code is using/based on this:

[注释]

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

In case you got multiple objects then use for each object different color in #2. Then in #5 scan for any different color then the one that is in the texel at current position instead of scanning for black.

这也可以在2D图像上完成,而不必使用网格.您只需要知道背景色即可.因此,您也可以为此使用预渲染/抓取/截屏的图像.

Also this can be done on 2D image instead of using mesh. You just need to know the background color. So you can use pre-renderd/grabed/screenshoted images for this too.

您可以添加discard和/或更改最终的if逻辑以更改行为(例如,您只需要轮廓,而内部没有网格等).或者,您可以添加轮廓颜色来呈现颜色,而不是直接分配轮廓颜色来获得高光的印象……而不是着色

You can add discard and or change the final if logic to change behaviour (like you want just outline and no mesh inside etc ...). Or you can add the outline color to render color instead of assigning it directly to get the impression of highlight ... instead of coloring

请参阅修改后的片段中的a),b),c)选项:

see a),b),c) options in modified fragment:

// 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接近零.可以直接利用它.这里是着色器的更新:

As you can not access client side code this approach will work in shader only. For smooth (curved) edged shapes the surface normal is near perpendicular to camera view axis (z). So dot between them is near zero. This can be exploited directly ... Here update of the shaders:

顶点

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

这里预览:

如您所见,它对于光滑的对象正常工作,但是对于像立方体这样的尖锐边缘根本不起作用...您可以使用与先前方法相同的组合(a,b,c).

As you can see it works properly for smooth objects but for sharp edges like on cube is this not working at all... You can use the same combinations (a,b,c) as in previous approach.

m保存原点设置为(0,0,0)的模型视图矩阵.这样就可以进行矢量转换(无需翻译).有关更多信息,请参见了解4x4均匀变换矩阵.

The m holds modelview matrix with origin set to (0,0,0). That enables it for vector conversion (no translation). For more info see Understanding 4x4 homogenous transform matrices.

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

The 0.5 in the dot product result if is the thickness of outline. 0.0 means no outline and 1.0 means whole object is outline.

这篇关于轮廓轮廓着色器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆