OpenGL(十二) 光源类型 平行光 、 点光源 、 聚光灯 的实现

2017年5月17日 没有评论

常规的 光源类型 有三种。最简单的自然是平行光。稍复杂些的为点光源,最复杂的为聚光灯。总体说来,在可编程管线中原理是一样的。在OpenGL代码中传入必要的参数,在shader中进行相关的计算绘制出效果。本文分别介绍三种效果的shader实现。

平行光

平行光我们已经写的轻车熟路了。它只需要一个光源方向就够了。通常我们会定义一个光源点,它与坐标轴原点的连线就是光线方向。

//GL Code
float lightPos[] = { 0.0f,1.5f,0.0f,0.0f };
float diffuseLightColor[] = { 1.0f,1.0f,1.0f,1.0f };

glUniformMatrix4fv(gpuProgram.GetLocation("M"), 1,GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(gpuProgram.GetLocation("V"), 1, GL_FALSE, identity);
glUniformMatrix4fv(gpuProgram.GetLocation("P"), 1, GL_FALSE, glm::value_ptr(projectionMatrix));
glUniformMatrix4fv(gpuProgram.GetLocation("NM"), 1, GL_FALSE, glm::value_ptr(normalMatrix));
glUniform4fv(gpuProgram.GetLocation("U_LightPos"), 1, lightPos);
glUniform4fv(gpuProgram.GetLocation("U_DiffuseLightColor"), 1, diffuseLightColor);

//fs
void main(){
    //...
    vec3 n = normalize(V_Normal);
    vec3 L = U_LightPos.xyz;
    float diffuseIntensity=max(0.0,dot(L,n));
    vec4 diffuseColor=U_DiffuseLightColor*diffuseIntensity;
    //...
    gl_FragColor=ambientColor+diffuseColor;
}

获取的光线方向直接与法线点乘,即为光照强度。

点光源

在平行光的基础上,根据模型与光源之间的距离进行衰减。衰减为与距离相关的二次函数。因此需要加入三个因数,分别为expFactorlinearFactorconstantFactor

//GL Code
float constantFactor = 1.0;
float linearFactor = 0.2;
float expFactor=0.0;

glUniform1f(gpuProgram.GetLocation("U_ConstantFactorn"), 1, constantFactor);
glUniform1f(gpuProgram.GetLocation("U_LinearFactor"), 1, linearFactor);
glUniform1f(gpuProgram.GetLocation("U_ExpFactor"), 1, expFactor);


//fs
float attenuation =  1.0;

void main(){
    //...
    vec3 n = normalize(V_Normal);
    vec3 L = U_LightPos.xyz - V_WorldPos;
    float distance = length(L);
    attenuation = 1.0/(U_ExpFactor*distance*distance + U_LinearFactor*distance + U_ConstantFactor);
    float diffuseIntensity = max(0.0,dot(L,n));
    vec4 diffuseColor = U_DiffuseLightColor*attenuation*diffuseIntensity;
    //...
    gl_FragColor=ambientColor+diffuseColor;
}

聚光灯

在点光源的基础上,添加一个带角度的锥形区域的检测。只有在区域内的部分才有照亮处理。因此需要传入shader两个参数,分别为光照方向和范围角度。

//GL Code
//...
float spotLightDirection[] = { 0.0f,-1.0f,0.0f,128.0f };
float spotLightCutoff = 15.0f;

glUniform1f(gpuProgram.GetLocation("U_Cutoff"), spotLightCutoff);
glUniform4fv(gpuProgram.GetLocation("U_LightDirection"), 1, spotLightDirection);

//...

//fs
float attenuation =  1.0;

void main(){
    //...

    float radianCutoff = U_Cutoff*3.14/180;
    float cosThta = cos(radianCutoff);
    vec3 spotLightDirection = normalize(U_LightDirection.xyz);

    vec3 n = normalize(V_Normal);
    vec3 L = U_LightPos.xyz - V_WorldPos;
    float distance = length(L);
    attenuation = 1.0/(U_ExpFactor*distance*distance + U_LinearFactor*distance + U_ConstantFactor);
    
    float currentCosThta=max(0.0,dot(-L,spotLightDirection));
    float diffuseIntensity = 0.0;
    if(currentCosThta > cosThta)
    {
        if(dot(L,n) >0.0)
        {
            diffuseIntensity = pow(currentCosThta,U_LightDirection.w); 
        }
    }
    vec4 diffuseColor = U_DiffuseLightColor*attenuation*diffuseIntensity;

    //...
    gl_FragColor=ambientColor+diffuseColor;
}

总结

以上为三种光源的OpenGL实现,可以看出,计算量最大的为聚光灯效果,而且为了让边缘柔和,需要pow的大量计算。移动端跑实时运算估计要优化算法了。

OpenGL(十二) 光源类型 平行光 、 点光源 、 聚光灯 的实现

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

Mac搭建本地局域网SVN服务(Cornerstone)

2017年5月10日 没有评论

下周需要和团队一起去外地工作几天,发现提交代码合并比较麻烦,考虑到团队还有美术同学,想想还是本地开启个svn,先临时用用吧。

mac系统是已经安装了svn服务的,所以这里我们只需要开启就行了。

1. 验证本地已安装svn服务。

$ svnserve --version

2. 创建代码库文件夹LocalSVNServer

$ sudo mkdir -p ~/Documents/LocalSVNServer

3. 初始化svn代码库

$ sudo svnadmin create ~/Documents/LocalSVNServer

打开LocalSVNServer文件夹,你会发现已经生成了svn服务。
Mac搭建本地局域网SVN服务(Cornerstone)

4. 配置svn用户权限,打开conf/svnserve.conf

Mac搭建本地局域网SVN服务(Cornerstone)

修改以上位置,其中anon-access = read代表匿名访问的时候是只读的,若改为anon-access = none代表禁止匿名访问,需要帐号密码才能访问

5. 配置账号passwd文件,打开conf/passwd

Mac搭建本地局域网SVN服务(Cornerstone)
[users]标签下面的是用户账号,把需要的 用户名=密码 添加在这里

6. 配置权限authz文件,打开conf/authz

Mac搭建本地局域网SVN服务(Cornerstone)
在[groups]下添加dev = liuyanghui标示创建了一个用户组dev,此用户组包含有liuyanghui用户,多个的话逗号连接:dev = liuyanghui,liuyanghui2

[/]
@dev = rw 这两句标示给dev用户组相应的权限

[/]表示授权的目录路径,这里是根目录,假如根目录下有一个目录叫做test,那么我们如果要编辑此目录的权限那么就要写成[test:/]

@dev表示给用户组授权,如果要给某一个用户授权则不用写前面的@,r表示可读,w表示可写

7. 启动svn(启动成功后活动监视器中可以搜索svnserve,会发现存在)

$ svnserve -d -r ~/Documents/LocalSVNServer

  • 关闭SVN
  • 命令终端直接输入下面命令,第一个数字为进程号。
  • $ ps aux | grep svn
  • Mac搭建本地局域网SVN服务(Cornerstone)
  • 然后$ sudo kill -xxx (xxx代表进程号),上面图片就是18328
  • 可以在活动监视器里进行搜索:svnserve,停止该服务

8. Cornerstone添加svn仓库,局域网其他机器svn地址写ip。本机的话localhost

Mac搭建本地局域网SVN服务(Cornerstone)

9. 如果发现提交svn出现文件夹无权限的话(一般是txn-current-lock无读写权限),增加svn仓库文件夹权限

$ sudo chmod -R a+w ~/Documents/LocalSVNServer/

分类: 未分类 标签:

OpenGL(十一) 可编程管线 基础光照 的实现

2017年5月2日 没有评论

在OpenGL中创建 基础光照 ,主要的工作将模型中的法线信息和法线空间运算矩阵传入到shader中。另一方面,LightDir,ViewDir通常是在shader中从引擎参数获取的,为了简化光照模型的实现,这里我们可以在shader中写死。至于经典的 ambient+diffuse+specular 光照原理,不是本文的重点,就在shader中一笔带过了。

原理

通过函数

glm::mat4 normalMatrix = glm::inverseTranspose(s_shaderData.model);

可以获取当前模型的法线矩阵。用来将法线信息从模型坐标转换为世界坐标。这个矩阵是随着模型的Transform改变而改变的,因此需要在Render时动态计算。

实现

在 基础光照 中,数据传递没什么特殊的,将Normal信息作为attribute传递到shader,将NormalMatrix作为uniform传递到shader。

//normalmatrix
glm::mat4 normalMatrix = glm::inverseTranspose(s_shaderData.model)
glUniformMatrix4fv(s_shaderData.NMLocation,1,GL_FALSE,glm::value_ptr(normalMatrix))

// normal
glEnableVertexAttribArray(s_shaderData.normalLocation);
glVertexAttribPointer(s_shaderData.normalLocation, 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)(sizeof(float)*5)); 

vs中首先将法线转置到世界坐标,然后将其传递给fs。

//vs
attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;

uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
uniform mat4 NM;

varying vec3 V_Normal;
varying vec3 V_WorldPos;

void main()
{
    V_Normal = mat3(NM)*normal;
    V_WorldPos = M * vec4(pos,1.0);
    gl_Position=P*V*M*vec4(pos,1.0);
}

在fs中使用经典的pong公式做个最简单的效果。

varying vec3 V_Normal;
varying vec3 V_WorldPos;

void main()
{
    vec3 lightPos = vec3(10.0,10.0,0.0);
    vec3 L = lightPos;

    L = normalize(L);
    vec3 n = normalize(V_Normal);

    //ambient
    vec4 AmbientLightColor = vec4(0.2,0.2,0.2,1.0);
    vec4 AmbientMaterial = vec4(0.2,0.2,0.2,1.0);
    vec4 ambientColor = AmbientLightColor * AmbientMaterial;
    
    //diffuse
    vec4 DiffuseLightColor = vec4(1.0,1.0,1.0,1.0);
    vec4 DiffuseMaterial = vec4(0.8,0.8,0.8,1.0);
    vec4 diffuseColor = DiffuseLightColor * DiffuseMaterial * max(0.0,dot(L,n));

    //specular
    vec3 reflectDir = normalize(reflect(-L,n));
    vec3 viewDir = normalize(vec3(0.0)-V_WorldPos.xyz);
    vec4 SpecularLightColor = vec4(1.0,1.0,1.0,1.0);
    vec4 SpecularMaterial = vec4(1.0,1.0,1.0,1.0);
    vec4 specularColor = SpecularLightColor*SpecularMaterial*pow(max(0.0,dot(viewDir,reflectDir)),128);

    gl_FragColor= ambientColor+diffuseColor+specularColor;
}

另外需要注意的是,有光照的模型通常需要打开深度测试,也需要记得将深度缓存清空。

//Open test
glEnable(GL_DEPTH_TEST);

//when render
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

总结

通过上面的代码,搭建基本框架。可以实现 基础光照 模型。如果需要添加其他参数效果,则需要增加Uniform传递的参数了。

OpenGL(十一) 可编程管线 基础光照 的实现

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

OpenGL(十) 手机不可用特性 ComputeShader GeometryShader …

2017年4月25日 没有评论

由于当前市场上还存在大量低端Android机器,它们只支持到OpenGL ES2.0,因此 手机不可用特性 有很多。对于一些3.0 甚至4.0的特性,只能看看热闹了。当然基本了解还是需要的,万一哪天都升级到3.0了呢。由于我现在也使用Unity3d开发,文中也有Unity3d的相关指标一起参考。

Subroutine

这个技术在之前的文章:OpenGL(八)使用 subroutine 切换可编程管线中介绍过,它是在OpenGL 4.0中加入的特性。目前我似乎未在Unity中找到对应的封装技术,唯一有些类似的是shader中的LOD技术,不过其实并不一样。

Instancing

这个技术之前也介绍过:OpenGL(七) GeometryInstancing 几何体实例化,它是OpenGL ES3.0引入的,同时又支持IOS,因此还是可以勉强用用的。对于Unity3d中的封装可以看官方文档

Compute Shader

这是在OpenGL 4.3中引入的特性。对应的ES版本为OpenGL ES3.1。在Unity3d的官方文档 中说明了它的使用范围:Modern OpenGL platforms (OpenGL 4.3 on Linux or Windows; OpenGL ES 3.1 on Android). Note that Mac OS X does not support OpenGL 4.3, so no compute shaders there yet。所以等到Android3.1成为主流且IOS支持,才能利用起强大的GPU运算啊。

Geometry Shader

它是在OpenGL 3.2中引入的特性。能用来凭空产生顶点。这就能有很多想象空间了,比如我绘制一个GL_POINT,但我在这里加进来6个顶点,就能绘制一个六星芒阵。可惜 官方表示 它需要 #pragma target 4.0 也就是要OpenGL ES3.2 或 OpenGL ES3.1+AEP。所以估计短期之内应该没戏。

Transform Feedback

OpenGL ES3.0加入的特性,用于将图元的属性在GPU上读取与更改。可以极大的减少CPU与GPU之间由于属性读写引起的通信。至于ParticleSystem底层应该没使用这种技术,我并未找到相应的资料,然而我估计为了兼容ES2.0,应该是没使用。在shader中我也没有对应的接口,希望有人知道可以告诉我。

Tessellation Shaders

OpenGL4.0加入的特性(似乎还没加入到ES中?)它可以自定义tcs之后出来的曲面细分规则。不用说,跟Geometry Shader一样,短期没戏。

总结

对于Unity3d来说,手机不可用的特性 与OpenGL是基本相同的。如果想仔细分辨,你可以查阅 官方的ShderModeTarget Levels 来确认相关的技术是否支持。总的说来对于移动平台:

  • 当前可用技术是Instancing(Unity5.5加入)
  • 遥遥无期的是Compute Shader,Geometry Shader,Tessellation Shader
  • 未具体说明的是Subrotine,Transform Feedback.

另外,关于OpenGL教程,极客学院翻译了一个tutorial 在这里 也蛮不错的。

OpenGL(十) 手机不可用特性 ComputeShader  GeometryShader ...

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

iOS 10.3 改进后的App Review机制

2017年4月24日 没有评论

今天没事查看了下iOS 10.3 的变更功能。发现Apple修改了Review机制,提供App内直接Review弹窗。

SKStoreReviewController.requestReview()

iOS 10.3 改进后的App Review机制

如果没有网络则无任何反应。
据说有调用次数限制,不过API中没有提到,我测试也没有触发这个现象。

看了API说明的话,有人可能注意到了,这句话:

available to the App Store by appending the query params "action=write-review" to a product URL.

我测试了下,如果在itms-apps url中添加action=write-review则可以打开AppStore中App评论详情,同时自动打开评论编辑窗口

func reviewApp(for appId: String) {
        if let url = URL(string: "itms-apps://itunes.apple.com/app/id/(appId)?action=write-review") {
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
    }

iOS 10.3 改进后的App Review机制

分类: 未分类 标签:

OpenGL(九)使用 FrameBufferObject

2017年4月19日 没有评论

在OpenGL中所有的图形,都会绘制到 FrameBufferObject 上。如果想使用界面的做分屏渲染,或需要屏幕图像制成贴图以备后期处理,就需要用到 FrameBufferObject 技术,这种方式也被称为 RTT (Render to Texture)。

原理

通过

glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,colorBuffer,0);

可以将ColorBuffer绑定到一张Texture上。当然也可以绑定到多张贴图中。另外使用这个函数也可以取出深度贴图。

当生成了自己的fbo之后,即可将图元绘制到贴图上面,可以理解为是一个预渲染的过程。

glBindFramebuffer(GL_FRAMEBUFFER,fbo);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
draw();
glBindFramebuffer(GL_FRAMEBUFFER,0);

然后在实际渲染时使用fbo中的贴图进行绘制。

实现

创建一个产生fbo的函数,并返回ColorBuffer与depthBuffer

GLuint CreateFrameBufferObject(GLuint &colorBuffer,int width,int height)
{
    GLuint fbo;
    glGenFramebuffers(1,&fbo);
    glBindFramebuffer(GL_FRAMEBUFFER,fbo);
    
    //color buffer
    glGenTextures(1,&colorBuffer);
    glBindTexture(GL_TEXTURE_2D,colorBuffer);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,colorBuffer,0);
    glBindTexture(GL_TEXTURE_2D,0);

    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(status != GL_FRAMEBUFFER_COMPLETE)
    {
        printf("FBO Create Fail/n");
    }
    glBindFramebuffer(GL_FRAMEBUFFER,0);
    return fbo;
}

接着创建另一个program来做平面承接fbo中的texture。

//create fbo
GLuint colorBuffer;
GLuint fbo = CreateFramebufferObject(colorBuffer, width, height);

//main draw
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
draw();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
RenderFullScreenQuad();
glFlush();

auto draw = [&]()->void
{
    //draw work
}

auto RenderFullScreenQuad = [&]()->void
{
    glUseProgram(fsqprogram);

    glBindTexture(GL_TEXTURE_2D, colorBuffer);
    glUniform1i(fsqtextureLocation, 0);
    glBindVertexArray(fsqvao);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, fsqibo);
    glDrawElements(GL_TRIANGLES, fsqindexCount, GL_UNSIGNED_INT, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

    glBindVertexArray(0);
    glUseProgram(0);
};

最后在shader中做一个全屏的面片,如果是标准的0.5为模型空间坐标的Quad可以简单的使用:

attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;

uniform mat4 M;
uniform mat4 V;
uniform mat4 P;

varying vec2 V_Texcoord;

void main()
{
    vec4 position=vec4(pos.x*2,pos.y*2,pos.z,1.0);
    V_Texcoord=texcoord;
    gl_Position=position;
}

来制作一个全屏的面片,最终将贴图贴到面片上即可。这部分的实现可以参考之前的文章:OpenGL(三) 加载贴图

总结

通过 FrameBufferObject (或称为RTT) 可以将画面抓到一张贴图中,有了这张图,我们就可以做很多后期处理的效果。

OpenGL(九)使用 FrameBufferObject

OpenGL(九)使用 FrameBufferObject

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

Dota2 AI 开发 (二)定制AI阵容 配置英雄出装

2017年4月17日 没有评论

Dota2 AI 开发(一)环境配置 中介绍了如何搭建 Dota2 AI 的开发环境,在这篇文章中,主要介绍Dota2中AI的常规控制方式,并介绍如何在人机比赛中配置一个裸跳刀的Sven。

常用指令

  • 重新加载Lua脚本:dota_bot_reload_scripts
  • 加速游戏:host_timescale 4.0
  • 开启作弊:sv_cheats 1

控制选人

在bots目录下创建一个名为 hero_selection.lua的脚本,在其中输入如下内容:

function Think()


    if ( GetTeam() == TEAM_RADIANT )
    then
        print( "selecting radiant" );
        SelectHero( 0, "npc_dota_hero_antimage" );
        SelectHero( 1, "npc_dota_hero_lina" );
        SelectHero( 2, "npc_dota_hero_sven" );
        SelectHero( 3, "npc_dota_hero_bloodseeker" );
        SelectHero( 4, "npc_dota_hero_crystal_maiden" );
    elseif ( GetTeam() == TEAM_DIRE )
    then
        print( "selecting dire" );
        SelectHero( 5, "npc_dota_hero_drow_ranger" );
        SelectHero( 6, "npc_dota_hero_earthshaker" );
        SelectHero( 7, "npc_dota_hero_juggernaut" );
        SelectHero( 8, "npc_dota_hero_mirana" );
        SelectHero( 9, "npc_dota_hero_nevermore" );
    end

end

这就是自定义的英雄阵容了,己方阵容为:

  • 敌法
  • 火女
  • 斯文
  • 血魔
  • 冰女

敌方为:

  • 小黑
  • 小牛
  • 剑圣
  • 白虎
  • 影魔

阵容当然是可以换的,那么如何知道想要的英雄叫什么呢?

更改为英文

在游戏属性设置>设置启动项中添加 -language english即可将界面改成英文,这样就可以知道所有东西的名称了。

比如在上面的敌方阵营中没有T,我希望把影魔换掉,所以我找到DK,它的名字如下:

Dota2 AI 开发 (二)定制AI阵容 配置英雄出装

所以将上面函数选人的最后一行改为:

SelectHero( 9, "npc_dota_hero_abaddon" );

改好后保存,然后在控制台运行

dota_bot_reload_scripts

然后再次创建房间开游戏,就会发现预选的角色变化了

Dota2 AI 开发 (二)定制AI阵容 配置英雄出装

通过这样的方式,相信大家能找到一个合适的阵容,以提高游戏乐趣。当然这也是制作特定角色AI的基础。

控制购买装备

在 Dota2 ai 中,是可以制定不同英雄的出装套路的。只要按照命名规范创建item_purchase_xxxx.lua即可。不过我发现有的道具名字并不是显示的英文名。例如跳刀明明是:

Dota2 AI 开发 (二)定制AI阵容 配置英雄出装

结果到了配置里就是item_blink,这是怎么回事呢?我查了一下午,终于搞明白其中的缘由了。因为道具名称可能会随着道具描述更改,为了保持底层数据的稳定,官方制定了一个对照表。我在gamepedia上找到了一个版本。 应该是比较全的了,另外这个页面里也有英雄名哦。

我觉得有时候找起来实在是麻烦了,所以会直接通过指令给自己调装备测试,例如我刚刚的跳刀,我可以通过指令来尝试来找到名字,如果装备名正确,自己使用的角色身上就会获得它。

-item item_blink_dagger //wrong
-item item_blink //correct

弄清楚了道具名称,我们就可以开始着手制作自己的AI出装了。这里我以Sven为例。

实现

我希望Sven,买完吃喝之后,裸一个跳刀,然后出相位鞋、连击刀、bkb、撒旦、龙心。首先我先在bots目录下创建一个名为:item_purchase_sven.lua的文件,然后编辑如下内容:

local tableItemsToBuy = { 
        "item_tango",
        "item_clarity",
        "item_flask",
        ----------------------
        "item_blink",
        ---------------------
        "item_boots", 
        "item_blades_of_attack",
        "item_blades_of_attack",
        ----------------------
        "item_ogre_axe",
        "item_quarterstaff",
        "item_sobi_mask",
        "item_robe",
        ---------------------- 
        "item_ogre_axe",
        "item_mithril_hammer",
        "item_recipe_black_king_bar",
        -----------------------
        "item_lifesteal",
        "item_mithril_hammer",
        "item_reaver",
        ----------------------
        "item_reaver",
        "item_vitality_booster",
        "item_recipe_heart",
    };


-----------------------------------------------------------------------------

local secretShopThreshold = 100000;
local distanceBuyShop = 500;

function ItemPurchaseThink()

    local npcBot = GetBot();

    if ( #tableItemsToBuy == 0 )
    then
        npcBot:SetNextItemPurchaseValue( 0 );
        return;
    end

    local sNextItem = tableItemsToBuy[1];

    npcBot:SetNextItemPurchaseValue( GetItemCost( sNextItem ) );

    if ( npcBot:GetGold() >= GetItemCost( sNextItem ) )
    then
        if ( IsItemPurchasedFromSecretShop(sNextItem) and 
            npcBot:DistanceFromSecretShop() <= secretShopThreshold )
        then
            --print("Money is enough,Will Move to secret shop for: ",sNextItem);
            npcBot.secretShopMode = true;

            local shop_top = Vector(-4600, 1200);
            local shop_bot = Vector(4600,  -1200);

            local dist_top = GetUnitToLocationDistance( npcBot, shop_top );
            local dist_bot = GetUnitToLocationDistance( npcBot, shop_bot );

            if (dist_top < dist_bot) then
                npcBot:Action_MoveToLocation(shop_top);
            else
                npcBot:Action_MoveToLocation(shop_bot);
            end

            if ( npcBot:DistanceFromSecretShop() <= distanceBuyShop ) 
            then
                print("Will buy at secret shop : ",sNextItem," cost is:",
                    tostring(GetItemCost(sNextItem)));
                npcBot:ActionImmediate_PurchaseItem( sNextItem );
                table.remove( tableItemsToBuy, 1 );
                npcBot.secretShopMode = false;
            end
        else
            print("Money is enough,Will buy: ",sNextItem," cost is:",
                tostring(GetItemCost(sNextItem)));
            npcBot:ActionImmediate_PurchaseItem( sNextItem );
            table.remove( tableItemsToBuy, 1 );
        end
    end

end

-------------------------------------------------------------------------

需要注意的是不能买合成出的装备,需要自己按配方一个个配置。

配好后难道要等AI自己打出那么多钱来测试么?当然不,通过指令

dota_bot_give_gold 1000

来给所以AI加钱,直接就能看到结果。为了调试可以先把出门用的吃喝注释掉,防止占格子位置。

另外这套AI还有些瑕疵。我发现当角色不在神秘商店附近时是不可以买东西的,所以我写了个逻辑:如果需要买神秘商店的装备,就先将角色移动过去,然后再买。逻辑是这样的没错,不过角色移动的表现会比较傻缺。不管不顾的直接走也就罢了,有时候还会摩擦摩擦。搞了一天又累又饿,既然都能用,就先这样吧,以后有时间再优化,或者有大神路过帮忙看看也好。

总结

最后上个效果图

Dota2 AI 开发 (二)定制AI阵容 配置英雄出装

Dota2 AI 开发 (二)定制AI阵容 配置英雄出装

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

OpenGL(八)使用 subroutine 切换可编程管线

2017年4月14日 没有评论

Subroutine 功能是在OpenGL 4.0 版本里才增加的,因此对于各种Android手机,这个功能基本跪了。如果你发现你的程序报错:ARB_shader_subroutine,那就说明当前显卡不支持。不过大体思路可以了解一下,因为思路类似的功能有其他的实现方式。

原理

在shader中声明一个函数变量,然后定义它的指针,并将其作为一个uniform变量公开出去。最后定义很多复写函数即可。

实现

由于版本限制,使用 subroutine 要注意在shader中加入版本的编译宏:

#version 400 core

在shader中编写:

subroutine vec4 SurfaceColor();
subroutine uniform SurfaceColor U_SurfaceColor;

subroutine (SurfaceColor) vec4 Ambient()
{
   //...
}

subroutine (SurfaceColor) vec4 Diffuse()
{
    //...
}

subroutine (SurfaceColor) vec4 Specular()
{
    //...
}

void main()
{
    gl_FragColor = U_SurfaceColor();
}

在shader中,每一个函数中的代码段代表一种处理函数。另一方面在GL指令中,绑定这个函数指针,并为其指定实现函数的索引值,即可实现效果的控制。

surfaceColorLocation = glGetSubroutineUniformLocation(program,GL_FRAGMENT_SHADER,"U_SurfaceColor");

GLuint ambientLightIndex = glGetSubroutineIndex(program,GL_FRAGMENT_SHADER,"Ambient");
GLuint diffuseLightIndex = glGetSubroutineIndex(program,GL_FRAGMENT_SHADER,"Diffuse");
GLuint specularLightIndex = glGetSubroutineIndex(program,GL_FRAGMENT_SHADER,"Specular");

//draw
glUniformMatrix4fv(MLocation, 1, GL_FALSE, glm::value_ptr(model));
glUniformSubroutinesuiv(GL_FRAGMENT_SHADER,1,&ambientLightIndex);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,ibo);
glDrawElements(GL_TRIANGLES,indexCount,GL_UNSIGNED_INT,0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

总结

本文介绍了OpenGL中的 subroutine 机制,通过它可以切换shader的内容。在Unity3d中使用Shader.maximumLOD技术可以达到类似的效果,相关内容可以参考官方文档

OpenGL(八)使用 subroutine 切换可编程管线

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

OpenGL(七) GeometryInstancing 几何体实例化

2017年4月10日 没有评论

几何体实例化( GeometryInstancing ),是一种用于大批量重复物件渲染的GPU技术,以降低客户端和显卡端数据传输量,所谓的“一次提交,多次渲染”。简单说来就是合并DrawCall。

原理

通过

glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0,3);

可以绘制多次几何体,这样就可以实现合并。然而不同几何体都重叠在一起了,这就需要传入一个差异性参数。然后通过glVertexAttribDivisor来指定分组赋值规则,这就相当于for循环最后的++i

实现

首先先设定一个变量数组,并将它存入VBO中

float posOffsets[] = {
    -1.0f,0.0f,0.0f,
    0.0f,0.0f,0.0f,
    1.0f,0.0f,0.0f
};
GLuint offsetVBO=CreateBufferObject(GL_ARRAY_BUFFER, sizeof(float) * 9, GL_STATIC_DRAW, posOffsets);

//get location index
offsetLocation = glGetAttribLocation(program, "offset");

绘制部分将这组VBO分组:

glBindBuffer(GL_ARRAY_BUFFER, offsetVBO);
glEnableVertexAttribArray(offsetLocation);
glVertexAttribPointer(offsetLocation, 3, GL_FLOAT, GL_FALSE, sizeof(float)*3, (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(offsetLocation,1);

最后在shader中承接这组数据:

attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;
attribute vec3 offset;

uniform mat4 M;
uniform mat4 V;
uniform mat4 P;
uniform mat4 NM;

varying vec3 V_Normal;
varying vec4 V_WorldPos;
varying vec2 V_Texcoord;

void main()
{
    V_Normal=mat3(NM)*normal;
    V_WorldPos=M*vec4(pos,1.0)+vec4(offset,1.0);
    V_Texcoord=texcoord;
    gl_Position=P*V*V_WorldPos;
}

这样就可以在3个不同的位置绘制相同的物体,且只占用一次DrawCall。

总结

通过 GeometryInstancing 可以在OpenGL中,实现合并Drawcall。

OpenGL(七) GeometryInstancing 几何体实例化

关注我的微信公众号,获取更多优质内容

分类: 未分类 标签:

[置顶] 房卡麻将分析之"防作弊处理"

2017年3月29日 没有评论

房卡麻将分析之"防作弊处理"

           

          棋牌游戏最重要的一个特点就是人与人对局,因为玩家各自的不可见,就存在着一些作弊的可能性和漏洞。对于手机房卡麻将游戏,大家最讨厌的问题就是作弊。如何防止玩家作弊,保证玩家的公平性,从技术上我们来讨论一些方案。



          首先,我先假设你的代码中服务器并不会犯一些低级错误,比如将所有玩家的手牌信息发给客户端。如果你没有保证好这一点,那么你的游戏将非常容易被辅助外挂进行”明牌化“处理。除去算牌记牌软件的因素,你的服务器只要不存在相关的漏洞就不用担心外挂。

                               [置顶]        房卡麻将分析之&quot;防作弊处理&quot;
          去掉服务器的因素,在客户端现在最经常出现的防作弊方案主要有两个方向:

一。玩家信息检测


第一种方向,主要是对于玩家的状态进行检测,目前也包括三个小的方法:


(一)玩家同IP提示。


          第一种方法它只是能够预防基于同一个局域网的玩家在一起游戏。处理也非常简单,因为服务器在接收手机联接时可以取得客户端IP地址,通过简单的转发,就可以让玩家得知同房间其它玩家的IP地址。如果房间里有相同IP的玩家,做一些提示即可。不过这种方案并不能够有效的预防作弊,基于4G信号来进行游戏的手机可以轻验跳过这样的验证。更多的时候只是一个心理预防。


(二)玩家GPS定位提示。


           第二种方法使用GPS定位来侦测玩家间距离相近或者定位一致。比第一种方案更真实的反映玩家是否在一起的情况。具体开发的时候,可以使用高德或百度的地图SDK,开发者到高德官网上申请账号,注册成为开发者后,可以创建应用,将包的信息提交,取得KEY,按照官网SDK接入游戏即可以方便的获取定位信息了。

[置顶]        房卡麻将分析之&quot;防作弊处理&quot;

           在服务器的数据库上,一般会有玩家上线信息的表,包括头像地址和GPS地址,玩家登录游戏后,通过高德或百度SDK取得当前定位并发送给服务器存储到数据库中,当玩家进入房间后,服务器也将其它玩家的相关信息发过来,在客户端进行对比并提示即可。

                        [置顶]        房卡麻将分析之&quot;防作弊处理&quot;

               在整个游戏过程中,可以根据需要进行定位更新处理。保证牌局对玩家进行实时检测。不过一般只需要游戏每局开局时做一些简单处理即可。


(三)玩家游戏过程视频在线。  


               第三种方法则可以在游戏过程中开启视频聊天,或用微信视频聊天,可以随时看到对方玩家在专注的游戏,也就避免玩家去进行作弊操作。不过这种方式相对比较耗电,技术上也相对较难一点。并不是太推荐。


               总体说来,在第一个方向上第二种方法比较可行,也较易操作。

二。牌局信息对比


第二种方向,主要是对于牌局的过程进行检查,目前也包括两个小的方法:


(一)牌局回放。

                牌局回放的意义在于玩家可以在游戏结束后对整个过程进行回顾,分析四个玩家的牌面进展整个过程,发现一些作弊操作。这个部分的技术方法我在公众号早期文章中进行了详细讲述,这里不再赘述,有兴趣的朋友可以查一下公众号历史文章。


                       [置顶]        房卡麻将分析之&quot;防作弊处理&quot;

(二)吃碰杠的供应记录。

              作弊的手段无非就是给牌,但一般游戏最容易的给牌就是吃和碰,在这方面做一些记录和显示,可以给玩家展现游戏过程中玩家是如何给牌的。比如我们的”大赢家“红中麻将,你可以清楚的看到哪个玩家给哪个玩家的碰牌。这种信息的处理非常重要和细致。

[置顶]        房卡麻将分析之&quot;防作弊处理&quot;

                 好了,基本上来说,并没有什么办法完全避免基于手机的房卡麻将,我们所做的手段,无非是降低作弊难度,提升公平性。如果有人说房卡麻将能百分之百防作弊,我觉得还是不要相信啦。最后我想说的是:游戏开心,远离赌博!

 

                  AR,VR,房卡棋牌技术,关注公众号:红孩儿的游戏开发之路

                                                   [置顶]        房卡麻将分析之&quot;防作弊处理&quot;

分类: 未分类 标签: