1:矩阵变换
case Projection::_3D:
{
float zeye = this->getZEye();
Mat4 matrixPerspective,matrixLookup;
loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION);
#if CC_TARGET_PLATFORM == CC_PLATFORM_WP8
//if needed,we need to add a rotation for Landscape orientations on Windows Phone 8 since it is always in Portrait Mode
GLView* view = getOpenGLView();
if(getOpenGLView() != nullptr)
{
multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,getOpenGLView()->getOrientationMatrix()); } #endif // issue #1334 Mat4::createPerspective(60,(GLfloat)size.width/size.height,10,zeye+size.height/2,&matrixPerspective); multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,matrixPerspective); Vec3 eye(size.width/2,size.height/2,zeye),center(size.width/2,0.0f),up(0.0f,1.0f,0.0f); Mat4::createLookAt(eye,center,up,&matrixLookup); multiplyMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION,matrixLookup); loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW); break; }
好奇的是为什么视图矩阵放在投影矩阵栈中的呢?网上我也看到过别人的吐槽,但是cocos为什么这么做肯定是有它的原因的。仔细一想其实这样是很正常的,一个顶点坐标要经过MVP变换,但是cocos只会传入模型变换(相对根节点的各种变换,但是投影和视图变换对所有节点都是一样的), 虽然模型和视图矩阵总是放在一起的,但其实也没必要这么死板,M*(V*p) = (M*V)*P,反正都一样。既然视图变换不会改变,为何不把视图和投影放在一起呢。如果真的要纠结这个问题,那我们就试着改一下吧,将视图变换移到loadIdentityMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);
把
MATRIX_STACK_PROJECTION换成MATRIX_STACK_MODELVIEW,其实也是一样的。
10和zeye+size.height/2分别代表了近截面和远截面。如果一个顶点的z 坐标是9或者 zeye+size.height/2 +1 ,将都显示不出来。
2:节点的变换
const Mat4& Node::getNodeToParentTransform() const
{
if (_transformDirty)
{
// Translate values
float x = _position.x;
float y = _position.y;
float z = _positionZ;
if (_ignoreAnchorPointForPosition)
{
x += _anchorPointInPoints.x;
y += _anchorPointInPoints.y;
}
bool needsSkewMatrix = ( _skewX || _skewY );
Vec2 anchorPoint(_anchorPointInPoints.x * _scaleX,_anchorPointInPoints.y * _scaleY);
// caculate real position
if (! needsSkewMatrix && !_anchorPointInPoints.equals(Vec2::ZERO))
{
x += -anchorPoint.x;
y += -anchorPoint.y;
}
// Build Transform Matrix = translation * rotation * scale
Mat4 translation;
//move to anchor point first,then rotate
Mat4::createTranslation(x + anchorPoint.x,y + anchorPoint.y,z,&translation); //需要绕锚点旋转,先移动到锚点
Mat4::createRotation(_rotationQuat,&_transform);
if (_rotationZ_X != _rotationZ_Y) //表示这部分还没懂
{
// Rotation values
// Change rotation code to handle X and Y
// If we skew with the exact same value for both x and y then we're simply just rotating
float radiansX = -CC_DEGREES_TO_RADIANS(_rotationZ_X);
float radiansY = -CC_DEGREES_TO_RADIANS(_rotationZ_Y);
float cx = cosf(radiansX);
float sx = sinf(radiansX);
float cy = cosf(radiansY);
float sy = sinf(radiansY);
float m0 = _transform.m[0],m1 = _transform.m[1],m4 = _transform.m[4],m5 = _transform.m[5],m8 = _transform.m[8],m9 = _transform.m[9];
_transform.m[0] = cy * m0 - sx * m1,_transform.m[4] = cy * m4 - sx * m5,_transform.m[8] = cy * m8 - sx * m9;
_transform.m[1] = sy * m0 + cx * m1,_transform.m[5] = sy * m4 + cx * m5,_transform.m[9] = sy * m8 + cx * m9;
}
_transform = translation * _transform; //旋转矩阵
//move by (-anchorPoint.x,-anchorPoint.y,0) after rotation
_transform.translate(-anchorPoint.x,0); //之前移到了锚点,现在移回。
if (_scaleX != 1.f)
{
_transform.m[0] *= _scaleX,_transform.m[1] *= _scaleX,_transform.m[2] *= _scaleX;
}
if (_scaleY != 1.f)
{
_transform.m[4] *= _scaleY,_transform.m[5] *= _scaleY,_transform.m[6] *= _scaleY;
}
if (_scaleZ != 1.f)
{
_transform.m[8] *= _scaleZ,_transform.m[9] *= _scaleZ,_transform.m[10] *= _scaleZ;
}
//以上为translation * rotation * scale
//其中rotation采用四元数来做的,具体请看其他blog。_rotationQuat如何生成,旋转矩阵如何从四元数来的。
// FIXME:: Try to inline skew
// If skew is needed,apply skew and then anchor point
if (needsSkewMatrix)
{
float skewMatArray[16] =
{
1,(float)tanf(CC_DEGREES_TO_RADIANS(_skewY)),0,(float)tanf(CC_DEGREES_TO_RADIANS(_skewX)),1,1
};
Mat4 skewMatrix(skewMatArray);
_transform = _transform * skewMatrix; //也还没懂
// adjust anchor point
if (!_anchorPointInPoints.equals(Vec2::ZERO)) //这部分也还没懂
{
// FIXME:: Argh,Mat4 needs a "translate" method.
// FIXME:: Although this is faster than multiplying a vec4 * mat4
_transform.m[12] += _transform.m[0] * -_anchorPointInPoints.x + _transform.m[4] * -_anchorPointInPoints.y;
_transform.m[13] += _transform.m[1] * -_anchorPointInPoints.x + _transform.m[5] * -_anchorPointInPoints.y;
}
}
if (_useAdditionalTransform)
{
_transform = _transform * _additionalTransform;
}
_transformDirty = false;
}
return _transform;
}
一些链接:
万向节死锁问题
四元数和旋转矩阵
四元数和旋转矩阵
3: 使用的shader
sprite竟然使用的是SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP,没有mv矩阵的,只有投影矩阵CC_PMatrix。
在fillQuads已经对QuadCommand对应的节点进行了变换。
感觉很乱啊。(我的版本是3.4。)
4:离屏渲染和帧缓冲对象,渲染缓冲区对象
请参考:
可以用来实现场景截图。参考cocos2d的RenderTexture。
5: 模板测试,alpha测试和遮罩
cocos2d的遮罩ClippingNode采用模板测试和alpha测试。
设置翻转属性可以决定是显示模板内的背景还是模板外的背景。
先将不翻转的情况吧,此时显示模板内的背景区域。
如果做到这点呢?
1:先覆盖模板缓冲,设置为0;此时如果要在一个点绘制一个像素,肯定不能成功。
glStencilMask(mask_layer);
glStencilFunc(GL_NEVER,mask_layer,mask_layer);
glStencilOp(!_inverted ? GL_ZERO : GL_REPLACE,GL_KEEP,GL_KEEP);
2:然后绘制模板
glStencilFunc(GL_NEVER,mask_layer);
glStencilOp(!_inverted ? GL_REPLACE : GL_ZERO,GL_KEEP);
_stencil->visit(renderer,_modelViewTransform,flags);
此时在模板区域内的值都为mask_layer(glStencilMask),其他部分的值还是0。模板并没有绘制出来,它只是为了填充模板缓冲中的一部分模板值。
3:绘制背景
glStencilFunc(GL_EQUAL,_mask_layer_le,_mask_layer_le);
glStencilOp(GL_KEEP,GL_KEEP);
this->draw(renderer,flags);
GL_EQUAL表示只有模板值和_mask_layer_le相等,才通过验证,但是目前模板缓冲中的所有值要么是0,要么是mask_layer。(这里我也是又点疑问的,为什么是_mask_layer_le,知道它是为了多层嵌套,但是还是理解不了它要做的效果。如果没有多层嵌套_mask_layer_le = mask_layer。估计先不管那么多吧。我们就考虑单层的情况吧!),只有模板区域内的模板值和参考值相等,所以只有模板区域内的像素能画出来。
现在拿一张背景图和一张部分透明的模板图做成遮罩,那部分透明的区域,我也不像显示背景的部分,该怎么做呢?此时加入alpha测试,alpha测试在模板测试之前,所以设置模板样图中透明部分不参加模板测试就好了,alpha测试过滤掉模板样图的透明部分。
做法如下:
// enable alpha testing
glEnable(GL_ALPHA_TEST);
// pixel will be drawn only if greater than an alpha threshold
glAlphaFunc(GL_GREATER,_alphaThreshold);
此时只有alpha值大于_alphaThreshold的模板区域才会去进行模板测试。
渲染完模板样图之后,要恢复alpha测试之前的状态。
if (_alphaThreshold < 1.0) //恢复以前的alpha测试的状态
{
glAlphaFunc(_currentAlphaTestFunc,_currentAlphaTestRef);
if (!_currentAlphaTestEnabled)
{
glDisable(GL_ALPHA_TEST);
}
}
6:绘图
(1):cocos2d drawNode绘制点:
这个比较简单,代码很好懂,我实现了一遍,碰到的几个问题:
glDrawArrays(GL_POINTS,_bufferCountGLPoint);//正确
glDrawArrays(GL_POINT,_bufferCountGLPoint);//错误
我不小心写成了下面这种,导致不能正确显示出来。那如何断点到这里的呢,我们可以获取opengl错误glGetError,cocos2d里面也封装了获取opengl错误的函数:CHECK_GL_ERROR_DEBUG,直接拿来调试就好了。
它们有什么区别呢:GL_POINTS是绘制模式是图元,GL_POINT多用来表示多边形的绘制填充模式(轮廓点式多边形、轮廓线式多边形或全填充式多边形)。
(2):还有个疑问,那就是glBufferData中的GL_STREAM_DRAW,为什么绘制点的时候是GL_STREAM_DRAW模式呢?
网上这样描述几种的区别:GL_STATIC_DRAW,GL_STREAM_DRAW,GL_DYNAMIC_DRAW用于给OpenGL系统提醒:预期数据是一直不变、数据每帧变一次或几帧变一次、数据每帧变两三次以上,方便硬件内部优化吧。
(3):吐槽一下:
GL::bindVAO(_vaoGLPoint);
glGenBuffers(1,&_vboGLPoint);
glBindBuffer(GL_ARRAY_BUFFER,_vboGLPoint);
glBufferData(GL_ARRAY_BUFFER,sizeof(V2F_C4B_T2F)*_bufferCapacityGLPoint,_bufferGLPoint,GL_STREAM_DRAW);
DrawNode初始化vao,vbo就向显卡传递了所有初始化的数据(这些根据就没用)
glBindBuffer(GL_ARRAY_BUFFER,GL_STREAM_DRAW);
这里又传递了所有数据。真的有这个必要嘛!!!
我的修改:
glBufferData(GL_ARRAY_BUFFER,GL_STREAM_DRAW);
换成:
glBufferData(GL_ARRAY_BUFFER,nullptr,GL_STREAM_DRAW);
######分割线
glBufferData(GL_ARRAY_BUFFER,GL_STREAM_DRAW);
换成
glBufferData(GL_ARRAY_BUFFER,_bufferGLpointCount*sizeof(V2F_C4F_T2F),_bufferPoints,GL_STREAM_DRAW);
也是同样的效果,如果不对,望大神指正。
(4):如何制定点的大小:(cocos2d的点是方形的)
V2F_C4F_T2F point = { position,color,Tex2F(pointSize,0) };
一时不解,改了这个Tex2F(pointSize,0),换成Tex2F(pointSize,pointSize),Tex2F(0,0)如何呢,发现后两个都没反应,默认大小还是1。于是查看了shader,才发现问题所在:
gl_Position = CC_MVPMatrix * a_position;
gl_PointSize = a_texCoord.x;
v_fragmentColor = a_color;
(7):颜色有问题
通过drawNode画点或者画线。颜色都感觉有问题,我传进去Color4F(255.0,255.0)红色,显示出来的却是淡蓝色,于是查看了下shader,下面是line的着色器:
v_color = vec4(a_color.rgb * a_color.a,a_color.a);
将v_color 硬设置成(1.0,0.0,1.0)是ok的,所以问题就出在我们的程序到顶点着色器。
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR,4,GL_UNSIGNED_BYTE,GL_TRUE,sizeof(V2F_C4B_T2F),(GLvoid *)offsetof(V2F_C4B_T2F,colors));
这是设置line的vao和vbo时候对颜色的设置。感觉不对啊,为什么是GL_UNSIGNED_BYTE,改成GL_FLOAT就对了。
7:数据传递方式:
方式一:
glBufferData(GL_ARRAY_BUFFER,sizeof(_quadVerts[0]) * _numberQuads * 4,nullptr,GL_DYNAMIC_DRAW);
void *buf = glMapBuffer(GL_ARRAY_BUFFER,GL_WRITE_ONLY);
memcpy(buf,_quadVerts,sizeof(_quadVerts[0])* _numberQuads * 4);
glUnmapBuffer(GL_ARRAY_BUFFER);
这是一段往vbo中填充数据的代码。
方式二:
glBufferData(GL_ARRAY_BUFFER,sizeof(_quadVerts[0]) * _numberQuads * 4,GL_DYNAMIC_DRAW);
GLvoid *glMapBuffer(GLenum target,GLuenum access):返回一个指向缓冲区对象的指针,可以在这个缓冲区对象中写入新值及更新之后,再调用GLboolean glUnMapBuffer(GLenum target)表示已经完成了对数据的更新,取消对这个缓冲区的映射。如果只需要更新所需范围内的数据值,也可调用GLvoid *glMapBuffwerRange(GLenum target,GLintptr offset,GLsizeiptr length,GLbitfield access)
void glBufferData(GLenum target,GLsizeiptr size,const GLvoid * data,GLenum usage );
功能是分配size个存储单位的OpenGL服务器内存,用于存储顶点数据或索引。以前有与当前缓冲区对象相关联的数据将删除。
glBufferData()首先在OpenGL拂去其中分配内存以存储数据。如果成功分配,data!=NULL,size个单位就会从客户机内存复制到这个对象中。如果data=NULL,为数据保留适当的空间,但不会初始化。
详细请看:opengl缓冲对象
具体采用哪种现在也还没个具体方案。cocos2d两种都有,render中采用glMapBuffer,很多其他地方用glBufferData。
第一部分就暂时这样吧!!!
原文链接:https://www.f2er.com/cocos2dx/339469.html