小玉米图文教程No.4 - 进阶级表面应用

这几天做ui和萌T研究了一下表面,大家可能以前使用过表面这种东西,也可能闻所未闻,开篇先说一下表面的定义吧。

所谓表面,指的就是一块画板,在游戏中,你可以将图形直接绘制在游戏窗口上,也可以选择创建一个表面然后将图形画在表面上。

本文是进阶级的表面教程,这里需要你掌握一些表面的初级知识以及混合模式的相关内容,如果对上面我所说的两个东西不是很明白的话,建议先去学习这两个东西的基本,再来看这篇文章。

GM8的表面函数:http://f1.gamemake.org/gm/scr/files/405_07_1surfaces.html
表面的入门教程:http://blog.sina.com.cn/s/blog_7de6faeb01010pkb.html
高级混合模式原理:http://www.gamemaker.cn/forum.php?mod=viewthread&tid=103

一些人在绘制表面的时候,常常会纠结透明度的问题,比如说前一阵子某鬼畜在一块表面上绘制了一个周边透明的光弹。绘制在表面上的图形与直接绘制在屏幕上的图形,颜色偏黑,可以看一下下面的对比图。

0.jpg    (图1)黄色光弹的精灵图片

1.jpg    (图2)直接在游戏中绘制这个光弹后的效果

2.jpg    (图三)先将光弹绘制到表面,然后将该表面绘制到游戏窗口中的效果。

可以看出,图三的光弹边缘就没有图二的那么清晰,而且根本就没有发光的效果,这是为什么呢?

为什么在表面上绘制出的图形与直接绘制出的图形在透明度表现方面有这么大的差距?

首先我们先来说一下GM的正常混合模式,两个参数分别是bm_src_alpha和bm_inv_src_alpha,混合因素为(As, As, As, As)和(1–As, 1–As, 1–As, 1–As),如果你已经了解了GM的混合模式原理,这些参数我也不用再细说了。这里要说一个比较重要的问题,就是将精灵直接绘制在游戏中时,目的地(dest)的透明度始终都为1。别问我为什么透明度始终都为1。。难道说你得游戏窗口在你绘制完了这一个图形之后变得透明了?

而一个新创建的表面。背景是透明的,他的目的地透明度却为0,按照混合模式的计算公式,这里就出现了一些问题。可以举例说明一下。

我们使用正常混合模式,在背景为黑色的区域绘制一个透明度为0.5,颜色为黄色的一个矩形,可以根据正常混合模式的混合因素,来算一下新的颜色是多少。

新的红色分量 = 将要绘制的像素的红色分量 * As + 背景颜色的红色分量 * (1-As)
                 = 1 * 0.5 + 0 * (1 - 0.5) = 0.5
新的绿色分量 = 将要绘制的像素的绿色分量 * As + 背景颜色的绿色分量 * (1-As)
                 = 1 * 0.5 + 0 * (1 - 0.5) = 0.5
新的蓝色分量 = 将要绘制的像素的蓝色分量 * As + 背景颜色的蓝色分量 * (1-As)
                 = 0 * 0.5 + 0 * (1 - 0.5) = 0

这样我们就算出了绘制在游戏窗口上的颜色的RGB为(128,128,0)
3.jpg

看左边的这个颜色是不是像在黑色的背景上绘制了一个半透明的黄色矩形呢?

接下来在用同样的混合模式,在背景为透明的表面上绘制一个透明度为0.5,颜色为黄色的一个矩形,同样根据混合模式计算公式,来算一下新的颜色是多少。
在这之前,先使用draw_clear_alpha(c_black,0)来清理一下表面

新的红色分量 = 将要绘制的像素的红色分量 * As + 背景颜色的红色分量 * (1-As)
                 = 1 * 0.5 + 0 * (1 - 0.5) = 0.5
新的绿色分量 = 将要绘制的像素的绿色分量 * As + 背景颜色的绿色分量 * (1-As)
                 = 1 * 0.5 + 0 * (1 - 0.5) = 0.5
新的蓝色分量 = 将要绘制的像素的蓝色分量 * As + 背景颜色的蓝色分量 * (1-As)
                 = 0 * 0.5 + 0 * (1 - 0.5) = 0
新的透明度分量 = 将要绘制的像素的透明度分量 * As + 背景颜色的透明度分量 * (1-As)
                 = 0.5 * 0.5 + 0 * (1 - 0.5) = 0.25

那么问题就来了,虽然在表面上绘制出的颜色一样也是黄色,但是透明度却变为了0.25,而这时你再把表面绘制在游戏窗口上,就相当于绘制了一个透明度为0.25的黄色矩形,所以跟直接绘制一个半透明的黄色矩形自然就不一样啦~这也就是为什么在表面上绘制出的形状透明度与直接绘制的透明度浅的原因。

如何解决透明度问题

我们已经知道产生这种问题的原因是因为使用了GM默认的正常混合模式绘制,才会让透明度在计算上面与实际绘制的有所差异,如果想要消除这种差异,只需要换一种混合模式就好了,目的就是,绘制一个精灵,把颜色和透明度都完全准确的画在表面上。我们可以使用bm_one,bm_zero的混合模式来实现这种效果,不妨来算一下,在一个透明的表面上绘制一个透明度为0.5的黄色矩形,得到的颜色和透明度是什么,首先要了解bm_one与bm_zero的混合因素,bm_one是(1, 1, 1, 1),bm_zero是(0, 0, 0, 0)。

新的红色分量 = 将要绘制的像素的红色分量 * 1 + 背景颜色的红色分量 * 0
                 = 1 * 1 + 1 * 0 = 1
新的绿色分量 = 将要绘制的像素的绿色分量 * 1 + 背景颜色的绿色分量 * 0
                 = 1 * 1 + 1 * 0 = 1
新的蓝色分量 = 将要绘制的像素的蓝色分量 * 1 + 背景颜色的蓝色分量 * 0
                 = 0 * 1 + 1 * 0 = 0
新的透明度分量 = 将要绘制的像素的透明度分量 * 1 + 背景颜色的透明度分量 * 0
                 = 0.5 * 1 + 0 * 1 = 0.5

在使用了新的混合模式之后,得出的是透明度为0.5的黄色,与我们绘制在表面上的颜色是完全相同的,所以以后在游戏窗口上绘制这个表面时也将绘制一个透明度为0.5的黄色矩形,就不会有绘制略暗的问题了!

叠加绘制

这还不算完,有的时候,你可能会在表面上绘制一个UI,先绘制一个窗口的精灵作为底层,然后要在上面绘制各种组件,这个时候,如果继续使用bm_one,bm_zero的混合模式,你就会发现这种效果。

4.jpg (图五) 在一块表面上先绘制一个透明度为0.75的黄色矩形,再绘制一个透明度为0.2的蓝色矩形,然后把这个表面绘制在游戏中。

没错,如图五所示,如果你在绘制了一次之后又叠加绘制一次,那么重合的部分就会被新的透明度所代替,为了达到所期待的效果,我们可以使用两次绘制,并用叠加的混合模式来解决问题。代码如下所示:

sur = surface_create(200,200)
surface_set_target(sur)
var bottomAlpha,topAlpha,tempAlpha;
bottomAlpha = 0.75  //设置黄色矩形的透明度
topAlpha = 0.2      //设置蓝色矩形的透明度
draw_clear_alpha(c_black,0)         //清除表面
// 进行第一次绘制,将所需要绘制的图形全都混合为黑色
draw_set_blend_mode_ext(bm_one,bm_zero)
draw_set_alpha(bottomAlpha)
draw_rectangle_color(0,0,200,200,c_black,c_black,c_black,c_black,false)
draw_set_blend_mode(bm_normal)
draw_set_alpha(topAlpha)
draw_rectangle_color(30,30,170,170,c_black,c_black,c_black,c_black,false)
var tempSprite;
tempSprite = sprite_create_from_surface(sur,0,0,200,200,false,false,0,0)    //将当前绘制的表面存为临时的精灵

// 进行第二次绘制,这次要正常的绘制所有图形
draw_clear_alpha(c_white,0)         //清除表面,进行第二次绘制
draw_set_blend_mode_ext(bm_one,bm_zero)
draw_set_alpha(bottomAlpha)
draw_rectangle_color(0,0,200,200,c_yellow,c_yellow,c_yellow,c_yellow,false)
draw_set_blend_mode(bm_normal)
draw_set_alpha(topAlpha)
draw_rectangle_color(30,30,170,170,c_blue,c_blue,c_blue,c_blue,false)

// 进行混合,将临时精灵叠加绘制在表面上
draw_set_blend_mode(bm_add)
if(bottomAlpha != 1) tempAlpha = max(0,bottomAlpha-0.8) else tempAlpha = bottomAlpha    //调整叠加透明度,使该透明度小于0.2
draw_sprite_ext(tempSprite,0,0,0,1,1,0,c_white,tempAlpha)
draw_set_blend_mode(bm_normal)

sprite_delete(tempSprite)       //删除临时精灵
surface_reset_target()

绘制效果如下图所示:

5.jpg

这样就不会出现叠加绘制透明度被替换的问题啦~

目前有 7 条评论

评论

  1. 断水: 2016-03-29 下午 11:29:52 回复Ta
    #1

    很实用的表面教程,解决了之前的表面BUG。

    Windows 10 x64Windows 10 x64 Google Chrome 44.0.2403.157Google Chrome 44.0.2403.157

    • Mage松仁玉米: 2016-03-29 下午 11:29:52 回复Ta

      @断水 其实并不是什么表面的BUG啦,本来表面的运行原理就是这样的。

      Windows 8 x64Windows 8 x64 Google Chrome 44.0.2403.157Google Chrome 44.0.2403.157

  2. 评论者: 2016-03-29 下午 11:29:52 回复Ta
    #2

    不错。

    Windows XPWindows XP Sogou ExplorerSogou Explorer

  3. 游客: 2016-03-29 下午 11:29:52 回复Ta
    #3

    能把实例文件放出来吗?

    Windows 8.1 x64Windows 8.1 x64 Google Chrome 46.0.2490.86Google Chrome 46.0.2490.86

    • Mage松仁玉米: 2016-03-29 下午 11:29:52 回复Ta

      @游客 实例并没有啥代码,跟文章中给出的是一样的,也是核心,其他的没什么卵用

      Windows 10 x64Windows 10 x64 QQBrowser 9.2.5130.400QQBrowser 9.2.5130.400

  4. shimatani: 2018-05-27 下午 10:00:12 回复Ta
    #4

    这文章帮大忙了,感谢大佬

    Windows 7 x64Windows 7 x64 Google Chrome 55.0.2883.87Google Chrome 55.0.2883.87

  5. your射友: 2018-06-13 下午 04:36:08 回复Ta
    #5

    我爱你

    Windows 7 x64Windows 7 x64 Google Chrome 67.0.3396.79Google Chrome 67.0.3396.79

分享:

支付宝

微信