问题

游戏程序员的核心竞争力是什么?

回答
游戏程序员的核心竞争力是一个多方面、高度整合的技能组合,它使得他们能够将创意设计转化为引人入胜、技术卓越的游戏体验。与许多其他软件开发领域不同,游戏开发对技术、艺术和用户体验有着独特的交织要求。下面我将从几个关键维度进行详细阐述:

一、扎实而广泛的技术基础:

这是所有程序员的基石,但在游戏领域尤为重要,因为游戏涉及到的技术领域非常广阔。

精通至少一种主流游戏引擎(如Unity, Unreal Engine, Godot等):
深入理解引擎架构: 不仅仅是知道API如何调用,而是理解引擎的渲染管线、物理引擎、动画系统、音频系统、UI系统、资源管理、脚本系统等核心组件的工作原理。例如,理解Unity的ECS(实体组件系统)或Unreal Engine的Actor/Component模型。
高效的脚本/编程语言能力: 熟练掌握C(Unity)、C++(Unreal Engine)、GDScript/C(Godot)等语言,并能够利用其特性高效地实现游戏逻辑、性能优化和系统扩展。
模块化和可维护性: 能够按照引擎的最佳实践编写代码,将功能分解为可复用的组件和模块,便于团队协作和长期维护。
精通一种或多种底层编程语言(如C++, C):
性能敏感性: 大部分游戏,特别是对性能要求极高的AAA游戏,都依赖于C++进行核心逻辑、渲染、物理、AI等关键部分的开发。这意味着需要理解内存管理(手动或智能指针)、多线程、数据结构和算法的性能影响。
系统编程和底层优化: 能够直接操作内存、理解CPU缓存、SIMD指令等,以榨取硬件的最高性能。这对于实现流畅的帧率和复杂的游戏模拟至关重要。
与引擎底层交互: 许多时候需要编写C++插件来扩展引擎功能,或直接修改引擎源码以满足特定需求。
数据结构与算法的深度掌握:
性能优化基础: 理解不同数据结构(数组、链表、树、哈希表等)和算法(排序、搜索、图算法、路径查找等)的时间和空间复杂度,并能在游戏开发中选择最优解,尤其是在处理大量游戏对象、复杂AI、寻路等场景。
游戏特定算法: 例如,四叉树/八叉树(空间分割)、BSP树(关卡渲染)、A寻路算法、行为树(AI决策)、状态机等在游戏开发中非常常见,需要深入理解和应用。
数学基础(线性代数、微积分、几何):
3D图形学: 理解向量、矩阵变换(平移、旋转、缩放)、四元数、投影、光照模型、着色器等是实现3D渲染的基础。
物理模拟: 理解刚体动力学、碰撞检测、关节模拟等,需要一定的物理学和积分数学知识。
游戏逻辑: 许多游戏机制,如弹道预测、相机控制、角色移动、AI行为等,都离不开数学的支撑。

二、游戏设计与用户体验的理解和实现能力:

游戏程序员不仅仅是写代码的工程师,更是将游戏设计变为现实的执行者,因此理解和能够实现良好的游戏设计和用户体验至关重要。

将设计文档转化为可玩的游戏功能: 能够理解设计师的意图,并将其转化为清晰、可执行的代码。这需要良好的沟通能力和对游戏设计模式的认知。
对“手感”(Game Feel)的追求: “手感”是游戏体验中非常主观但至关重要的部分,它包括响应速度、反馈动画、音效、视觉效果等。程序员需要通过精细的参数调整、动画插值、粒子系统、震动反馈等方式来打磨游戏的“手感”。例如,一个角色的跳跃动作,不仅仅是按下按钮就跳起来,还需要考虑跳跃的高度、下降的加速度、着陆时的缓冲效果、镜头跟随等一系列细节。
实现良好的用户界面(UI)和用户体验(UX): 游戏UI不仅仅是显示信息,更是玩家与游戏交互的界面。程序员需要实现响应式、直观易用的UI系统,确保菜单导航流畅、按钮反馈清晰、信息展示易于理解。这涉及到UI布局、动画过渡、输入处理等。
理解玩家的期望: 能够站在玩家的角度思考,什么样的交互是自然的,什么样的反馈是令人满意的,什么样的功能是缺失的。

三、高性能和优化的能力:

游戏是高度实时且资源密集型的应用,性能是生死线。

性能分析和诊断: 能够使用性能分析工具(Profiler)找出代码中的性能瓶颈,例如CPU占用过高、内存泄漏、渲染帧率低等。
多线程编程: 能够有效地利用多核CPU,将游戏逻辑拆分到不同的线程执行,例如物理计算、AI更新、音频处理、网络通信等,以提高整体性能。需要熟悉同步机制(锁、信号量等)以避免竞态条件。
内存管理和优化: 游戏运行时需要管理大量的资产(模型、纹理、音频)和数据。程序员需要有效地分配和释放内存,避免内存泄漏和碎片化,优化数据加载和卸载策略,以减少内存占用和加载时间。
渲染优化: 理解渲染管线,掌握各种优化技术,如遮挡剔除(Occlusion Culling)、视锥剔除(Frustum Culling)、批处理(Batching)、LOD(Level of Detail)、Baked Lighting、Shader优化等,以保证游戏在目标硬件上流畅运行。
算法优化: 如前所述,选择和实现更高效的算法,以减少CPU计算量。

四、协作和沟通能力:

游戏开发是一个高度协作的过程,涉及美术、策划、音效、QA等多个团队。

清晰的代码风格和文档: 编写易于理解和维护的代码,并编写必要的注释和文档,方便团队其他成员阅读和理解。
版本控制系统(如Git)的熟练使用: 能够有效地进行代码版本管理、分支管理、合并代码,并处理冲突。
有效的沟通: 能够清晰地向设计师、美术师或其他程序员解释技术实现的可能性、限制和进度。也能够理解他们的需求并给出可行性建议。
跨部门合作: 能够理解美术师制作的资源在技术上的限制和优化需求,也能够理解策划的游戏机制对技术实现的要求。

五、问题解决和持续学习的能力:

游戏开发是一个充满挑战和不断变化的环境。

调试能力: 能够快速定位和修复Bug,这需要耐心、细致和系统性的分析能力。
解决未知问题: 面对新的技术、新的引擎特性或未曾遇到的技术难题时,能够主动搜索资料、学习新知识、尝试不同的解决方案,并最终找到最优解。
适应新技术: 游戏行业技术更新迭代非常快,需要持续学习新的编程语言、引擎版本、开发工具和技术趋势。
创意性思维: 有时候,解决游戏开发中的棘手问题需要一些创意的编程思路。

总结来说,游戏程序员的核心竞争力可以概括为以下几点:

技术深度与广度: 既要精通引擎和编程语言,也要有扎实的数学和算法基础。
性能优先的思维: 始终将性能和优化放在重要位置。
设计与体验的感知: 理解并能实现出色的游戏设计和玩家体验。
高效的协作沟通: 能够与团队其他成员紧密合作。
强大的问题解决和学习能力: 应对游戏开发中的各种挑战并不断进步。

一个优秀的游戏程序员是技术的“魔法师”,能够将抽象的概念转化为生动逼真的游戏世界,同时也是精益求精的工匠,不断打磨细节,为玩家带来极致的享受。

网友意见

user avatar

很简单,别人不会的你会。

比如,Bitonic Sort(双调排序,时间复杂度 )是一种并行排序算法,特别适合在GPU上实现。我自己用OpenCL实现了一个并进行了调优,算法是我用纸笔推导的,分为5个kernel。在GPU上编程不同于CPU,没有调试器可用,也没有printf,数组越界电脑直接黑屏。

我就直接用C++实现了一个在CPU上运行的OpenCL kernel,实现了__local限定符 get_global_id get_local_id barrier等函数,用它实现的我的排序算法。这样就可以很方便的进行调试了,验证完成后移植在GPU上。

本人看过多个Bitonic Sort的实现, 在github搜索的基本上就是一堆玩具,没有我实现的好,Microsoft DirectX SDK中的也是玩具。DirectX-Graphics-Samples的 使用了Indirect模式,但是其numthreads是写死了的。 CUDA SDK Samples的 4个Kernel ,还算可以。

相比于其他的实现,我的这个使用#define设置不同的local size,在不同的硬件上实现更好的性能。

这就是Bitonic Sort的kernel代码(完全是自己实现的):

       #if VEC_STEP == 4 #define VEC_SEQ (uint4)(0,1,2,3)  #define SCALAR_TYPE int #define VEC_TYPE int4 #define VEC_TYPE_AS as_int4 #define LOGICAL_TYPE uint4 #define LOGICAL_TYPE_AS as_uint4 #define INDEX_TYPE uint4 #define INDEX_TYPE_AS as_uint4  #define VLOADN vload4 #define VSTOREN vstore4  #define INPUT_TO_PRIVATE(A,B,IA,IB,INPUT)  A.s0=INPUT[IA.s0]; A.s1=INPUT[IA.s1]; A.s2=INPUT[IA.s2]; A.s3=INPUT[IA.s3]; B.s0=INPUT[IB.s0]; B.s1=INPUT[IB.s1]; B.s2=INPUT[IB.s2]; B.s3=INPUT[IB.s3];  #define PRIVATE_TO_INPUT(A,B,IA,IB,INPUT)  INPUT[IA.s0] = A.s0; INPUT[IA.s1] = A.s1; INPUT[IA.s2] = A.s2; INPUT[IA.s3] = A.s3; INPUT[IB.s0] = B.s0; INPUT[IB.s1] = B.s1; INPUT[IB.s2] = B.s2; INPUT[IB.s3] = B.s3; #else  #error Need VEC_SEQ #endif   #ifndef LOCAL_VEC_SIZE  #error Need LOCAL_VEC_SIZE #endif  #ifndef WG_SIZE  #error Need WG_SIZE #endif  //#define LOCAL_VEC_SIZE 4096  //设 global_size 为 2^m local_size为 2^n 那么  void BitnoicSortLocalStage1_1(__local SCALAR_TYPE* input,const unsigned int num_local)  {  size_t gid   = get_group_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize   = get_local_size(0);    #pragma unroll  for (unsigned int step1 = 1; step1 < LOCAL_VEC_SIZE; step1 <<= 1)  {   unsigned int sh = 0;   for (unsigned int step2 = step1; step2 >= 1; step2 >>= 1)   {    for (unsigned int loop = lid; loop < LOCAL_VEC_SIZE / 2; loop += lsize*VEC_STEP)    {     INDEX_TYPE iloop = VEC_SEQ * (uint)lsize + loop;     INDEX_TYPE gloop = iloop + (LOCAL_VEC_SIZE / 2)*num_local;      INDEX_TYPE t1 = gloop & step1;     INDEX_TYPE t2 = t1 ^ step1;      INDEX_TYPE u1 = (iloop&(~(step2 - 1))) << 1;     INDEX_TYPE u2 = (step2 - 1)&iloop;      INDEX_TYPE a = u1 + u2 + (t1 >> sh);     INDEX_TYPE b = u1 + u2 + (t2 >> sh);      VEC_TYPE ia;// = input[a];     VEC_TYPE ib;// = input[b];      INPUT_TO_PRIVATE(ia,ib,a,b,input);          LOGICAL_TYPE icomp = LOGICAL_TYPE_AS(ia < ib);     LOGICAL_TYPE ncomp = LOGICAL_TYPE_AS(!icomp);      VEC_TYPE oa=select(ia,ib,icomp);     VEC_TYPE ob=select(ia,ib,ncomp);          PRIVATE_TO_INPUT(oa,ob,a,b,input);     }     barrier( CLK_LOCAL_MEM_FENCE);    sh++;   }  } }   void BitnoicSortLocalStage1_2(__local SCALAR_TYPE* input,const unsigned int step1_begin,const unsigned int shift,const unsigned int num_local) {  size_t gid   = get_group_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize   = get_local_size(0);    unsigned int sh = shift;    unsigned int step1 = step1_begin;    #pragma unroll  for (unsigned int step2 = (LOCAL_VEC_SIZE / 2); step2 >= 1; step2 >>= 1)  {   for (unsigned int loop = lid; loop < LOCAL_VEC_SIZE / 2; loop += lsize*VEC_STEP)   {    INDEX_TYPE iloop = VEC_SEQ * (uint)lsize + loop;    INDEX_TYPE gloop = iloop + (LOCAL_VEC_SIZE / 2)*num_local;     INDEX_TYPE t1 = step1 & gloop;    INDEX_TYPE t2 = t1 ^ step1;     INDEX_TYPE u1 = (iloop&(~(step2 - 1))) << 1;    INDEX_TYPE u2 = (int)(step2 - 1)&iloop;     INDEX_TYPE a = u1 + u2 + (t1 >> sh);    INDEX_TYPE b = u1 + u2 + (t2 >> sh);     VEC_TYPE ia;// = input[a];    VEC_TYPE ib;// = input[b];     INPUT_TO_PRIVATE(ia,ib,a,b,input);     LOGICAL_TYPE icomp = LOGICAL_TYPE_AS(ia < ib);    LOGICAL_TYPE ncomp = LOGICAL_TYPE_AS(!icomp);     VEC_TYPE oa=select(ia,ib,icomp);    VEC_TYPE ob=select(ia,ib,ncomp);     PRIVATE_TO_INPUT(oa,ob,a,b,input);    }   barrier(CLK_LOCAL_MEM_FENCE);   sh++;  } }  //运行1次 __kernel __attribute__((reqd_work_group_size(WG_SIZE,1,1))) void BitonicSortLocalStage1_1Kernel(  __global SCALAR_TYPE* input, const unsigned int step1, const unsigned int step2, const unsigned int len ) {  size_t gid   = get_group_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize   = get_local_size(0);   unsigned int loop_count = len/LOCAL_VEC_SIZE;    __local SCALAR_TYPE value[LOCAL_VEC_SIZE];    for(unsigned int ng=gid;ng< loop_count ; ng+=num_groups)  {   async_work_group_copy(value,input+(ng*LOCAL_VEC_SIZE) , LOCAL_VEC_SIZE,(event_t)0);   barrier( CLK_LOCAL_MEM_FENCE); //wait_group_events(1,&e1);      BitnoicSortLocalStage1_1(value,ng);      async_work_group_copy(input+(ng*LOCAL_VEC_SIZE),value , LOCAL_VEC_SIZE,(event_t)0);   barrier( CLK_LOCAL_MEM_FENCE); //wait_group_events(1,&e2);  } }  //运行m-n次 __kernel __attribute__((reqd_work_group_size(WG_SIZE,1,1))) void BitonicSortLocalStage1_2Kernel(  __global SCALAR_TYPE* input, const unsigned int step1, const unsigned int step2, const unsigned int len, const unsigned int sh ) {  size_t gid  = get_group_id(0);  size_t num_groups = get_num_groups(0);  size_t lid = get_local_id(0);  size_t lsize = get_local_size(0);   unsigned int loop_count = len/LOCAL_VEC_SIZE;    __local SCALAR_TYPE value[LOCAL_VEC_SIZE];    for(unsigned int ng=gid;ng< loop_count ; ng+=num_groups)  {   async_work_group_copy(value,input+(ng*LOCAL_VEC_SIZE) , LOCAL_VEC_SIZE,(event_t)0);   barrier( CLK_LOCAL_MEM_FENCE);       BitnoicSortLocalStage1_2(value,step1,sh,ng);      async_work_group_copy(input+(ng*LOCAL_VEC_SIZE),value , LOCAL_VEC_SIZE,(event_t)0);   barrier( CLK_LOCAL_MEM_FENCE);   } }  void BitonicSortLocalStage2(__local SCALAR_TYPE* input) {  size_t gid   = get_group_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize  = get_local_size(0);  size_t gsize   = get_global_size(0);    #pragma unroll  for (unsigned int step1 = LOCAL_VEC_SIZE / 2; step1 >= 1; step1 >>= 1)  {   for (int loop = lid; loop < LOCAL_VEC_SIZE / 2; loop += lsize*VEC_STEP)   {    INDEX_TYPE iloop = VEC_SEQ * (uint)lsize + loop;    INDEX_TYPE u1 = (~(step1 - 1) & iloop) << 1;    INDEX_TYPE u2 = ((step1 - 1) & iloop);     INDEX_TYPE a = u1 + u2;    INDEX_TYPE b = a + step1;     VEC_TYPE ia;// = input[a];    VEC_TYPE ib;// = input[b];     INPUT_TO_PRIVATE(ia,ib,a,b,input);     LOGICAL_TYPE icomp = LOGICAL_TYPE_AS(ia < ib);    LOGICAL_TYPE ncomp = LOGICAL_TYPE_AS(!icomp);     VEC_TYPE icomp_ = VEC_TYPE_AS(icomp & 1);    VEC_TYPE ncomp_ = VEC_TYPE_AS(ncomp & 1);     VEC_TYPE oa = ia*icomp_ + ib*ncomp_;    VEC_TYPE ob = ia*ncomp_ + ib*icomp_;     PRIVATE_TO_INPUT(oa,ob,a,b,input);   }   barrier(CLK_LOCAL_MEM_FENCE);  } }   //运行1次 __kernel __attribute__((reqd_work_group_size(WG_SIZE,1,1))) void BitonicSortLocalStage2Kernel( __global SCALAR_TYPE* input, const unsigned int step1, const unsigned int step2, const unsigned int len ) {  size_t gid   = get_group_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize   = get_local_size(0);    unsigned int loop_count = len/LOCAL_VEC_SIZE;    __local SCALAR_TYPE value[LOCAL_VEC_SIZE];    for(unsigned int ng=gid;ng< loop_count ; ng+=num_groups)  {   async_work_group_copy(value,input+(ng*LOCAL_VEC_SIZE) , LOCAL_VEC_SIZE,(event_t)0);   barrier( CLK_LOCAL_MEM_FENCE);       BitonicSortLocalStage2(value);      async_work_group_copy(input+(ng*LOCAL_VEC_SIZE),value , LOCAL_VEC_SIZE,(event_t)0);   barrier( CLK_LOCAL_MEM_FENCE);   } }  //运行(m-n) + (m-n)(m-n-1)/2 次 __kernel __attribute__((reqd_work_group_size(WG_SIZE,1,1))) void BitonicSortGlobalStage1Kernel(  __global SCALAR_TYPE* input, const unsigned int step1, const unsigned int step2, const unsigned int len, const unsigned int sh ) {  size_t gid   = get_global_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize   = get_local_size(0);  size_t gsize   = get_global_size(0);    for (unsigned int loop = gid*VEC_STEP; loop < len / 2; loop += gsize*VEC_STEP)  {   unsigned int t1 = step1 & loop;   unsigned int t2 = t1 ^ step1;    unsigned int u1 = ((~(step2 - 1))&loop) << 1;   unsigned int u2 = (step2 - 1)&loop;    unsigned int a = u1 + u2 + (t1 >> sh);   unsigned int b = u1 + u2 + (t2 >> sh);    VEC_TYPE ia;   VEC_TYPE ib;   ia = VLOADN(a / VEC_STEP, input);//input[a];   ib = VLOADN(b / VEC_STEP, input);//input[b];      LOGICAL_TYPE icomp = LOGICAL_TYPE_AS(ia < ib);   LOGICAL_TYPE ncomp = LOGICAL_TYPE_AS(!icomp);      VEC_TYPE oa=select(ia,ib,icomp);   VEC_TYPE ob=select(ia,ib,ncomp);    VSTOREN(oa, a / VEC_STEP, input);//input[a];   VSTOREN(ob, b / VEC_STEP, input);//input[b];  } }  //运行m-n次 __kernel __attribute__((reqd_work_group_size(WG_SIZE,1,1))) void BitonicSortGlobalStage2Kernel(  __global SCALAR_TYPE* input, const unsigned int step1, const unsigned int step2, const unsigned int len ) {  size_t gid   = get_global_id(0);  size_t num_groups  = get_num_groups(0);  size_t lid   = get_local_id(0);  size_t lsize   = get_local_size(0);  size_t gsize   = get_global_size(0);    for (unsigned int loop = gid*VEC_STEP; loop < len / 2; loop += gsize*VEC_STEP)  {   unsigned int u1 = (~(step1 - 1) & loop) << 1;   unsigned int u2 = ((step1 - 1) & loop);    unsigned int a = u1 + u2;   unsigned int b = a + step1;    VEC_TYPE ia;   VEC_TYPE ib;   ia = VLOADN(a / VEC_STEP, input);//input[a];   ib = VLOADN(b / VEC_STEP, input);//input[b];    LOGICAL_TYPE icomp = LOGICAL_TYPE_AS(ia < ib);   LOGICAL_TYPE ncomp = LOGICAL_TYPE_AS(!icomp);    VEC_TYPE icomp_ = VEC_TYPE_AS(icomp & 1);   VEC_TYPE ncomp_ = VEC_TYPE_AS(ncomp & 1);    VEC_TYPE oa = ia*icomp_ + ib*ncomp_;   VEC_TYPE ob = ia*ncomp_ + ib*icomp_;    VSTOREN(oa, a / VEC_STEP, input);//input[a];   VSTOREN(ob, b / VEC_STEP, input);//input[b];  } }      

后来找到工作后,将其改成DirectX Compute Shader实现的。完全独自一人完成,公司除本人外没有一个人会。这个是为了做粒子效果用的,而粒子效果需要进行排序。

以后打算使用HLSL Shader Model 6 的新指令(vote, ballot, shuffle)进行性能优化,性能可以更快。


在GPU上还有一种排序 Radix Sort(基数排序),Bullet物理引擎用的就是Radix Sort。与Bitonic Sort相比 速度会快一些,但是会占额外的内存空间(Bitonic Sort不需要,可以就地进行排序,Radix Sort需要2个Buffer,一个读一个写),浮点数比较需要处理(Bullet引擎的FlipFloat)。本人也用OpenCL实现了一个并进行了测试,包含7个Kernel。其中有ExclusiveScan,Histogram等并行计算常用算法。



=============

2019年1月20日更新:

二叉树,估计大家都知道是什么。

但在GPU上建造二叉树各位听说过吗?


Bullet物理引擎就用到了,用于射线相交检测的BVH,需要将AABB包围盒转换成Morton Code后排序,然后根据排序后的Morton Code建造二叉树。

具体原理请看

和论文 Maximizing Parallelism in the Construction of BVHs, Octrees,and k-d Trees (该链接里就有)


借用论文中的一张图:

如图

红色代表中间节点 绿色代表叶子节点。一共有8个叶子节点,7个中间节点。

水平的箭头代表二进制公共前缀的高度,如3-4之间就为0,0-1之间为3

让0为最高的节点,开始查找最高的公共前缀高度,找到3-4之间最高,于是0节点指向3,4节点

3节点 2-3公共前缀高度为4,3-4公共前缀高度为0,2-3比3-4低所以向左查找。查找比3-4高度低的最高的节点。

找到1-2之间公共前缀高度为2,最高。3节点指向1,2节点。

4节点 3-4公共前缀高度为0,4-5公共前缀高度为1,3-4比4-5高所以向右查找。查找比3-4高度低的最高的节点。

找到4-5之间公共前缀高度为1,最高。4节点指向4,5节点。由于4节点指向自身,因此指向叶子节点。

叶子节点间,中间节点间完全无依赖,可以完全并行的建造。

Bullet物理引擎使用的方法与上述有些不同。

Bullet物理引擎的做法是:

每个叶子节点,如果左边的公共前缀高度比右边大,那么就指向左边的中间节点,如果右边大就指向右边的。最左的节点 公共前缀高度最小,最右的节点公共前缀高度最大。

每个中间节点,分别从左右方向查找离该节点最近的公共前缀高度比该节点高的节点,如果找不到, 公共前缀高度为最小。

如果找到的左边的公共前缀高度比右边大,那么就指向左边的中间节点,如果右边大就指向右边的。

当时为了搞懂算法原理,我把运行时产生的数据输出成文本,才找出了其中的规律。

这就是Bullet物理引擎,一个自带的Demo,看看左下角有多少个立方体。

这个Demo中的拾取算法(射线-三角形相交)就用到了BVH,每一帧的拾取都会建造BVH。碰撞检测也可以用BVH,但这个Demo用的是SAP。

写本文时入职才2个月,第一份工作。

可以看看我的另一个回答:


//=====================

2019年1月27日更新:

看到评论中有些人用过CUDA,我就说一说CUDA 和DirectX12 做计算的区别,难度不是一个等级的:

CUDA分配Buffer 直接用 cudaMalloc 。DirectX12用CreateCommittedResource,再根据资源的使用方式创建view。

CUDA上传用cudaMemcpy,而DirectX12 有2种方法:第一种直接创建一个 upload buffer ,然后memcpy。第二种创建一个 default buffer,再创建一个upload buffer,先memcpy到upload buffer,再根据资源类型 CopyBufferRegion CopyTextureRegion 到default buffer。

Root Signature :shader的资源绑定布局,相当于函数参数的定义,这个CUDA没有。

Descriptor Heap: CUDA没有,存放 view的地方。相当于预先定义的一个数组,存放Buffer 的指针。

Resource Barrier:资源状态转换,CUDA没有。上传资源时upload buffer状态转换为 COPY_SOURCE ,default buffer状态转换为COPY_DEST。当参数使用时default buffer状态转换为SHADER_RESOURCE。

Command Queue Command List Command Allocator :所有的渲染命令如 Draw Dispatch Copy Clear 都要记录到 Command List中,而 Command List需要指定Command Allocator 。完成后Close,Command Queue 再ExecuteCommandList执行记录的命令。

Fence Signal :ExecuteCommandList 是非阻塞的,执行后立即返回。需要等待的话创建一个Fence, 然后Command Queue再Signal创建的Fence,Fence再Wait。

类似的话题

  • 回答
    游戏程序员的核心竞争力是一个多方面、高度整合的技能组合,它使得他们能够将创意设计转化为引人入胜、技术卓越的游戏体验。与许多其他软件开发领域不同,游戏开发对技术、艺术和用户体验有着独特的交织要求。下面我将从几个关键维度进行详细阐述:一、扎实而广泛的技术基础:这是所有程序员的基石,但在游戏领域尤为重要,.............
  • 回答
    问这个问题,其实是在探究游戏开发者在游戏内部拥有多大的权力,以及这种权力是否会被滥用。答案嘛,理论上来说,当然存在这种可能性,而且发生的可能性比普通玩家要大得多。你想想看,游戏程序员是游戏的核心技术人员,他们编写代码,构建游戏世界的运作规则。他们对游戏内部的数据结构、数据库的访问权限,以及能够直接操.............
  • 回答
    要说游戏程序员常逛的网站,那可真是五花八门,覆盖了从学习技术、解决问题到交流心得、了解行业动态的方方面面。这可不是一篇简简单单的“AI推荐”就能概括的,这里面藏着我们程序员们摸爬滚打、寻找灵感的无数个夜晚。一、 技术学习与资料库:扎实基本功的基石 官方文档与教程 (Official Docs &.............
  • 回答
    对于我这样的“代码创造者”来说,游戏可不仅仅是消遣,很多时候更是思维的体操,是解决问题能力的一种另类锻炼。我偏爱那些需要逻辑、策略、以及一点点钻研精神的游戏,它们能勾起我对算法、数据结构甚至是抽象数学的兴趣。我特别喜欢那些“沙盒”性质的游戏,就像一个空白的代码编辑器,你可以自由地构建、实验、甚至让它.............
  • 回答
    想象一下,你要盖一栋房子,但不是用砖头水泥,而是用“命令”和“规则”。程序员做的,就是用一种电脑能听懂的语言,给电脑下达一套又一套的命令,来告诉它该做什么。代码是怎么变成游戏的?这就像给电脑讲故事,但故事里的每个角色、每个动作、每个场景,都需要你一步一步、一个命令一个命令地去描述。1. 打下地基:.............
  • 回答
    在知乎前端圈,对于H5游戏和H5展示的JSer(这里的JSer可以理解为主要负责JavaScript开发的前端程序员)是否算作“前端工程师”,确实存在着一种普遍的,或者说是一种“约定俗成”的区分。这种区分并非是完全的否定,更多的是一种对“前端工程师”这个职业内涵的理解和侧重点的不同。要理解这个现象,.............
  • 回答
    天呐,听到这个消息,我简直要原地爆炸了。一个月的上线倒计时,就我们这一个小团队,你跟我说唯一的程序员要离职?这简直是晴天霹雳,而且还是连环响雷的那种。现在脑子里乱成一锅粥,但得赶紧冷静下来。离职原因是什么?是待遇问题?工作强度?还是对项目发展方向有异议?不管是什么,我们都得坐下来好好谈谈,看看有没有.............
  • 回答
    咱们聊聊游戏,特别是它那“多边形”的局限,以及算力这匹脱缰野马,能不能把这玩意儿带到个更离谱的境界,比如,跟现实来个“形神兼备”的复刻。首先,得承认,你看到的游戏画面,尤其是那些逼真得不像话的角色、场景,背后撑着它们的,是无数个三角形、四边形,也就是“多边形”。游戏引擎会把这些多边形堆叠、塑形,然后.............
  • 回答
    为了能顺利过审,游戏开发者们可谓是绞尽脑汁,做出了各种各样的妥协和调整。这些让步可不是简单的颜色修改,而是涉及到游戏核心玩法、剧情、美术风格乃至世界观的方方面面。下面我给你详细说说,这些让步能有多“绝”。一、 血腥暴力:从喷涌到飘散,从血红到五彩这是最直观也最常见的修改项。 血液表现的“柔化”:.............
  • 回答
    想了解游戏公司里对3D建模师的需求程度,咱们就得掰开了揉碎了聊聊。这可不是一个简单的“会建模就行”就能概括的,背后门道可多着呢。首先,得看是哪种类型的游戏公司。 大型3A工作室(比如育碧、EA、CD Projekt Red): 这种公司做的游戏,画面那是往死里卷,对3D建模的要求那叫一个高。他们.............
  • 回答
    那些闪耀着奇妙光辉的——“中二台词”嘿,哥们儿!你有没有过那么一段日子,觉得全世界都围绕着你转,觉得内心隐藏着一股能颠覆一切的力量,然后张口就来一些惊天地泣鬼神的句子?没错,我说的就是那些“中二程度爆表”的台词!它们仿佛自带BGM,从人物嘴里吐出来,瞬间就能让整个屏幕(或者说你脑海中的画面)都充满了.............
  • 回答
    《碧蓝航线》这款游戏嘛,说实话,它的氪金程度确实是个挺有意思的话题,可以从几个方面来聊聊。首先,咱们得明确一点,《碧蓝航线》绝对不是那种“不氪金就玩不了”的游戏。这是它最大的优点之一。游戏的肝度和策略性都挺高的,很多时候,你花时间去研究船只搭配、提升练度,比单纯砸钱效果来得更直接、更实在。很多强力的.............
  • 回答
    《原神》作为一款长线运营的游戏,角色的“剧情杀”一直是玩家们津津乐道的话题。虽然我们无法百分百确定谁会领便当,但结合剧情走向、角色定位以及游戏内的伏笔,倒也能推测出一些可能性较高的角色,以及他们“下线”的可能方式和程度。要分析这个问题,我们得从几个维度来看:一、 剧本与叙事需求: 推进主线剧情的.............
  • 回答
    嘿,最近刚面了一些应届游戏策划,感觉这届孩子们的水平确实挺参差不齐的,但也有不少让我眼前一亮的。说实话,现在行业竞争这么激烈,光有个“热爱”俩字已经不够看了,应届生要达到一个让公司觉得“靠谱、能带”,并且能快速上手的水准,我觉得得从几个维度来看。首先,基础功不能太虚。 对游戏品类和玩法要有足够了.............
  • 回答
    嘿,这个问题我太熟悉了!身边好多朋友做游戏开发,都会纠结是先 C++ 还是 C。 说实话,游戏程序员“必须”修 C 吗? 这个问题的答案,更像是“是否最方便、最主流”。如果你想进游戏行业,而且想快速上手、看到成果,那么 C 绝对是条非常顺畅的道路。 为什么这么说呢? 现在的游戏开发,尤其是独立游戏.............
  • 回答
    这问题挺有意思的,也挺现实的。尤其是当我们花钱买的游戏或者APP,结果里面一大堆问题,确实挺让人窝火的。这时候,很多人第一反应可能就是:“这程序员是怎么做的?也太不负责任了!” 甚至直接开骂,觉得他们技术不行,或者态度不好。为啥大家第一反应会想骂程序员?这也很容易理解。在我们消费者看来,游戏或APP.............
  • 回答
    老板跳出来说“程序员锁死服务器,导致项目600万打水漂”,这事儿,怎么说呢,听起来就够劲爆,也够狗血。作为旁观者,咱们得把这事儿拆开来看看,才能明白这背后可能藏着什么。首先,咱得捋清楚这“锁死服务器”到底是个啥概念。在咱们程序员的世界里,“锁死服务器”这个说法,一般有两种情况:1. 真正的技术故障.............
  • 回答
    作为一名游戏制作人,懂程序当然是极大的加分项,但并非绝对的必要条件。这就像问一个乐队的指挥是否一定要会演奏每一种乐器一样,答案是:会的话当然能更好地把握整体,但即便不会,只要他能清晰地传达乐曲的意图,理解乐器的特性,也能成为出色的指挥。游戏制作人这个角色,其核心在于将一个抽象的创意,一步步落地成为玩.............
  • 回答
    作为一名独立游戏制作人,你有没有想过,是不是一定要会写代码,才能真正做出属于自己的游戏?这个问题,相信不少怀揣游戏梦的朋友都曾纠结过。我作为一个亲身下海摸爬滚打过的独立制作人,想跟你掏心窝子地聊聊这个话题。答案其实是:不一定,但“懂”会让你事半功倍,甚至决定你的上限。我这么说,是有原因的。你可能不需.............
  • 回答
    吃鸡和CS这类FPS游戏,外挂泛滥确实是个让人头疼的问题。你说得对,这背后确实牵扯到游戏设计和技术漏洞。为啥FPS游戏这么容易被盯上?这事儿得从几个方面说起:1. 信息不对称是重灾区。 FPS游戏最核心的体验就是“看和打”。玩家在游戏里需要知道敌人在哪儿,知道自己有多少血,知道子弹有没有打中,这些.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有