Game Studio
Liên kế mạng xã hội

Game Studio


Hương dẫn sử dụng mặt nạ trong Cocos 2D-X

Giới thiệu

Hiệu ứng là một trong những phần không thể thiếu được của các game hiện nay. Trong Sins – The Salvation, chúng tôi đã tạo rất nhiều hiệu ứng đẹp mặt. Một trong những hiệu ứng đó là hiệu ứng vẽ sprite sử dụng mặt nạ. Hiệu ứng này chúng tôi từng dùng để tạo hiệu ứng cho vòng ma thuật nhưng vì một số lý do nhạy cảm, chúng đã không còn tồn tại trong game hiện tại. Trong bài viết này, tôi sẽ hướng dẫn các bạn cách tạo hiệu ứng này.

Tiền đề bài viết

Bài viết thuộc loạt bài viết chia sẻ các công nghệ sử dụng trong Sins – The Salvation mà nhiều học viên của STDIO và một số thành viên khác đã yêu cầu với chúng tôi.

Đối tượng hướng đến

Tất cả những lập trình viên sử dụng game engine dựa trên nền tảng OpenGLES 2.0 cụ thể là lập trình với GLSL, mong muốn tạo nhiều hiệu ứng đẹp cho game.

Hiện thực

Sau khi kết thúc bài viết này, các bạn có thể tạo được hiệu ứng có dạng như sau:

Để hoàn thành bài viết này, tôi chỉ cần sử dụng 2 hình ảnh: hình ảnh thứ nhất là hình ảnh HelloWorld.png được đính kèm trong dự án mẫu của Cocos2d-x, hình ảnh thứ 2 là hình ảnh Mask.png được đính kèm bên dưới.

Để vẽ được sprite sử dụng mặt nạ, chúng ta cần phải viết shader hỗ trợ multi-texture. Trong bài viết này, tôi sử dụng 2 texture: texture thứ nhất để làm nền, texture thứ hai để làm mặt nạ che texture nền.

Vertex shader tôi giữ giống như shader của Cocos2d-x:

  1. attribute vec4 a_position;
  2. attribute vec2 a_texCoord;
  3. attribute vec4 a_color;
  4.  
  5. #ifdef GL_ES
  6. varying lowp vec4 v_fragmentColor;
  7. varying mediump vec2 v_texCoord;
  8. #else
  9. varying vec4 v_fragmentColor;
  10. varying vec2 v_texCoord;
  11. #endif
  12.  
  13. void main()
  14. {
  15. gl_Position = CC_MVPMatrix * a_position;
  16. v_fragmentColor = a_color;
  17. v_texCoord = a_texCoord;
  18. }

Fragment shader của tôi như sau:

  1. #ifdef GL_ES
  2. precision lowp float;
  3. #endif
  4.  
  5. varying vec4 v_fragmentColor;
  6. varying vec2 v_texCoord;
  7. uniform sampler2D u_texture;
  8. uniform sampler2D u_mask;
  9.  
  10. void main()
  11. {
  12. vec4 texColor = texture2D(u_texture, v_texCoord);
  13. vec4 maskedColor = texture2D(u_mask, v_texCoord);
  14. vec4 blendingColor = vec4(texColor.r, texColor.g,
  15. texColor.b, maskedColor.a * texColor.a);
  16.  
  17. gl_FragColor = v_fragmentColor * blendingColor;
  18. }

Như các bạn thấy ở trên, 3 kênh màu đỏ, xanh lá cây, xanh dương của màu cuối cùng được tạo ra tôi giữ giống hệt 3 kênh trong texture nền. Để tạo được hiệu ứng, kênh alpha là điểm cần lưu ý. Theo phương pháp hiện thực của tôi, tôi đã nhân giá trị của 2 kênh alpha trong texture nền và texture mặt nạ để tính toán giá trị kênh alpha trong màu cuối cùng. Để hiểu được lý do tại sao tôi làm như vậy, các bạn có thể xem lại ảnh mặt nạ tôi đã nêu ở trên.

Sau khi đã viết shader cho sprite này, tôi bắt phần hiện thực component MaskedSprite. Đây là phần khởi tạo component của tôi:

  1. bool MaskedSprite::init(const char* textureName, const char* maskedTextureName)
  2. {
  3. if(!CCSprite::initWithFile(textureName))
  4. {
  5. return false;
  6. }
  7.  
  8. _maskedTexture = CCTextureCache::sharedTextureCache()->addImage(
  9.                                                         maskedTextureName);
  10. _maskedTexture->retain();
  11.  
  12. CCGLProgram* pProgram = new CCGLProgram();
  13. pProgram->initWithVertexShaderFilename("MaskedSprite.vert",
  14.                                                            "MaskedSprite.frag");
  15.  
  16. setShaderProgram(pProgram);
  17.  
  18. pProgram->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);
  19. pProgram->addAttribute(kCCAttributeNameColor, kCCVertexAttrib_Color);
  20. pProgram->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);
  21.  
  22. pProgram->link();
  23. pProgram->updateUniforms();
  24.  
  25. _locTexture = glGetUniformLocation(pProgram->getProgram(), "u_texture");
  26. _locMask = glGetUniformLocation(pProgram->getProgram(), "u_mask");
  27.  
  28. pProgram->release();
  29.  
  30. return true;
  31. }

Ở phần khởi tạo này, tôi đã khởi tạo shader tôi đã viết ở trên cho component và lấy giá trị 2 vị trí của 2 texture uniform (texture nền và texture làm mặt nạ). 2 vị trí này tôi sẽ sử dụng cho bước render.

Đây là phần render trong component của tôi:

  1. void MaskedSprite::draw()
  2. {
  3. CC_NODE_DRAW_SETUP();
  4.  
  5. ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst );
  6.  
  7. glActiveTexture(GL_TEXTURE0);
  8. glBindTexture( GL_TEXTURE_2D, m_pobTexture->getName() );
  9. glUniform1i(_locTexture, 0);
  10.  
  11. glActiveTexture(GL_TEXTURE1);
  12. glBindTexture( GL_TEXTURE_2D, _maskedTexture->getName() );
  13. glUniform1i(_locMask, 1);
  14.  
  15.  
  16. ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );
  17.  
  18. long offset = (long)&m_sQuad;
  19.  
  20. #define kQuadSize sizeof(m_sQuad.bl)
  21.  
  22. // vertex
  23. int diff = offsetof( ccV3F_C4B_T2F, vertices);
  24. glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE,
  25.                                     kQuadSize, (void*) (offset + diff));
  26.  
  27. // texCoods
  28. diff = offsetof( ccV3F_C4B_T2F, texCoords);
  29. glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE,
  30.                                     kQuadSize, (void*)(offset + diff));
  31.  
  32. // color
  33. diff = offsetof( ccV3F_C4B_T2F, colors);
  34. glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE,
  35.                                     kQuadSize, (void*)(offset + diff));
  36.  
  37. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
  38.  
  39. CHECK_GL_ERROR_DEBUG();
  40. }
  • Dòng 3: thiết đặt program cho trạng thái hiện tại là program đã được khởi tạo ở trên.
  • Dòng 7-13: bind 2 texture nền và texture mặt nạ.
  • Dòng 16: enable các thuộc tính được dùng của các đỉnh (trong trường hợp này là thuộc tính vị trí, màu sắc, toạ độ của texture).
  • Dòng 18-35: vẽ component.

Mã nguồn đầy đủ của component các bạn có thể xem tại đây:

Cách dùng Component

  1. CCSize winSize = CCDirector::sharedDirector()->getWinSize();
  2.  
  3. MaskedSprite* pSprite = MaskedSprite::create("HelloWorld.png",
  4.                                                 "Mask.png");
  5. pSprite->setPosition(ccp(winSize.width/2.0f, winSize.height/2.0f));
  6. this->addChild(pSprite);

Tổng kết

Đây là một trong những hiệu ứng đơn giản chúng tôi đã áp dụng vào Sins – The Salvation. Hy vọng với bài viết này, các bạn có thể tạo ra được những game phong phú hơn cho riêng mình.

Theo: Stdio


Đăng sự kiện cho developer