在Cocos中使用shader实现月相变化

Published: 23 Dec 2014 Category: 游戏研发

分析

要实现月相变化,有三点需要注意:

  • 月亮实体。也就是说用一个图片表示月亮
  • 月亮的光晕。月亮周边的光晕可以通过图片表现,但是如果是月相,被遮挡住的部分也需要有光晕。
  • 被遮挡部分

最终月亮效果如下:

遮挡的实现

定义一个遮挡的圆形(遮罩),这个圆形和月亮(包括光晕)的圆形大小一样,而要使月相变化只需要移动这个遮罩的位置。 如月相为0(月亮完全被盖住),只需要将遮罩完全盖住月亮。 月相为50,将遮罩的圆心定义在月亮圆形的边上。 月相为100,将遮罩拿开

光晕的实现

光晕也分为三部分处理。

1.月亮圆形边上的光晕。

2.月亮豁口的光晕(遮罩的光晕)。

3.月亮豁口和圆形边交界的光晕。

光晕使用如下方法来模拟:距离月亮实体越近,透明度越低。

片段着色器

//功能:遮罩区域不显示,其余部分显示。
#ifdef GL_ES
precision lowp float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;

uniform vec2 u_maskCenter;  // 遮罩中心
uniform float u_maskRadius;  // 遮罩半径
uniform float u_moonRadius;  // 月亮半径

void main()
{
    --该点到遮罩圆心的距离
    float x2_mask = (v_texCoord.x - u_maskCenter.x) * (v_texCoord.x - u_maskCenter.x);
    float y2_mask = (v_texCoord.y - u_maskCenter.y) * (v_texCoord.y - u_maskCenter.y);
    --该点到月亮圆心的距离
    float x2_center = (v_texCoord.x - 0.5) * (v_texCoord.x - 0.5);
    float y2_center = (v_texCoord.y - 0.5) * (v_texCoord.y - 0.5);
    --遮罩和月亮实体的半径,注意,遮罩的半径和月亮光晕的半径是相等的。
    float r2_moon = u_moonRadius * u_moonRadius;
    float r2_mask = u_maskRadius * u_maskRadius;

    if (x2_mask + y2_mask >= r2_mask && x2_center + y2_center < r2_moon)//(月亮实体处理)像素点在月亮实体内且在遮罩外
    {
        gl_FragColor =  texture2D(CC_Texture0, v_texCoord);
    }
    else if(x2_mask + y2_mask >= r2_moon && x2_center + y2_center < r2_mask)//(光晕)点在月亮的光晕中且在遮罩的光晕中
    {
        float ratio;
        float r1 = 1- (sqrt(x2_center + y2_center) - u_moonRadius) / (u_maskRadius - u_moonRadius);//月亮光晕
        float r2 = 1- (u_maskRadius - sqrt(x2_mask + y2_mask)) / (u_maskRadius - u_moonRadius);//遮罩光晕
        if (x2_mask + y2_mask >= r2_mask && x2_center + y2_center >= r2_moon)
            ratio = r1;
        else if (x2_center + y2_center < r2_moon) 
            ratio = r2;
        else
            ratio = r1 * r2; //月角,这个地方比较难理解

        vec4 color = texture2D(CC_Texture0, vec2(0.5, 0.5));//颜色定义为月亮图片中间的
        color.a = pow(ratio, 3.0);//透明度变化更快
        gl_FragColor = color;
    }
    else//其他地方为背景
    {
        gl_FragColor = vec4(0.0);
    }
}

Shader的使用

-- 可控制月相变换的月亮。
local general_moon = class('general_moon', function()
    return cc.Node:create()
end)

local vsh = 'default.vsh'
local fsh_main = 'general_moon_main.fsh'

local tex_main = 'tmp/moon.png'

-- 月相范围
local max_ph = 100
local min_ph = 0
local def_ph = 50

 -- 阴影半径
local mask_radius = 125

-- 月亮半径
local moon_radius = 100


-- 创建函数
function general_moon.create(phase)
    local node = general_moon.new()
    node:init(phase)
    return node
end

-- 初始化
function general_moon:init(phase)
    self:initSprite()-- 初始化sprite
    self.phase = (phase or def_ph)-- 设置月相
    self:initShader()-- 初始化shader
    cc.scheduleUpdate(self, self.update)
end

-- 渐变月相
function general_moon:changePhase(phase)
    self.phase = phase
end

-- 更新
function general_moon:update(dt)
        self:useShader() 
end

-- 初始化sprite
function general_moon:initSprite()
    local moonMain = cc.Sprite:create(tex_main)
    moonMain:setBlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
    self:addChild(moonMain)
    self._moonMain = moonMain
    self._maskRadius = mask_radius / moonMain:getContentSize().width
    self._moonRadius = moon_radius / moonMain:getContentSize().width
end


-- 初始化shader
function general_moon:initShader()
    local shader_main = cc.GLProgram:createWithFilenames(vsh, fsh_main)
    shader_main:bindAttribLocation(cc.ATTRIBUTE_NAME_POSITION, cc.VERTEX_ATTRIB_POSITION)
    shader_main:bindAttribLocation(cc.ATTRIBUTE_NAME_COLOR, cc.VERTEX_ATTRIB_COLOR)
    shader_main:bindAttribLocation(cc.ATTRIBUTE_NAME_TEX_COORD, cc.VERTEX_ATTRIB_TEX_COORDS)
    shader_main:link()
    self._moonMain:setGLProgram(shader_main)
end

-- 使用shader
function general_moon:useShader()
    self._moonMain:getGLProgram():use()
    local maskCenterMain = 0.5
        - math.sqrt(2) * self._maskRadius * (self.phase  - min_ph) / (max_ph - min_ph)
    local stateMain = self._moonMain:getGLProgramState()
    stateMain:setUniformVec2('u_maskCenter', cc.p(maskCenterMain, maskCenterMain))
    stateMain:setUniformFloat('u_moonRadius', self._moonRadius)
    stateMain:setUniformFloat('u_maskRadius', self._maskRadius)

end

return general_moon

解释:

1.根据月相比例确定遮罩的圆心,纹理坐标从左上角开始,因为这个坐标只是x和y分量的坐标,所以要乘以sqrt(2) local maskCenterMain = 0.5 - math.sqrt(2) * self.maskRadius * (self.phase - minph) / (maxph - minph)

2.传给shader遮罩的圆心、遮罩半径和月亮实体半径。注意,这里遮罩半径==月亮光晕半径

3.moonMain:setBlendFunc(gl.SRCALPHA, gl.ONEMINUSSRCALPHA)这个是默认的,但是这里不知道为什么没有这句话就不显示。