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

OpenGL中顶点着色器直接向片元着色器传递变量的方法

跨中间透传着色器直接传递顶点变量到片元着色器的方案

嘿,这个问题我之前帮人解决过好几次了——其实核心就是让中间的TCS/TES/GS做“接力棒”,把顶点着色器的变量原封不动传下去,完全不用在这些阶段碰MVP或者法线计算。下面给你具体的实现方案:

1. 用接口块统一变量定义(减少重复代码)

首先,你可以把需要传递的所有顶点数据(比如世界空间位置、法线、UV等)封装成一个接口块,这样每个着色器都可以直接引用,不用重复声明变量,避免出错。

你可以把这个块放在一个共用的GLSL头文件里,然后在每个着色器里#include进去:

// common.glsl
struct VertexData {
    vec3 worldPos;  // 顶点着色器计算好的世界空间位置
    vec3 normal;    // 顶点着色器计算好的法线
    vec2 uv;        // 原始UV
};

2. 每个着色器阶段的透传实现

接下来,每个中间着色器只需要接收上一阶段的输出,原样赋值给本阶段的输出即可,完全不需要额外计算。

顶点着色器(VS)

在这里完成所有你需要的计算(比如MVP转换、法线转换),然后把结果输出到接口块:

#version 450
#include "common.glsl"

layout(location = 0) in vec3 inPos;
layout(location = 1) in vec3 inNormal;
layout(location = 2) in vec2 inUV;

uniform mat4 mvpMatrix;
uniform mat4 modelMatrix;

out VertexData vertOut;

void main() {
    // 所有计算都在这里完成,中间阶段不用碰
    vertOut.worldPos = (modelMatrix * vec4(inPos, 1.0)).xyz;
    vertOut.normal = normalize((modelMatrix * vec4(inNormal, 0.0)).xyz);
    vertOut.uv = inUV;
    
    // 计算裁剪空间位置,后续阶段透传
    gl_Position = mvpMatrix * vec4(inPos, 1.0);
}

细分控制着色器(TCS)

作为第一个中间阶段,接收VS的输出,原样传递给TES,同时设置细分因子(因为是透传,所以设为1.0,相当于不细分):

#version 450
#include "common.glsl"

layout(vertices = 3) out;

in VertexData tcsIn[];
out VertexData tcsOut[];

void main() {
    // 透传顶点数据
    tcsOut[gl_InvocationID] = tcsIn[gl_InvocationID];
    // 透传裁剪空间位置
    gl_Position = gl_in[gl_InvocationID].gl_Position;
    
    // 透传模式下,细分因子设为1,不改变拓扑
    gl_TessLevelOuter = vec4(1.0);
    gl_TessLevelInner = vec2(1.0);
}

细分评估着色器(TES)

接收TCS的输出,继续原样传递给GS:

#version 450
#include "common.glsl"

layout(triangles, equal_spacing) in;

in VertexData tesIn[];
out VertexData tesOut;

void main() {
    // 透传顶点数据
    tesOut = tesIn[gl_InvocationID];
    // 透传裁剪空间位置
    gl_Position = gl_in[gl_InvocationID].gl_Position;
}

几何着色器(GS)

接收TES的输出,逐个顶点透传给片元着色器:

#version 450
#include "common.glsl"

layout(triangles) in;
layout(triangle_strip, max_vertices = 3) out;

in VertexData gsIn[];
out VertexData gsOut;

void main() {
    // 遍历每个顶点,原样传递
    for(int i = 0; i < 3; i++) {
        gsOut = gsIn[i];
        gl_Position = gl_in[i].gl_Position;
        EmitVertex();
    }
    EndPrimitive();
}

片元着色器(FS)

直接接收GS传递过来的顶点数据,就像没有中间着色器一样使用:

#version 450
#include "common.glsl"

in VertexData fragIn;

layout(location = 0) out vec4 outColor;

void main() {
    // 直接使用来自顶点着色器的数据
    outColor = vec4(fragIn.normal * 0.5 + 0.5, 1.0); // 法线可视化示例
}

3. 关键注意事项

  • 插值控制:如果需要片元拿到平滑插值后的结果,你可以在接口块的输出上加上smooth qualifier,比如out smooth VertexData vertOut;,效果和直接从VS到FS的插值完全一致。
  • SPIR-V/现代API优化:如果用Vulkan等现代API,你还可以通过指定变量的location来更直接地绑定传递,但接口块的方式兼容性更好,在OpenGL和Vulkan里都能用。
  • 避免多余计算:中间阶段绝对不要修改这些变量,只做赋值透传,这样就完全符合你“不在GS做MVP和法线计算”的需求。

内容的提问来源于stack exchange,提问作者jovchaos

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 10:30:10