求助:将2D天空着色器适配为3D并添加相机参数支持
Hey there! Great job getting that atmosphere shader running in GameMaker Studio 2. Let's tackle your question: yes, this shader is absolutely worth modifying—it's a solid base for atmospheric scattering, and adding camera up vector and FOV support is totally achievable with your intermediate shader skills. No need to swap to a new example; let's adapt this one.
Key Changes Needed
To add camera up vector and FOV support, we need to:
- Pass camera-specific data into the shader as uniforms
- Convert 2D screen coordinates into world-space 3D rays that respect your camera's FOV and orientation
- Update the ray origin to match your camera's position
Modified Shader Code
Here's the updated version with all the required changes, plus comments explaining what's new:
// New camera-related uniforms - set these from GameMaker Studio 2 uniform vec3 u_cameraPos; // Camera's world position uniform vec3 u_cameraUp; // Camera's up vector (normalized) uniform vec3 u_cameraForward; // Camera's forward look direction (normalized) uniform float u_cameraFOV; // Camera's field of view (in radians) uniform vec3 u_sunPosition; varying vec2 v_vTexcoord; varying vec4 v_vColour; varying vec3 v_vPosition; #define PI 3.141592 #define iSteps 16 #define jSteps 8 vec2 rsi(vec3 r0, vec3 rd, float sr) { // ray-sphere intersection(光线-球体相交),假设球体以原点为中心。当result.x > result.y时无相交 float a = dot(rd, rd); float b = 2.0 * dot(rd, r0); float c = dot(r0, r0) - (sr * sr); float d = (b*b) - 4.0*a*c; if (d < 0.0) return vec2(1e5,-1e5); return vec2( (-b - sqrt(d))/(2.0*a), (-b + sqrt(d))/(2.0*a) ); } vec3 atmosphere(vec3 r, vec3 r0, vec3 pSun, float iSun, float rPlanet, float rAtmos, vec3 kRlh, float kMie, float shRlh, float shMie, float g) { // 归一化太阳与视图方向。 pSun = normalize(pSun); r = normalize(r); // 计算主光线的步长。 vec2 p = rsi(r0, r, rAtmos); if (p.x > p.y) return vec3(0,0,0); p.y = min(p.y, rsi(r0, r, rPlanet).x); float iStepSize = (p.y - p.x) / float(iSteps); // 初始化主光线时间。 float iTime = 0.0; // 初始化瑞利散射与米氏散射的累加器。 vec3 totalRlh = vec3(0,0,0); vec3 totalMie = vec3(0,0,0); // 初始化主光线的光学深度累加器。 float iOdRlh = 0.0; float iOdMie = 0.0; // 计算瑞利散射与米氏散射的相位。 float mu = dot(r, pSun); float mumu = mu * mu; float gg = g * g; float pRlh = 3.0 / (16.0 * PI) * (1.0 + mumu); float pp = 1.0 + gg - 2.0 * mu * g; float pMie = 3.0 / (8.0 * PI) * ((1.0 - gg) * (mumu + 1.0)) / (sign(pp)*pow(abs(pp), 1.5) * (2.0 + gg)); // 采样主光线。 for (int i = 0; i < iSteps; i++) { // 计算主光线采样位置。 vec3 iPos = r0 + r * (iTime + iStepSize * 0.5); // 计算采样点高度。 float iHeight = length(iPos) - rPlanet; // 计算该步长下瑞利散射与米氏散射的光学深度。 float odStepRlh = exp(-iHeight / shRlh) * iStepSize; float odStepMie = exp(-iHeight / shMie) * iStepSize; // 累加光学深度。 iOdRlh += odStepRlh; iOdMie += odStepMie; // 计算次光线的步长。 float jStepSize = rsi(iPos, pSun, rAtmos).y / float(jSteps); // 初始化次光线时间。 float jTime = 0.0; // 初始化次光线的光学深度累加器。 float jOdRlh = 0.0; float jOdMie = 0.0; // 采样次光线。 for (int j = 0; j < jSteps; j++) { // 计算次光线采样位置。 vec3 jPos = iPos + pSun * (jTime + jStepSize * 0.5); // 计算采样点高度。 float jHeight = length(jPos) - rPlanet; // 累加光学深度。 jOdRlh += exp(-jHeight / shRlh) * jStepSize; jOdMie += exp(-jHeight / shMie) * jStepSize; // 递增次光线时间。 jTime += jStepSize; } // 计算衰减。 vec3 attn = exp(-(kMie * (iOdMie + jOdMie) + kRlh * (iOdRlh + jOdRlh))); // 累加散射值。 totalRlh += odStepRlh * attn; totalMie += odStepMie * attn; // 递增主光线时间。 iTime += iStepSize; } // 计算并返回最终颜色。 return iSun * (pRlh * kRlh * totalRlh + pMie * kMie * totalMie); } vec3 ACESFilm( vec3 x ) { float tA = 2.51; float tB = 0.03; float tC = 2.43; float tD = 0.59; float tE = 0.14; return clamp((x*(tA*x+tB))/(x*(tC*x+tD)+tE),0.0,1.0); } void main() { // Step 1: Convert 2D screen texcoord to normalized device coordinates (-1 to 1 range) vec2 ndc = v_vTexcoord * 2.0 - 1.0; // Step 2: Calculate camera right vector from forward and up vec3 cameraRight = normalize(cross(u_cameraForward, u_cameraUp)); // Step 3: Calculate aspect ratio (GameMaker's view width / view height) float aspect = 16.0 / 9.0; // Replace this with your actual view aspect ratio, or pass as a uniform // Step 4: Calculate ray direction in camera space, then convert to world space float fovScale = tan(u_cameraFOV * 0.5); vec3 rayDirCamera = vec3(ndc.x * fovScale * aspect, ndc.y * fovScale, 1.0); vec3 rayDirWorld = normalize(rayDirCamera.x * cameraRight + rayDirCamera.y * u_cameraUp + rayDirCamera.z * u_cameraForward); // Step 5: Use camera position as ray origin (adjusted to planet space - assuming planet is at origin) vec3 rayOrigin = u_cameraPos + vec3(0, 6372e3, 0); // Offset by planet radius to place camera above surface vec3 color = atmosphere( rayDirWorld, // Updated ray direction (world space, respects FOV/up) rayOrigin, // Updated ray origin (camera position) u_sunPosition, // 太阳位置 22.0, // 太阳强度 6371e3, // 行星半径(米) 6471e3, // 大气层半径(米) vec3(5.5e-6, 13.0e-6, 22.4e-6), // 瑞利散射系数 21e-6, // 米氏散射系数 8e3, // 瑞利标高 1.2e3, // 米氏标高 0.758 // 米氏散射偏好方向 ); // 应用曝光。 color = ACESFilm( color ); gl_FragColor = vec4(color, 1.0); }
Critical Explanations
- Camera Uniforms: You'll need to set these from GameMaker's GML code every frame. For example,
u_cameraPosis your camera's world position,u_cameraForwardis the direction it's looking (normalized),u_cameraUpis usually(0,1,0)for top-down (adjust if your camera is tilted), andu_cameraFOVis your camera's FOV in radians (convert from degrees withdeg2rad()). - Ray Direction Calculation: We convert the 2D texture coordinate to a 3D ray that matches your camera's perspective. Replace the hardcoded
16.0/9.0with your actual view aspect ratio, or pass it as another uniform for full flexibility. - Ray Origin: We offset the camera position by the planet's radius to ensure the camera is placed above the planet surface, matching the original shader's setup.
Is This Worth It?
Absolutely. This modification keeps the high-quality scattering effect you already have, while making it responsive to your 2D camera's movement, rotation, and FOV changes. For an intermediate shader developer, this is a great learning exercise, and the result will feel much more integrated with your GameMaker project.
内容的提问来源于stack exchange,提问作者Chris Legasse

