这一章介绍怎么用顶点数组创建一个3D 模型,如何创建一个shape 对象,缓冲(buffers) ,域(fields)
由于要定义这个3D 模型的每个顶点,然后存入顶点数组,所以这章不会画出一个比较复杂的3D 模型,我们只是画一个立方体来说明如何创建一个3D 模型,如果对于一个复杂的3D 模型还是一个个顶点画的话,只能说太牛逼了,那时候就要用到3dmax ,maya
上一章中直接用了一个createSphere ()函数创建了一个球体,这个函数是o3d 自带的,其实也有自带的创建立方体的函数,当然这章是不会用的,否则就没东西可以讲了。我们要做的就是自己写一个。
function createCube(material, size){
Size
在o3d 中首先要做的便是创建一个shape 对象,一个图元(primitive )对象和一个streamBank
var cubeShape = g_pack . createObject ( 'Shape' );
var cubePrimitive = g_pack . createObject ( 'Primitive' );
var streamBank = g_pack . createObject ( 'StreamBank' );
cubePrimitive . material = material ; //
cubePrimitive . owner = cubeShape ; // 定义shape
cubePrimitive . streamBank = streamBank ; ·· // 定义这个shape 的streamBank
shape ( 不像纸上画画最重要的是把每条线画出来,在O3d 或者说是传统的openGL 和D3D 其中点与点之间的连接可以有多种规律,比如说比较常用的每三个点通过连线组成三角形,多个点连成多个连续的三角形,每四个点组成的四边形,多个点连成的多个连续的四边形等等。
下面就要定义一下图元中各个点之间连接的规律了
cubePrimitive . primitiveType = g_o3d . Primitive . TRIANGLELIST ; //
cubePrimitive . numberPrimitives = 12 ; // 这个图元中有12
cubePrimitive . numberVertices = 8 ; // 有8
创建一个绘制元素
cubePrimitive . createDrawElement ( g_pack , null );
8 个顶点的数据,在js 中这些数据都是存放在一个一维数组中的:
var positionArray = [
- size/2 , - size/2 , size/2 , // 顶点0
size/2 , - size/2 , size/2 , // 顶点1
- size/2 , size/2 , size/2 , // 顶点2
size/2 , size/2 , size/2 , // 顶点3
- size/2 , size/2 , - size/2 , // 顶点4
size/2 , size/2 , - size/2 , // 顶点5
- size/2 , - size/2 , - size/2 , // 顶点6
size/2 , - size/2 , - size/2 // 顶点7
];
size/2 (还记得size 是这个函数传进来的参数吧)都是每个顶点的坐标。
现在就有个问题,就是size 要多大才能够在屏幕当中显示一个身材合适的立方体呢,一开始可能会假象这个size 取成10 吧,10 这个数字不大不小蛮好的,可是你会发现屏幕中除了一片灰色就什么都没有了,因为10 对于现在这个情况来说太大了,连摄像机都在那个立方体里面了(当然你不会看到立方体里面那个面,因为一般情况下o3d 是只绘制一个面的)。
在o3d 中到底用的是什么单位导致10 这个在现实世界中这么小的一个数字能够变得这么大。咳咳,其实貌似在这样的3D 世界中是没有单位之说的( 至少我还不知道) ,因为这个物体的大小是相对于镜头的位置的,如果镜头离物体近了,size 就算很小那物体还是庞然大物,因此想要调整物体大小,可以调整size 的大小,也可以把镜头拉远(还记得第一章中设置照相机的位置吧),也可以把物体放远点,这些都是跟真实世界中差不多的。
O3d 中这些顶点的坐标数据都会先放入一个缓冲(buffers) 中,缓冲是一个对象(object) ,用来暂时存储顶点的各种数据的,像位置坐标,色彩,贴图坐标。下面就先创建缓冲对象:
// 创建顶点缓冲来存放顶点数据
var positionsBuffer = g_pack.createObject('VertexBuffer');
然后创建域(field) ,域是一小块buffer 。
var positionsField = positionsBuffer . createField ( 'FloatField' , 3 );
positionsBuffer . set ( positionArray ); //
在一开始有说到要创建一个streamBank 对象,但是没解释为什么,这个真是说来话长啊,因为o3d 使用的是可编程渲染管线(即shader ),而非固定函数渲染管线(fixed function pipeline) ,shader (又可以叫做着色器)可以分为vertex shader (顶点着色器)和pixel shader (像素着色器)。有了pixel shader 之后,你能决定里面的每个像素该怎么填充。因为GPU 浮点运算能力比CPU 比显卡强得多,所以把这些计算交给显卡做再合适不过了。
而这次绘制正方形,每个顶点也是通过vertex shader 来渲染的,是不是有点大材小用了,其实我也这么觉得,不过我们还好暂时还不用去写这个shader ,可以让Js 的接口来做这件事,这就是这个streamBank 做的了,streamBank 把域(field) 中的数据作为vertex shader 的输入。可以是js 中的顶点数据和底层shader 交互的通道。下面这段代码就是把域(field )和vertex shader 联系起来。
streamBank . setVertexStream (
g_o3d . Stream . POSITION , // semantic: This stream stores vertex positions
0 , // semantic index: First (and only) position stream
positionsField , // field: the field this stream uses.
0 ); // start_index: How many elements to skip in the field.
( 注释的英文我就不翻译了- -)
最后你可以创建一个index buffer ,这个是用来重排上面vertex buffer 里各个点的得排列顺序的。你可以不加这个,不过显示出来的是不是一个立方体就不一定了- - 。
var indicesArray = [
0 , 1 , 2 , // face 1
2 , 1 , 3 ,
2 , 3 , 4 , // face 2
4 , 3 , 5 ,
4 , 5 , 6 , // face 3
6 , 5 , 7 ,
6 , 7 , 0 , // face 4
0 , 7 , 1 ,
1 , 7 , 3 , // face 5
3 , 7 , 5 ,
6 , 0 , 4 , // face 6
4 , 0 , 2
];var indexBuffer = g_pack . createObject ( 'IndexBuffer' );
indexBuffer . set ( indicesArray );
cubePrimitive . indexBuffer = indexBuffer ;
Index buffer 和vertex buffer 的创建方式和存储方式是差不多的. 是两个差不多的Object ,都是用来存储数据的。
函数最后就是把这个已经创建好的shape 返回了。
return cubeShape;
}
函数写好了,得试下先,把前一章用来创建一个球体的语句替换掉,替换成
var shape = createSphere(material,0.5);
然后就会发现原来那个很丑的圆就变成一个很丑的正方形了。为了体现出这是一个立方体,而不是一个正方形,我们还要做透视投影变换,这个得用shading language 了,
<textarea id="effect">
// World View Projection matrix that will transform the input vertices
// to screen space.
float4x4 worldViewProjection : WorldViewProjection;
// input parameters for our vertex shader
struct VertexShaderInput {
float4 position : POSITION;
};
// input parameters for our pixel shader
struct PixelShaderInput {
float4 position : POSITION;
};
/**
* The vertex shader simply transforms the input vertices to screen space.
*/
PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
PixelShaderInput output;
// Multiply the vertex positions by the worldViewProjection matrix to
// transform them to screen space.
output.position = mul(input.position, worldViewProjection);
return output;
}
/**
* This pixel shader just returns the color red.
*/
float4 pixelShaderFunction(PixelShaderInput input): COLOR {
return float4(1, 0, 0, 1); // Red.
}
// Here we tell our effect file *which* functions are
// our vertex and pixel shaders.
// #o3d VertexShaderEntryPoint vertexShaderFunction
// #o3d PixelShaderEntryPoint pixelShaderFunction
// #o3d MatrixLoadOrder RowMajor
</textarea>
shading language 了,它放在一个textarea 中方便js 程序获取,可以看到里面有两个函数,vertexShaderFunction 和pixelShaderFunction ,分别为vertex shader 和pixelShader 的入口,这个怎么工作,通俗点讲vertexShaderFunction 就是把每个顶点的数据传进去后经过一定的计算后再返回进行绘制。pixelShaderFunction 也是这样,在这里vertexShaderFunction 就一句代码:output.position = mul(input.position, worldViewProjection); 就是进行了一次投影变换. 而pixelShaderFunction 就只是把这个立方体的颜色变成了红色。最后三句不要看成是注释啊,前两句申明了vertex shader 的入口函数是vertexShaderFunction , 的入口函数是pixelShaderFunction
接下来就要用这段代码作为这个立方体的shader string, 还记不记得在前面创建过一个effect ,在后面加上下面两句代码
var shaderString = document.getElementById('effect').value;
effect.loadFromFXString(shaderString); // 载入shading language
然后就会发现这个图形已经有立方体的样子了(我们已经能够脑淫出这是个立方体了- - )。
换个视角看会更像,还记得上一章设置摄像机参数吧。
viewInfo.drawContext.view = g_math.matrix4.lookAt([2, 2, 2], // 将摄像机放置在(2 ,2 ,2
[0, 0, 0], //
[0, 1, 0]); // up
关于shading language 和material 这里只是出于需要稍微涉及下,以后会详细地介绍,就像cg 的强大一样,o3d shading language 对于o3d