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

如何通过View矩阵移动SDF绘制的圆形?绑定矩形可动但圆形不动

问题:SDF绘制的圆形无法随View矩阵移动

我用SDF(有符号距离函数)绘制圆形,目前能定位圆形但无法通过View矩阵移动它。绑定的矩形可以正常移动,但圆形不会跟着移动,尝试将圆形居中到矩形内也没有效果,其他SDF绘制的图形也存在同样问题。

相关代码

Circle类实现

#include <glew.h>
#include "circle.h"

Circle::Circle()
{
    const std::string vertexShaderSource = R"END(

        #version 330 core
        layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>

        uniform mat4 model;
        uniform mat4 projection;
        uniform mat4 view;

        void main()
        {
            gl_Position = projection * view * model * vec4(vertex.xy, 0.0, 1.0);
        }

        )END";

    const std::string fragmentShaderSource = R"END(

        #version 330 core

        out vec4 color;
        uniform vec2 dim;
        uniform vec2 pos;

        /**
         * Convert r, g, b to normalized vec3
         */
        vec3 rgb(float r, float g, float b) {
            return vec3(r / 255.0, g / 255.0, b / 255.0);
        }

        /**
         * Draw a circle at vec2 `pos` with radius `rad` and
         * color `color`.
         */
        vec4 circle(vec2 uv, vec2 pos, float rad, vec3 color) {
            float d = length(pos - uv) - rad;
            float t = clamp(d, 0.0, 1.0);
            return vec4(color, 1.0 - t);
        }

        void main() {

            vec2 uv = gl_FragCoord.xy;
            float radius = 60;

            vec2 center = pos;

            // Background layer
            vec4 layer1 = vec4(rgb(210.0, 222.0, 228.0), 0.3);
            
            // Circle
            vec3 red = rgb(225.0, 95.0, 60.0);
            vec4 layer2 = circle(uv, center, radius, red);
            
            // Blend the two
            color = mix(layer1, layer2, layer2.a);
        }

        )END";

    shader.Compile(vertexShaderSource.c_str(), fragmentShaderSource.c_str());

    // Configure VAO etc..
}

void Circle::Update(float deltaTime, int width, int height)
{
    // Activate shader
    shader.Activate();

    auto camera = Services::Get<RenderService>()->camera;

    glm::mat4 model = glm::mat4(1.0f);
    glm::mat4 view;

    if (skipPan == false)
    {
        view = camera.GetViewMatrix();
    }
    else
    {
        view = glm::mat4{1.0f};
    }

    projection = glm::ortho(0.0f, static_cast<float>(width),
                            static_cast<float>(height), 0.0f, -1.0f, 1.0f);

    model = glm::translate(model, glm::vec3(transform.position.x, transform.position.y, 0.0f));
    model = glm::scale(model, glm::vec3(transform.dimension.width, transform.dimension.height, 1.0f));

    shader.setMat4("projection", projection);
    shader.setMat4("model", model);
    shader.setMat4("view", view);
    shader.setVec2("dim", glm::vec2{transform.dimension.width, transform.dimension.height});
    shader.setVec2("pos", glm::vec2{transform.position.x, transform.position.y});
    shader.setVec4("spriteColor", glm::vec4{style.Background.r, style.Background.g, style.Background.b, style.Background.a});

    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

VAO/VBO配置代码

// configure VAO/VBO
const float vertices[] = {
    0.0f, 1.0f, 0.0f,
    1.0f, 0.0f, 1.0f,
    0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f,
    1.0f, 1.0f, 1.0f,
    1.0f, 0.0f, 1.0f};

glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);

glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBindVertexArray(vao);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

问题现象

初始状态:
初始状态

向右平移相机后:
向右平移后

问题根源

矩形的顶点通过顶点着色器中的projection * view * model矩阵变换,正确转换到屏幕空间,因此能随相机移动;但SDF圆形的计算直接使用屏幕像素坐标(gl_FragCoord.xy)和世界空间坐标(pos),两者未经过统一的View/Projection变换,导致相机移动时圆形的屏幕位置无法同步更新。

解决方案

需要将SDF计算的两个坐标统一到同一空间,以下是两种可行方法:

方法1:将屏幕坐标转换到世界空间

修改片段着色器,把gl_FragCoord转换为世界空间坐标,再与世界空间的pos计算距离:

  1. 更新片段着色器:
#version 330 core

out vec4 color;
uniform vec2 dim;
uniform vec2 pos;
uniform mat4 projection;
uniform mat4 view;
uniform vec2 screenSize; // 新增:屏幕宽高

vec3 rgb(float r, float g, float b) {
    return vec3(r / 255.0, g / 255.0, b / 255.0);
}

vec4 circle(vec2 uv, vec2 pos, float rad, vec3 color) {
    float d = length(pos - uv) - rad;
    float t = clamp(d, 0.0, 1.0);
    return vec4(color, 1.0 - t);
}

void main() {
    // 将屏幕坐标转换为世界空间
    vec2 ndc = (gl_FragCoord.xy / screenSize) * 2.0 - 1.0;
    ndc.y = -ndc.y; // 修正正交投影的y轴方向

    vec4 clipPos = vec4(ndc, 0.0, 1.0);
    vec4 viewPos = inverse(projection) * clipPos;
    viewPos /= viewPos.w; // 透视除法(正交投影可省略,但保留通用性)
    vec4 worldPos = inverse(view) * viewPos;

    float radius = 60;

    // Background layer
    vec4 layer1 = vec4(rgb(210.0, 222.0, 228.0), 0.3);
    
    // Circle:使用世界空间坐标计算
    vec3 red = rgb(225.0, 95.0, 60.0);
    vec4 layer2 = circle(worldPos.xy, pos, radius, red);
    
    color = mix(layer1, layer2, layer2.a);
}
  1. 在Update函数中传递屏幕尺寸:
// 原有代码后添加
shader.setVec2("screenSize", glm::vec2{width, height});

方法2:将世界空间坐标转换到屏幕空间

把世界空间的pos转换为屏幕像素坐标,再与gl_FragCoord.xy比较:

  1. 更新片段着色器:
#version 330 core

out vec4 color;
uniform vec2 dim;
uniform vec2 pos;
uniform mat4 projection;
uniform mat4 view;
uniform vec2 screenSize; // 新增:屏幕宽高

vec3 rgb(float r, float g, float b) {
    return vec3(r / 255.0, g / 255.0, b / 255.0);
}

vec4 circle(vec2 uv, vec2 pos, float rad, vec3 color) {
    float d = length(pos - uv) - rad;
    float t = clamp(d, 0.0, 1.0);
    return vec4(color, 1.0 - t);
}

void main() {
    // 将世界空间的pos转换为屏幕像素坐标
    vec4 screenPos = projection * view * vec4(pos, 0.0, 1.0);
    screenPos.xy /= screenPos.w; // 透视除法
    // 转换到屏幕像素范围
    screenPos.xy = (screenPos.xy * 0.5 + 0.5) * screenSize;
    // 修正y轴方向
    screenPos.y = screenSize.y - screenPos.y;

    float radius = 60;

    // Background layer
    vec4 layer1 = vec4(rgb(210.0, 222.0, 228.0), 0.3);
    
    // Circle:使用屏幕空间坐标计算
    vec3 red = rgb(225.0, 95.0, 60.0);
    vec4 layer2 = circle(gl_FragCoord.xy, screenPos.xy, radius, red);
    
    color = mix(layer1, layer2, layer2.a);
}
  1. 同样在Update函数中传递screenSize。

额外优化:让圆形居中于矩形内

如果需要圆形始终居中在矩形内,可以不再单独传递pos,而是通过model矩阵计算矩形的世界空间中心:

  1. 在片段着色器中添加uniform mat4 model;
  2. 计算矩形中心的世界空间坐标:
vec2 rectCenter = (model * vec4(0.5, 0.5, 0.0, 1.0)).xy;
  1. 使用rectCenter替代原有的pos作为圆形中心。

内容的提问来源于stack exchange,提问作者J.Wennergren

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 08:22:49