three.js中的webgl_clipping例子實現(xiàn)了針對指定用戶裁切平面進行裁切的功能肠鲫。在現(xiàn)代圖形管線中干毅,通過API指定用戶裁切平面的功能已經(jīng)廢棄。但是這個功能很容易在shader中實現(xiàn)院溺。
實現(xiàn)過程
webgl_clpping例子首先實現(xiàn)了環(huán)結幾何體(torusknot geometry)數(shù)據(jù)的生成功能眉菱,接著使用phong shading對環(huán)結幾何體進行渲染。隨后指定用戶裁切平面拐格,在眼睛坐標空間對幾何體進行裁切率拒。(three.js在此處借助自己的框架實現(xiàn)優(yōu)勢,實現(xiàn)了更加復雜的場景禁荒。在例子中不僅輕松添加了陰影效果猬膨,并同時實現(xiàn)了對用戶裁切平面功能在所有shader上的動態(tài)支持)
模仿webgl_clipping例子,只從實現(xiàn)用戶裁切平面的角度呛伴,使用C++和OpenGL ES 3.0獲得了如下的渲染效果勃痴,iOS版本實現(xiàn)源碼可以從github上獲取。
Torusknot(環(huán)結)幾何體生成和渲染
webgl_clipping使用程序生成了torus knot幾何體數(shù)據(jù)热康,并對其使用phong shading方式進行渲染沛申。
1. Torus knot幾何體數(shù)據(jù)的生成
環(huán)結幾何體使用三角形幾何公式依據(jù)不同的旋轉弧度計算生成。代碼如下:
/**
* class TorusKnotGeometry constructor
* @param radius 整個torus knot環(huán)形結幾何體的半徑
* @param tube 環(huán)形結輪廓管道半徑
* @param tubularSegments 管道的數(shù)據(jù)段數(shù)
* @param radialSegments 環(huán)形幾何體橫截面的數(shù)據(jù)段數(shù)
* @param p 環(huán)形幾何體圍繞中心軸扭轉的圈數(shù)
* @param q 環(huán)形幾何體內部圓環(huán)的數(shù)量
*
*/
TorusKnotGeometry(float radius=1.0,float tube=0.4,int tubularSegments=64,int radialSegments=8,int p=2,int q=3){
...
// helper variables
Cvec3 vertex;
Cvec3 normal;
Cvec3 P1,P2;
Cvec3 B,T,N;
// 生成頂點姐军,法線和紋理坐標
// tubularSegments為環(huán)結幾何體圍繞圍繞中心軸旋轉p圈的角度上所分的段數(shù)
for (int i = 0; i <= tubularSegments; ++ i) {
// u為每個管道端所占據(jù)的弧度radian铁材,用以計算當前分段位置出環(huán)形曲面上的位置
float u = (float)i / tubularSegments * p * M_PI * 2;
//P1為當前曲面位置上的點,P2為稍微靠前一點弧度曲面上的點奕锌。
//這兩個點用于生成一個特定的”坐標系“著觉,用于計算正確的頂點位置。
Cvec3 P1 = calculatePositionOnCurve(u, p, q, radius);
Cvec3 P2 = calculatePositionOnCurve(u + 0.01, p, q, radius);
//計算出正交標準化的切面空間坐標系[T,B,N]
//T為P1點上的切線矢量
Cvec3 T = P2-P1;
//將P1和P2作為從環(huán)結幾何體坐標系原點而來的矢量惊暴,計算出半路half-way矢量作為法線計算的輔助矢量
Cvec3 N = P2+P1;
//計算出半法線bi-normal矢量
Cvec3 B = cross(T, N);
//再計算出真正的法線normal矢量
N = cross(B, T);
//標準化 B, N, T饼丘。
B.normalize();
N.normalize();
T.normalize();
for (int j = 0; j <= radialSegments; ++ j ) {
//注意此處我們在xy-平面塑造形狀,無需計算z-值辽话。
//環(huán)結幾何體圍繞中心軸旋轉弧度分段中肄鸽,每一段的弧度radians
float v = j / (float)radialSegments * M_PI * 2;
float cx = -tube * cos(v);
float cy = tube * sin(v);
//計算添加圍繞環(huán)結中心軸(z軸)旋轉的頂點的最終值
Cvec3 vertex;
vertex[0] = P1[0] + (cx * N[0] + cy * B[0]);
vertex[1] = P1[1] + (cx * N[1] + cy * B[1]);
vertex[2] = P1[2] + (cx * N[2] + cy * B[2]);
vertices.push_back(vertex);
//P1總是位于相關被計算頂點的中心,據(jù)此計算法線
Cvec3 normal=vertex - P1;
normal.normalize();
normals.push_back(normal);
//紋理坐標的計算
Cvec2 uv(i / (float)tubularSegments,j / (float)radialSegments);
uvs.push_back(uv);
}
}
//生成繪制頂點的索引集合
for (int j = 1; j <= tubularSegments; j ++ ) {
for (int i = 1; i <= radialSegments; i ++ ) {
//索引值
int a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 );
int b = ( radialSegments + 1 ) * j + ( i - 1 );
int c = ( radialSegments + 1 ) * j + i;
int d = ( radialSegments + 1 ) * ( j - 1 ) + i;
//三角形面
//indices.push( a, b, d );
indices.push_back(a);
indices.push_back(b);
indices.push_back(d);
//indices.push( b, c, d );
indices.push_back(b);
indices.push_back(c);
indices.push_back(d);
}
}
幾何數(shù)據(jù)生成后油啤,生成對應的vertex buffer object和index buffer object典徘,使用GL_TRIANGLES圖元(primitives)類型進行繪制,使用phong shading方式進行著色渲染益咬,效果如下:
用戶裁切平面的實現(xiàn)
在OpenGL ES 1.1和OpenGL版本中提供固定功能方式的API用于實現(xiàn)裁切平面逮诲。但是當使用現(xiàn)代渲染管線(shader方式)時,并不能使用這個API,不過這個功能在shader中很容易實現(xiàn)汛骂。實現(xiàn)原理使用平面的數(shù)學知識。一個平面可以使用公式來指定评腺,公式中
為平面的法線帘瞭,隨后使用
來計算點
到這個平面的距離,如果
蒿讥,則認為這個點位于這個平面所指定的半空間之內蝶念,不需要被裁切,如果
,則認為這個點位于指定的半空間之外芋绸,需要被裁切媒殉。
在shader中實現(xiàn)上面的思路時,通常在眼睛坐標系中計算裁切平面摔敛,這種裁切效果比較符合人的經(jīng)驗預期廷蓉。shader相關代碼如下:
//vertex shader
...
//uniform變量 -- 4X4模型視圖矩陣
uniform mat4 uModelViewMatrix;
//眼睛坐標指定的用戶裁切平面數(shù)據(jù)
uniform vec4 uUserClipPlane;
//頂點位置
layout(location = 0) in vec3 myVertex;
//頂點到平面的距離變量
out float vDistance;
void main(void){
vec4 p = vec4(myVertex,1);
//轉換頂點
vec4 eyePos = uModelViewMatrix * p;
vec3 ecPosition = eyePos.xyz;
//計算到user clip plane的距離
vDistance = dot(ecPosition,uUserClipPlane.xyz)+uUserClipPlane.w;
...
}
//fragment shader
...
out vec4 fragColor;
void main(){
//如果位于指定半空間之外,則廢棄這個像素
if(vDistance<0.0)
discard;
...
}