一键导入
forge-gobo-spotlight
Add a projected-texture (gobo/cookie) spotlight with cone falloff, shadow map, and pattern projection to an SDL GPU project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Add a projected-texture (gobo/cookie) spotlight with cone falloff, shadow map, and pattern projection to an SDL GPU project
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | forge-gobo-spotlight |
| description | Add a projected-texture (gobo/cookie) spotlight with cone falloff, shadow map, and pattern projection to an SDL GPU project |
Add a spotlight with inner/outer cone angles, projected gobo texture, and a single 2D shadow map. The spotlight's view-projection matrix serves triple duty — shadow mapping, gobo UV projection, and cone masking. Based on GPU Lesson 24.
SDL_CreateGPUTexture — shadow depth map (D32_FLOAT, 2D, SAMPLER | DEPTH_STENCIL_TARGET)SDL_CreateGPUTexture — gobo texture (R8G8B8A8_UNORM, 2D, SAMPLER)SDL_CreateGPUSampler — NEAREST + CLAMP for shadow, LINEAR + CLAMP for goboSDL_CreateGPUGraphicsPipeline — shadow pipeline (depth-only, no color targets)SDL_BeginGPURenderPass — shadow pass with depth-only attachmentSDL_BindGPUFragmentSamplers — bind diffuse + shadow + gobo textures in scene passmat4_look_at(pos, target, up) + mat4_perspective(2 * outer_angle, 1.0, near, far)num_samplers to include shadow + gobo (e.g. 3 total)smoothstep(cos_outer, cos_inner, cos_angle) — cosine decreases with angle, so outer < innerstep() functionsR8G8B8A8_UNORM.smoothstep(cos_outer, cos_inner, ...) with outer < inner. Swapping them inverts the cone.gobo_uv.y = 1.0 - gobo_uv.y.2 * outer_angle to cover the full spotlight cone. Too narrow clips shadows; too wide wastes resolution.#define SPOT_INNER_DEG 20.0f /* full-intensity inner cone half-angle */
#define SPOT_OUTER_DEG 30.0f /* falloff-to-zero outer cone half-angle */
#define SPOT_INTENSITY 5.0f /* HDR brightness */
#define SPOT_NEAR 0.5f
#define SPOT_FAR 30.0f
#define SHADOW_MAP_SIZE 1024
/* Compute spotlight view-projection (static light). */
vec3 spot_pos = vec3_create(6.0f, 5.0f, 4.0f);
vec3 spot_target = vec3_create(0.0f, 0.0f, 0.0f);
vec3 spot_up = vec3_create(0.0f, 1.0f, 0.0f);
mat4 light_view = mat4_look_at(spot_pos, spot_target, spot_up);
float outer_rad = SPOT_OUTER_DEG * FORGE_DEG2RAD;
mat4 light_proj = mat4_perspective(2.0f * outer_rad, 1.0f, SPOT_NEAR, SPOT_FAR);
mat4 light_vp = mat4_multiply(light_proj, light_view);
vec3 spot_dir = vec3_normalize(vec3_sub(spot_target, spot_pos));
SDL_GPUTextureCreateInfo ti;
SDL_zero(ti);
ti.type = SDL_GPU_TEXTURETYPE_2D;
ti.format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
ti.width = SHADOW_MAP_SIZE;
ti.height = SHADOW_MAP_SIZE;
ti.layer_count_or_depth = 1;
ti.num_levels = 1;
ti.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER
| SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
SDL_GPUTexture *shadow_depth = SDL_CreateGPUTexture(device, &ti);
/* Use UNORM (not sRGB) — the gobo is a linear attenuation mask. */
SDL_GPUTextureCreateInfo tex_info;
SDL_zero(tex_info);
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
tex_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
tex_info.width = w;
tex_info.height = h;
tex_info.layer_count_or_depth = 1;
tex_info.num_levels = 1;
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
/* Gobo sampler — linear filtering for smooth projection, clamp to edge. */
SDL_GPUSamplerCreateInfo si;
SDL_zero(si);
si.min_filter = SDL_GPU_FILTER_LINEAR;
si.mag_filter = SDL_GPU_FILTER_LINEAR;
si.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
si.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
si.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
/* Direction from spotlight to fragment. */
float3 to_frag = world_pos - spot_pos;
float dist = length(to_frag);
float3 L_frag = to_frag / dist;
/* Cone falloff. */
float cos_angle = dot(L_frag, normalize(spot_dir));
float cone = smoothstep(cos_outer, cos_inner, cos_angle);
if (cone > 0.0)
{
/* Transform fragment into spotlight clip space. */
float4 light_clip = mul(light_vp, float4(world_pos, 1.0));
float3 light_ndc = light_clip.xyz / light_clip.w;
/* Gobo texture projection — remap NDC to UV space. */
float2 gobo_uv = light_ndc.xy * 0.5 + 0.5;
gobo_uv.y = 1.0 - gobo_uv.y;
/* Cone masking — clamp to spotlight bounds. */
float in_bounds = step(0.0, gobo_uv.x) * step(gobo_uv.x, 1.0) *
step(0.0, gobo_uv.y) * step(gobo_uv.y, 1.0);
/* Sample gobo pattern (grayscale). */
float gobo = gobo_tex.Sample(gobo_smp, gobo_uv).r;
/* Shadow test with PCF. */
float shadow = sample_shadow(light_ndc, texel_size);
/* Blinn-Phong from spotlight. */
float3 L = normalize(spot_pos - world_pos);
float NdotL = max(dot(N, L), 0.0);
float3 H = normalize(L + V);
float NdotH = max(dot(N, H), 0.0);
/* Quadratic attenuation. */
float atten = 1.0 / (1.0 + 0.09 * dist + 0.032 * dist * dist);
total_light += (albedo * NdotL + spec) * cone * gobo * shadow *
in_bounds * atten * spot_intensity * spot_color;
}
/* Shadow pipeline: no color targets, depth-only. */
SDL_GPUDepthStencilTargetInfo shadow_depth_info;
SDL_zero(shadow_depth_info);
shadow_depth_info.texture = shadow_depth_texture;
shadow_depth_info.load_op = SDL_GPU_LOADOP_CLEAR;
shadow_depth_info.store_op = SDL_GPU_STOREOP_STORE;
shadow_depth_info.clear_depth = 1.0f;
SDL_GPURenderPass *shadow_pass = SDL_BeginGPURenderPass(
cmd, NULL, 0, &shadow_depth_info);
SDL_BindGPUGraphicsPipeline(shadow_pass, shadow_pipeline);
/* Draw shadow casters (exclude the light source model). */
draw_model_shadow(shadow_pass, cmd, &truck, &truck_mat, &light_vp);
for (int i = 0; i < BOX_COUNT; i++) {
draw_model_shadow(shadow_pass, cmd, &box, &box_mat[i], &light_vp);
}
SDL_EndGPURenderPass(shadow_pass);
/* Bind 3 samplers: diffuse, shadow depth, gobo pattern. */
SDL_GPUTextureSamplerBinding tex_binds[3];
tex_binds[0] = (SDL_GPUTextureSamplerBinding){
.texture = diffuse_tex, .sampler = sampler };
tex_binds[1] = (SDL_GPUTextureSamplerBinding){
.texture = shadow_depth_texture, .sampler = shadow_sampler };
tex_binds[2] = (SDL_GPUTextureSamplerBinding){
.texture = gobo_texture, .sampler = gobo_sampler };
SDL_BindGPUFragmentSamplers(pass, 0, tex_binds, 3);
Add PBR texture loading with separate roughness/metallic support to an SDL GPU project using forge_scene.h
Add Cook-Torrance PBR shading with GGX, Schlick-GGX, and Schlick Fresnel to an SDL GPU project alongside forge_scene.h
Add instanced grass rendering with segmented blades, wind animation, LOD density rings, and terrain LOD with geomorphing to an SDL GPU project
Add procedural noise texture generation to the asset pipeline — C tool, Python plugin, web UI with live preview
Add an asset pipeline lesson — hybrid Python + C track for asset processing, procedural geometry, and web frontend
Add an audio lesson — sound playback, mixing, spatial audio, DSP effects, with SDL GPU scenes and forge UI