OpenGL(LibGDX)激光束
来源:互联网 发布:眼睛变大知乎 编辑:程序博客网 时间:2024/06/07 04:55
几周前,我开始考虑如何在我们的游戏原型防御中集成一些不错的激光fx。
在互联网上搜索一些关于这个问题的指南后,我在开发者的博客上发现了一个单独的帖子; Cliffski的。
不过,令人遗憾的是,他只是就这个问题提出了一个大纲,没有任何真正的实施或细节。
在实施他的想法之后,我开始试图加强和扩大它; 这就是我这样做的。
Pro Tip#1:无论何时使用OpenGL,设计效果; 学习使用所需IDE的热插拔功能。例如; 代码被整合到正在运行的程序中,每当你保存它; 而无需重新启动整个程序。这样可以轻松尝试不同的混合技术等。
激光FX 101
要在游戏中创建激光fx,您基本上需要3个精灵:
- 开始帽背景; 来自喷嘴的初始爆炸
- 中间部分: 激光器的重复部分
- 端盖: 您的激光的最后部分。(例如淡出)
- 干扰叠加:可重复的重叠动画,显示“干涉”,将给出方向和现实主义。
为了我们的目的,我们将通过另外3个精灵来扩展它,并调整其他4。
本质上我将开始,结束和中间部分划分为2个精灵; 背景和叠加。
背景表示激光的特征“发光”,而叠加表示“白色,明亮”的光束。
我做到了这一点,所以我可以通过编程方式设置这个sprite的背景颜色(发光),使我能够减少纹理所需的量。(并使我能够在游戏过程中改变颜色以获得更多的古怪效果)
所以现在我们有7个精灵:
- 开始上限
- 背景: 来自喷嘴的爆发和辉光
- 覆盖层:来自喷嘴的爆破和梁
- 封顶
- 背景: 结束部分发光; 淡出了光辉
- 叠加: 结束部分梁; 从光束中淡出
- 中段
- 背景: 光束的光芒
- 叠加: 梁本身
中段
(作者笔记:px测量是64x64的图像)
由于中间部分将定义激光器的实际外观,我们将从此开始。打开您最喜欢的作品程序,并在图像中间创建一条白色线条,从上到下,宽度为2 像素宽的单独图层,我们称之为“中间叠加层”。现在添加一个外部发光效果到这个层,白色,它作为一个非线性淡入淡出,使它以5 像素结束。将“覆盖”复制到新层中,称之为“中间背景”。现在调整外部发光效果,使其淡出更大(〜18像素),并具有透明度约50%。现在将叠加层和背景图层保存为单独的精灵。(例如:laser-mid-o.png&laser-mid-b.png)不要忘记将整个图像保存到单独的文件,因为我们将重新使用它的开始和结束部分。
专业提示#2:您可以添加具有不同外部辉光效果的重叠/背景图层,以创建不同类型的激光。请记住,背景图层应该表示“发光”,而覆盖层应该代表光束本身。
开始部分
将“mid-overlay”和“mid-background”的内容复制到名为“start-overlay”和“start-background”的两个新图层中。
在“中间重叠”图层上,禁用外部辉光,并在图像的中心绘制一个白色圆圈(或者您认为喷嘴的任何位置),稍大于“中间重叠”线。拆下喷嘴后面的一部分线,所以你最终会得到类似于一个球的东西,一个从它出来的棍子。
然后重新启用此层的辉光。
现在重复这个过程为“中后卫” ; 所以发光结合了圆圈。
将图层保存为单独的精灵。
(例如:laser-start-o.png&laser-start-b.png)
结束部分
将“中间叠加”和“中间背景”的内容复制到称为“终端覆盖”和“末端背景”的两个新图层中。
创建一个临时图层,并从顶部到底部填充从白色到透明的渐变。选择结果填充,并使用选择删除“end- *”层中的内容。
从本质上讲,您应该从底部到顶部最终都会出现从不透明到透明的褪色效果。
再次,将图层保存为单独的精灵。
(例如:laser-end-o.png&laser-end-b.png)
Pro Tip#3:这显然可以用不同的方式完成,尝试和实验!
叠加动画
为此,我简单地使用随机噪声,模糊的我的光束的长度,宽度大约一半的光辉。然后我通过向上移动纹理来动画化,确保它在底部和顶部都无缝重复。
(例如:将顶部向外移动的部分复制到底部)
现在到了实际的代码!
渲染
基本上我们要渲染:
- 设置混合添加 ; {GL10.GL_SRC_ALPHA,GL10.GL_ONE}
- 调整光束的α和发光颜色(衰变效果)
- 对于{x =开始,中间,末尾精灵}
- 将颜色设置为发光颜色
- 绘制x- background精灵
- 将颜色设置为光束颜色
- 绘制x -overlay精灵
- 将颜色设置为默认值
- 从启动子程序的中心绘制重叠动画到最终精灵的中心。
增加的是:
- 向alpha添加随机性
- 添加褪色激光; 例如:中点网格(和端帽网格)的末端可以具有targetAlpha。(见底部代码)
混纺
首先,激光look'n'feel的主要部分是一个特定的混合技术呼叫加成。
openGL命令是:
OpenGL的
openGlContext.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
LibGDX
spriteBatch.end();
// actual drawing is done on end(); if we do not end, we contaminate previous rendering.
spriteBatch.setBlendFunction(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
spriteBatch.start();
区别?好看:
颜色
现在,为您的光束和光线选择一种颜色,并确保用于绘制背景精灵的网格 具有发光颜色,因为它是顶点颜色; 并且覆盖精灵具有光束颜色。
要获得传统的激光效果,请使用红色作为发光,白色用于光束。
OpenGL的
vertices[idx++] = x;
vertices[idx++] = y;
vertices[idx++] = color;
vertices[idx++] = u;
vertices[idx++] = v;
LibGDX
spriteBatch.setColor(color);
为了使激光看起来更逼真,我们将添加一个简单的衰减效果,将发光和光束的alpha设置为:
color.alpha =
1
- (lifeTimeOfLaser / totalTimeOfLaser) ^
2
通过使这个指数而不是线性,我们得到一个很好的“余辉”效应。
纹理重复
对于几乎所有的纹理,我们不需要重复{开始,结束}或只是拉伸它们{middle}。
主要的问题是Overlay动画。我们需要垂直重复这个纹理,所以我们不会得到任何伸展的文物。
通常这可以通过调整网格的纹理坐标中的V来轻松解决。
vertices[idx++] = v * scale;
// Where factor by which we are stretching
但是,仅当叠加动画处于单个纹理中时,此操作才会起作用。
如果你是(我真的希望如此;如果不使这个优先级数字1)使用纹理地图集,那么U,V坐标指向纹理内的一个区域,我们不能简单地重复。
解决这个问题的方法是创建一个由多个指向该区域的四边形组成的单个网格。
创建这样一个网格的方法:
(作者注:此方法还包括设置所述网格的颜色,以及a的扩展名可以给出α梯度。例如:网格从colorT.alpha内插到colorT.alpha * alpha的比例。)
[基于LibGDX SpriteBatch,稍作调整]
/**
* Creates a mesh which will draw a repeated texture. To be used whenever we are dealing with a region of a TextureAtlas
* @param vertices For re-use, the vertices to use for the mesh. If insufficient are provided, a new array will be constructed
* @param region The AtlasRegion to use
* @param scale The factor by which we want to repeat our texture
* @param x
* @param y
* @param originX
* @param originY
* @param width
* @param height
* @param scaleX Scale by which we want to expand the mesh on X
* @param scaleY Scale by which we want to expand the mesh on Y
* @param rotation Degrees of rotation for mesh
* @param colorBase The initial base color
* @param alpha The alpha by which to mult the colorBase by; resulting in the end interpolation target.
* @return
*/
private
static
final
float
[] constructEndingMesh(
float
[] vertices, AtlasRegion region,
int
scale,
float
x,
float
y,
float
originX,
float
originY,
float
width,
float
height,
float
scaleX,
float
scaleY,
float
rotation,
Color colorT,
float
alpha)
{
if
(scale *
20
> vertices.length)
vertices =
new
float
[
20
*scale];
float
color = colorT.toFloatBits();
float
colorE;
int
idx =
0
;
// bottom left and top right corner points relative to origin
final
float
worldOriginX = x + originX;
final
float
worldOriginY = y + originY;
float
fx = -originX;
float
fy = -originY;
float
fx2 = width - originX;
float
fy2 = height - originY;
// scale
if
(scaleX !=
1
|| scaleY !=
1
) {
fx *= scaleX;
fy *= scaleY;
fx2 *= scaleX;
fy2 *= scaleY;
}
// construct corner points, start from top left and go counter clockwise
final
float
p1x = fx;
final
float
p1y = fy;
final
float
p2x = fx;
final
float
p2y = fy2;
final
float
p3x = fx2;
final
float
p3y = fy2;
final
float
p4x = fx2;
final
float
p4y = fy;
float
Fx1;
float
Fy1;
float
Fx2;
float
Fy2;
float
Fx3;
float
Fy3;
float
Fx4;
float
Fy4;
// rotate
if
(rotation !=
0
) {
final
float
cos = MathUtils.cosDeg(rotation);
final
float
sin = MathUtils.sinDeg(rotation);
Fx1 = cos * p1x - sin * p1y;
Fy1 = sin * p1x + cos * p1y;
Fx2 = cos * p2x - sin * p2y;
Fy2 = sin * p2x + cos * p2y;
Fx3 = cos * p3x - sin * p3y;
Fy3 = sin * p3x + cos * p3y;
Fx4 = Fx1 + (Fx3 - Fx2);
Fy4 = Fy3 - (Fy2 - Fy1);
}
else
{
Fx1 = p1x;
Fy1 = p1y;
Fx2 = p2x;
Fy2 = p2y;
Fx3 = p3x;
Fy3 = p3y;
Fx4 = p4x;
Fy4 = p4y;
}
float
x1 = Fx1 + worldOriginX;
float
y1 = Fy1 + worldOriginY;
float
x2 = Fx2 + worldOriginX;
float
y2 = Fy2 + worldOriginY;
float
scaleX2 = ((Fx2-Fx1) / scale);
float
scaleY2 = ((Fy2-Fy1) / scale);
float
scaleX3 = ((Fx3-Fx4) / scale);
float
scaleY3 = ((Fy3-Fy4) / scale);
float
scaleAlpha = (colorT.a - (colorT.a*alpha)) / scale;
float
x3 = x1;
float
y3 = y1;
float
x4 = x2;
float
y4 = y2;
final
float
u = region.getU();
final
float
v = region.getV();
final
float
u2 = region.getU2();
final
float
v2 = region.getV2();
for
(
int
i =
1
; i <= scale; i++) {
x1 = Fx1 + scaleX2*(i-
1
) + worldOriginX;
y1 = Fy1 + scaleY2*(i-
1
) + worldOriginY;
x2 = Fx1 + scaleX2*i + worldOriginX;
y2 = Fy1 + scaleY2*i + worldOriginY;
x3 = Fx4 + scaleX3*i + worldOriginX;
y3 = Fy4 + scaleY3*i + worldOriginY;
x4 = Fx4 + scaleX3*(i-
1
) + worldOriginX;
y4 = Fy4 + scaleY3*(i-
1
) + worldOriginY;
color = colorT.toFloatBits();
colorT.a-=scaleAlpha;
colorE = colorT.toFloatBits();
vertices[idx++] = x1;
vertices[idx++] = y1;
vertices[idx++] = color;
vertices[idx++] = u;
vertices[idx++] = v;
vertices[idx++] = x2;
vertices[idx++] = y2;
vertices[idx++] = colorE;
vertices[idx++] = u;
vertices[idx++] = v2;
vertices[idx++] = x3;
vertices[idx++] = y3;
vertices[idx++] = colorE;
vertices[idx++] = u2;
vertices[idx++] = v2;
vertices[idx++] = x4;
vertices[idx++] = y4;
vertices[idx++] = color;
vertices[idx++] = u2;
vertices[idx++] = v;
}
return
vertices;
}
然后将这个网格和纹理一起提供给OpenGL或LibGDX。
就是这样。有乐趣的编码!
参考文献:
- Cliffski的博客:绘制激光束的复杂性
给我一个基本的概述,如何做激光,而不深入到任何细节 - 无尽的空间战斗
灵感! - Ikuraga
灵感!
- OpenGL(LibGDX)激光束
- LIBGDX版NEHE OPENGL- 3. Adding Color
- LIBGDX版NEHE OPENGL- 4. Rotation
- LIBGDX版NEHE OPENGL- 6. Texture Mapping
- libgdx java语言的opengl手机引擎
- libGDX视频教程(一) -- 初识libGDX
- libGDX
- LibGDX
- LIBGDX版NEHE OPENGL- 2. Your First Polygon
- LIBGDX版NEHE OPENGL- 5. 3D Shapes
- LIBGDX版NEHE OPENGL- 7. Texture Filters, Lighting & Keyboard Control
- MANAGED & UNMANAGED (DYNAMIC )TEXTURES IN LIBGDX (OPENGL CONTEXT LOSS)
- Libgdx Developer's Guide(Libgdx开发者手册)-5(生命周期)
- libGDX开发教程(一)--Libgdx基础入门
- libGDX开发教程(一)--Libgdx基础入门
- LibGdx学习笔记(一)
- [Unity3d] 如何在Unity3d中创建激光束
- 如何在Unity3d中创建激光束
- 在CentOS上搭建PHP服务器环境
- How to get the SHA-1 fingerprint certificate in Android Studio for debug mode?
- java.lang.ClassNotFoundException: com.sun.faces.config.ConfigureListener
- Solve the Equation
- Android百度地图实例详解之仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,导航等)
- OpenGL(LibGDX)激光束
- 方正何处去?
- 想要
- Script到Code Blocks、Code Behind到MVC、MVP、MVVM
- Linux下spi驱动开发
- 基于S3C2440的嵌入式Linux驱动——SPI子系统解读(一)
- S3C2440 Linux驱动移植——SPI
- SPI驱动的移植(Linux2.6.24)
- Linux中SPI子系统图解和mini2440下的SPI驱动移植