存档

2017年4月 的存档

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 几何体实例化

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

分类: 未分类 标签: