You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让3D场景中伤害数字始终朝向旋转的相机?

问题描述

实现了绕角色旋转的3D相机,同时用RenderNumber函数在角色上方绘制伤害数字,但伤害数字无法随相机旋转始终面向镜头,视角变化时可读性差。相关渲染代码如下:

RenderNumber 函数

void RenderNumber(vec3_t Position,int Num,vec3_t Color,float Alpha,float Scale)
{
    vec3_t p;
    VectorCopy(Position,p);
    vec3_t Light[4];
    VectorCopy(Color,Light[0]);
    VectorCopy(Color,Light[1]);
    VectorCopy(Color,Light[2]);
    VectorCopy(Color,Light[3]);
    if(Num == -1)
    {
        float UV[4][2];
        TEXCOORD(UV[0],0.f       ,32.f/32.f);
        TEXCOORD(UV[1],32.f/256.f,32.f/32.f);
        TEXCOORD(UV[2],32.f/256.f,17.f/32.f);
        TEXCOORD(UV[3],0.f       ,17.f/32.f);
        RenderSpriteUV(BITMAP_FONT+1,p,45,20,UV,Light,Alpha);
    }
    else if(Num == -2)
    {
        RenderSprite(BITMAP_FONT_HIT,p,32*Scale,20*Scale, Light[0], 0.f, 0.f, 0.f, 27.f/32.f, 15.f/16.f);   
    }
    else
    {
        char Text[32];
        itoa(Num,Text,10);
        p[0] -= strlen(Text)*5.f;
        unsigned int Length = strlen(Text);
        p[0] -= Length*Scale*0.125f;
        p[1] -= Length*Scale*0.125f;
        for(unsigned int i=0;i<Length;i++)
        {
            float UV[4][2];
            float u = (float)(Text[i]-48)*16.f/256.f;
            TEXCOORD(UV[0],u           ,16.f/32.f);
            TEXCOORD(UV[1],u+16.f/256.f,16.f/32.f);
            TEXCOORD(UV[2],u+16.f/256.f,0.f);
            TEXCOORD(UV[3],u           ,0.f);
            RenderSpriteUV(BITMAP_FONT+1,p,Scale,Scale,UV,Light,Alpha);
            p[0] += Scale*0.5f;
            p[1] += Scale*0.5f;
        }
    }
}

RenderSpriteUV 函数

void RenderSpriteUV(int Texture,vec3_t Position,float Width,float Height,float (*UV)[2],vec3_t Light[4],float Alpha)
{
    BindTexture(Texture);

    vec3_t p2;
    VectorTransform(Position,CameraMatrix,p2);
    float x = p2[0];
    float y = p2[1];
    float z = p2[2];
    
    Width  *= 0.5f;
    Height *= 0.5f;
    vec3_t p[4];
    Vector(x-Width, y-Height, z, p[0]);
    Vector(x+Width, y-Height, z, p[1]);
    Vector(x+Width, y+Height, z, p[2]);
    Vector(x-Width, y+Height, z, p[3]);

    glBegin(GL_QUADS);
    for(int i=0;i<4;i++)
    {
        glColor4f(Light[i][0],Light[i][1],Light[i][2],Alpha);
        glTexCoord2f(UV[i][0],UV[i][1]);
        glTexCoord3f(UV[i][0],UV[i][1],100.f);
        glVertex3fv(p[i]);
    }
    glEnd();
}

粒子创建与渲染函数

void CreatePoint(vec3_t Position,int Value,vec3_t Color,float scale, bool bMove, bool bRepeatedly)
{
    for(int i=0;i<MAX_POINTS;i++)
    {
        PARTICLE *o = &Points[i];
        if(!o->Live)
        {
            o->Live = true;
            o->Type = Value;
            VectorCopy(Position,o->Position);
            o->Position[2] += 140.f;
            VectorCopy(Color,o->Color);
            o->bRepeatedly = bRepeatedly;
            o->fRepeatedlyHeight = RequestTerrainHeight(o->Position[0], o->Position[1])+140.0f;
            o->Gravity  = 10.f;
            o->Scale    = scale;
            o->LifeTime = 0;
            o->bEnableMove = bMove;
            return;
        }
    }
}

void RenderPoints( BYTE byRenderOneMore )
{
    EnableAlphaTest();
    DisableDepthTest();
    for(int i=0;i<MAX_POINTS;i++)
    {
        PARTICLE *o = &Points[i];
        if(o->Live)
        {
            if ( byRenderOneMore==1 )
            {
                if ( o->Position[2]>350.f ) continue;
            }
            else if ( byRenderOneMore==2 )
            {
                if ( o->Position[2]<=300.f ) continue;
            }
            else if(o->bRepeatedly)
            {
                if( o->Position[2] <= o->fRepeatedlyHeight) continue;
            }
            RenderNumber(o->Position,o->Type,o->Color,o->Gravity*0.4f,o->Scale);
        }
    }
}

解决方案

核心思路是让伤害数字的四边形始终面向相机,通过相机的右向、上向向量构建世界空间的四边形,再转换到相机空间渲染。

1. 修改 RenderSpriteUV 函数

替换原实现,改为基于相机向量构建面向镜头的四边形:

void RenderSpriteUV(int Texture, vec3_t Position, float Width, float Height, float (*UV)[2], vec3_t Light[4], float Alpha)
{
    BindTexture(Texture);

    Width *= 0.5f;
    Height *= 0.5f;

    // 从相机视图矩阵提取世界空间的右向、上向向量
    vec3_t cameraRight, cameraUp;
    Vector(CameraMatrix[0][0], CameraMatrix[1][0], CameraMatrix[2][0], cameraRight);
    Vector(CameraMatrix[0][1], CameraMatrix[1][1], CameraMatrix[2][1], cameraUp);

    // 缩放向量到 sprite 尺寸
    VectorScale(cameraRight, Width, cameraRight);
    VectorScale(cameraUp, Height, cameraUp);

    // 计算四个顶点的世界空间位置
    vec3_t p[4];
    VectorSubtract(Position, cameraRight, p[0]); VectorSubtract(p[0], cameraUp, p[0]); // 左下
    VectorAdd(Position, cameraRight, p[1]); VectorSubtract(p[1], cameraUp, p[1]);     // 右下
    VectorAdd(Position, cameraRight, p[2]); VectorAdd(p[2], cameraUp, p[2]);         // 右上
    VectorSubtract(Position, cameraRight, p[3]); VectorAdd(p[3], cameraUp, p[3]);     // 左上

    // 转换到相机空间
    vec3_t pCam[4];
    for (int i = 0; i < 4; i++) {
        VectorTransform(p[i], CameraMatrix, pCam[i]);
    }

    glBegin(GL_QUADS);
    for(int i=0;i<4;i++)
    {
        glColor4f(Light[i][0], Light[i][1], Light[i][2], Alpha);
        glTexCoord2f(UV[i][0], UV[i][1]);
        glVertex3fv(pCam[i]);
    }
    glEnd();
}

2. 调整 RenderNumber 中的文字偏移逻辑

原偏移基于世界空间X轴,改为基于相机右向偏移,确保文字居中:

else
{
    char Text[32];
    itoa(Num, Text, 10);
    unsigned int Length = strlen(Text);
    
    // 获取相机右向向量,计算整体偏移让文字居中
    vec3_t cameraRight;
    Vector(CameraMatrix[0][0], CameraMatrix[1][0], CameraMatrix[2][0], cameraRight);
    VectorScale(cameraRight, -Length * Scale * 0.25f, cameraRight);
    
    vec3_t p;
    VectorCopy(Position, p);
    VectorAdd(p, cameraRight, p);
    
    for(unsigned int i=0;i<Length;i++)
    {
        float UV[4][2];
        float u = (float)(Text[i]-48)*16.f/256.f;
        TEXCOORD(UV[0],u           ,16.f/32.f);
        TEXCOORD(UV[1],u+16.f/256.f,16.f/32.f);
        TEXCOORD(UV[2],u+16.f/256.f,0.f);
        TEXCOORD(UV[3],u           ,0.f);
        RenderSpriteUV(BITMAP_FONT+1, p, Scale, Scale, UV, Light, Alpha);
        
        // 每个字符沿相机右向偏移
        vec3_t charOffset;
        Vector(CameraMatrix[0][0], CameraMatrix[1][0], CameraMatrix[2][0], charOffset);
        VectorScale(charOffset, Scale * 0.5f, charOffset);
        VectorAdd(p, charOffset, p);
    }
}

3. 同步修改 RenderSprite 函数

Num=-2分支调用的RenderSprite需做同逻辑修改,确保图标也面向相机:

// 示例修改后的 RenderSprite 函数
void RenderSprite(int Texture, vec3_t Position, float Width, float Height, vec3_t Color, float RotX, float RotY, float RotZ, float UOffset, float VOffset)
{
    BindTexture(Texture);

    Width *= 0.5f;
    Height *= 0.5f;

    vec3_t cameraRight, cameraUp;
    Vector(CameraMatrix[0][0], CameraMatrix[1][0], CameraMatrix[2][0], cameraRight);
    Vector(CameraMatrix[0][1], CameraMatrix[1][1], CameraMatrix[2][1], cameraUp);

    VectorScale(cameraRight, Width, cameraRight);
    VectorScale(cameraUp, Height, cameraUp);

    vec3_t p[4];
    VectorSubtract(Position, cameraRight, p[0]); VectorSubtract(p[0], cameraUp, p[0]);
    VectorAdd(Position, cameraRight, p[1]); VectorSubtract(p[1], cameraUp, p[1]);
    VectorAdd(Position, cameraRight, p[2]); VectorAdd(p[2], cameraUp, p[2]);
    VectorSubtract(Position, cameraRight, p[3]); VectorAdd(p[3], cameraUp, p[3]);

    vec3_t pCam[4];
    for (int i = 0; i < 4; i++) {
        VectorTransform(p[i], CameraMatrix, pCam[i]);
    }

    // 计算UV(根据原函数的UOffset、VOffset调整)
    float UV[4][2];
    float uStart = UOffset;
    float uEnd = UOffset + Width*2 / 256.f; // 假设纹理分辨率256,需根据实际调整
    float vStart = VOffset;
    float vEnd = VOffset + Height*2 / 32.f;

    TEXCOORD(UV[0], uStart, vEnd);
    TEXCOORD(UV[1], uEnd, vEnd);
    TEXCOORD(UV[2], uEnd, vStart);
    TEXCOORD(UV[3], uStart, vStart);

    glBegin(GL_QUADS);
    for(int i=0;i<4;i++)
    {
        glColor4f(Color[0], Color[1], Color[2], 1.0f);
        glTexCoord2f(UV[i][0], UV[i][1]);
        glVertex3fv(pCam[i]);
    }
    glEnd();
}

内容的提问来源于stack exchange,提问作者Josef Dvořák

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 22:15:49