文章出處

+BIT祝威+悄悄在此留下版了個權的信息說:

CSharpGL(14)用geometry shader渲染模型的法線(normal)

+BIT祝威+悄悄在此留下版了個權的信息說:

2016-08-13

由于CSharpGL一直在更新,現在這個教程已經不適用最新的代碼了。CSharpGL源碼中包含10多個獨立的Demo,更適合入門參考。

為了盡可能提升渲染效率,CSharpGL是面向Shader的,因此稍有難度。

問題

在處理光照效果等問題時,模型的頂點的法線是必不可少的數據。但是法線并不直接顯示在模型上,也沒有別的辦法可以直觀地看到。如果法線計算錯了,那是非常難以排查的。所以我就想用geometry shader來渲染出模型的法線。如下圖的白色針狀部分,就是這個teapot的法線。為了便于區分,針尖部分是頂點位置,較粗的針頭部分則是法線的方向。從這個圖中可以看到,我做的這個teapot的法線是很有問題的。怪不得之前拿它試驗光照效果時會有一些詭異的現象。

+BIT祝威+悄悄在此留下版了個權的信息說:

下載

這個示例是CSharpGL的一部分,CSharpGL已在GitHub開源,歡迎對OpenGL有興趣的同學加入(https://github.com/bitzhuwei/CSharpGL

 

+BIT祝威+悄悄在此留下版了個權的信息說:

原理

Geometry shader的執行,在vertex shader之后,在fragment shader之前。Geometry shader的輸入數據是一個primitive(points、lines、triangles等),輸出可以是0或多個primitive(points、line_strip或triangle_strip)。Geometry shader的作用,就是能增加新的圖元。本篇就利用這個功能,將模型頂點的法線作為新增的圖元渲染出來。

+BIT祝威+悄悄在此留下版了個權的信息說:

Geometry shader

Geometry shader代碼如下,含義參考注釋即可。如果要用此shader,最好刪掉中文注釋。因為有的顯卡可能不支持中文,會造成無法編譯通過的情況。

 1 #version 410 core
 2 
 3 //輸入類型為三角形
 4 layout (triangles) in;
 5 //輸出的是三角形帶
 6 layout (triangle_strip, max_vertices = 11) out;
 7 
 8 uniform mat4 modelMatrix;
 9 uniform mat4 viewMatrix;
10 uniform mat4 projectionMatrix;
11 
12 uniform float normalLength = 0.5f;
13 
14 in VS_GS_VERTEX
15 {
16     vec3 normal;
17 } vertex_in[];
18 
19 out GS_FS_VERTEX
20 {
21     vec3 color;
22 } vertex_out;
23 
24 void main(void)
25 {
26     int i;
27     //先輸出模型本身
28     for (i = 0; i < gl_in.length(); i++) {
29         vertex_out.color = vertex_in[i].normal;
30         vec4 position = gl_in[i].gl_Position;
31         gl_Position = projectionMatrix * viewMatrix * (modelMatrix * position);
32         EmitVertex();
33     }
34     EndPrimitive();
35 
36     //生成頂點的法線(一個法線用一個三棱柱表示)
37     for (i = 0; i < gl_in.length(); i++) {//我的理解:此處gl_in.length()為3
38         //法線顏色為白色
39         vertex_out.color = vec3(1, 1, 1);
40 
41          //獲取模型的頂點位置(針尖)
42         vec4 position = gl_in[i].gl_Position;
43         //獲取模型的法線(針頭)位置
44         vec4 target = position + vertex_in[i].normal * normalLength;
45         {
46             vec4 v0 = position;
47             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v0);
48             EmitVertex();//生成一個三棱柱頂點
49 
50             vec4 v1 = target;
51             if (target.x > position.x) { v1.x += normalLength / 30.0f; }
52             else { v1.x -= normalLength / 10.0f; }
53             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v1);
54             EmitVertex();//生成一個三棱柱頂點
55 
56             vec4 v2 = position;
57             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v2);
58             EmitVertex();//生成一個三棱柱頂點
59 
60             vec4 v3 = target;
61             if (target.y > position.y) { v3.y += normalLength / 30.0f; }
62             else { v3.y -= normalLength / 10.0f; }
63             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v3);
64             EmitVertex();//生成一個三棱柱頂點
65             
66             vec4 v4 = position;
67             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v4);
68             EmitVertex();//生成一個三棱柱頂點
69 
70             vec4 v5 = target;
71             if (target.z > position.z) { v5.z += normalLength / 30.0f; }
72             else { v5.z -= normalLength / 10.0f; }
73             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v5);
74             EmitVertex();//生成一個三棱柱頂點
75 
76             vec4 v6 = position;
77             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v6);
78             EmitVertex();//生成一個三棱柱頂點
79 
80             vec4 v7 = target;
81             if (target.x > position.x) { v7.x += normalLength / 30.0f; }
82             else { v7.x -= normalLength / 10.0f; }
83             gl_Position = projectionMatrix * viewMatrix * (modelMatrix * v7);
84             EmitVertex();//生成一個三棱柱頂點
85 
86         }
87         
88         EndPrimitive();//依據上面的8個頂點,為此頂點的法線生成一個三棱柱
89     }
90 }
geometry shader

 

輸入\輸出類型

Geometry shader允許的輸入輸出類型,可以參考(https://www.opengl.org/wiki/Geometry_Shader

GS input

OpenGL primitives

TES parameter

vertex count

points​

GL_POINTS

point_mode​

1

lines​

GL_LINES, GL_LINE_STRIP, GL_LINE_LIST

isolines​

2

lines_adjacency​

GL_LINES_ADJACENCY, GL_LINE_STRIP_ADJACENCY

N/A

4

triangles​

GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN

triangles​, quads​

3

triangles_adjacency​

GL_TRIANGLES_ADJACENCY, GL_TRIANGLE_STRIP_ADJACENCY

N/A

6

輸出類型只能是points​、line_strip或triangle_strip。

Geometry shader的一個特點,就是從vertex shader傳過來的頂點,在經過geometry shader后就消失了,不會再傳到fragment shader。所以為了保證仍舊渲染模型本身,輸入、輸出類型必須​一致。于是我只能選triangles\triangle_strip。

當然,用一套shader program渲染模型,再用另一套來渲染法線,那也是可以的。我不這么做一是因為懶,二是希望內存中只有一套數據,三是希望不去更改目前的CSSL+Renderer框架。

uniform

Geometry shader中的uniform變量與vertex shader、fragment shader中的相同。

in\out類型變量

例如上面的

1 in VS_GS_VERTEX
2 {
3     vec3 normal;
4 } vertex_in[];

必須用數組的形式。這符合輸入數據會有多個頂點的實際。

而out類型的變量,例如上面的

1 out GS_FS_VERTEX
2 {
3     vec3 color;
4 } vertex_out;
+BIT祝威+悄悄在此留下版了個權的信息說:

與vertex shader、fragment shader中的形式相同。但是它可以被多次使用,在使用后,每調用一次 EmitVertex(); 就會生效一次(產生一個頂點,此頂點在fragment shader中的GS_FS_VERTEX.color字段就是剛剛設置的值)。

可以說,geometry shader的工作方式是:根據輸入的圖元的各個頂點信息,根據業務需要,設置好你需要的新頂點的各項信息,然后用EmitVertex();,就會生成一個頂點,并傳給fragment shader。而調用 EndPrimitive(); 時,就會結束一個圖元的生成過程(同時開始下一個圖元的生成過程)。

gl_Position

geometry shader中是可以設置頂點的位置的。因此就不宜直接在vertex shader中設置了。

 

+BIT祝威+悄悄在此留下版了個權的信息說:

填坑

坑人的是geometry shader的layout in\out類型不支持quads。而我之前做的球體、立方體模型用的都是quad_strip類型的索引。試驗證明這樣的模型是用不了geometry shader的。所以我只好做一套用triangles或triangle_strip做索引的球體了。

坑填完后,效果如下圖。

我制作的球體模型:

我意外制作的“冰激凌”模型:

我制作的立方體模型:

 

+BIT祝威+悄悄在此留下版了個權的信息說:

總結

實際上,目前的法線會出現不穩定且無限延長的現象,這個問題待解決。

2016-02-16

解決法線不穩定的問題

這個不穩定問題在我整理了CSSL2GLSL后消失了。原因也未知,可能與下面這個問題相關。

下圖中的法線沒有完全顯示。而是每個面只顯示了一個。這是因為在geometry shader中的 layout (triangle_strip, max_vertices = 11) out; 最大頂點數太小了,改到27個頂點(=1個三角形的3個頂點+3個三棱柱的8個頂點)即可。

正常顯示的立方體法線圖:

 CSSL支持geometry shader

現在的CSSL支持geometry shader的編寫和代碼生成。并且,代碼生成過程中也會自動解析用戶自定義的結構類型,例如下面這樣的:

1 in VS_GS_VERTEX
2 {
3     vec3 normal;
4 } vertex_in[];

還有下面這樣的,都支持。

1 out GS_FS_VERTEX
2 {
3     vec3 color;
4 } vertex_out;
對應上面的兩個GLSL類型,C#中的CSSL寫法是這樣的:
1         class VS_GS_VERTEX
2         {
3             public vec3 normal;//必須是public的字段
4         }
5         [In]
6         VS_GS_VERTEX[] vertex_in;
1         class GS_FS_VERTEX
2         {
3             public vec3 color;//必須是public的字段
4         }
5         [Out]
6         GS_FS_VERTEX vertex_out;

初始值的自動轉化

此外,像下面這樣的C#中的初始值也支持自動轉化到GLSL。
1         [Uniform]
2         float normalLength = 0.5f;

這個會自動轉換為GLSL中的:

1 uniform float normalLength = 0.5;

debug 

既然teapot的法線計算有問題,那就來debug。改正normal的計算程序后,得到了正常的法線圖。
+BIT祝威+悄悄在此留下版了個權的信息說:

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()