如何通过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计算距离:
- 更新片段着色器:
#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); }
- 在
Update函数中传递屏幕尺寸:
// 原有代码后添加 shader.setVec2("screenSize", glm::vec2{width, height});
方法2:将世界空间坐标转换到屏幕空间
把世界空间的pos转换为屏幕像素坐标,再与gl_FragCoord.xy比较:
- 更新片段着色器:
#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); }
- 同样在
Update函数中传递screenSize。
额外优化:让圆形居中于矩形内
如果需要圆形始终居中在矩形内,可以不再单独传递pos,而是通过model矩阵计算矩形的世界空间中心:
- 在片段着色器中添加
uniform mat4 model; - 计算矩形中心的世界空间坐标:
vec2 rectCenter = (model * vec4(0.5, 0.5, 0.0, 1.0)).xy;
- 使用
rectCenter替代原有的pos作为圆形中心。
内容的提问来源于stack exchange,提问作者J.Wennergren
相关产品推荐
相关产品推荐

