Valkyrie 图形编程与 Shader 开发
Valkyrie 语言原生支持图形编程,可以直接编写 shader 代码,无需依赖 GLSL 或 HLSL。通过内置的图形 API 和 wgpu 集成,Valkyrie 提供了现代化的 GPU 编程体验。
Valkyrie Shader 语言特性
内置图形类型
Valkyrie 提供了丰富的图形编程类型:
valkyrie
# 向量类型
type Vec2 = [f32; 2]
type Vec3 = [f32; 3]
type Vec4 = [f32; 4]
# 矩阵类型
type Mat2 = [[f32; 2]; 2]
type Mat3 = [[f32; 3]; 3]
type Mat4 = [[f32; 4]; 4]
# 纹理类型
type Texture2D = texture<f32, 2>
type TextureCube = texture<f32, cube>
type Sampler = sampler
# 颜色类型
type Color = Vec4 # RGBA
type RGB = Vec3Shader 函数标记
valkyrie
# Vertex Shader 标记
@vertex
micro vertex_main(vertex: VertexInput) -> VertexOutput {
# 顶点着色器逻辑
}
# Fragment Shader 标记
@fragment
micro fragment_main(input: FragmentInput) -> FragmentOutput {
# 片段着色器逻辑
}
# Compute Shader 标记
@compute(workgroup_size = [8, 8, 1])
micro compute_main(id: ComputeInput) {
# 计算着色器逻辑
}基础 Shader 示例
简单顶点着色器
valkyrie
# 顶点输入结构
structure VertexInput {
@location(0) position: Vec3,
@location(1) color: Vec3,
@location(2) uv: Vec2
}
# 顶点输出结构
structure VertexOutput {
@builtin(position) clip_position: Vec4,
@location(0) color: Vec3,
@location(1) uv: Vec2
}
# Uniform 缓冲区
@group(0) @binding(0)
structure CameraUniform {
view_proj: Mat4
}
@vertex
micro vertex_main(vertex: VertexInput, camera: CameraUniform) -> VertexOutput {
VertexOutput {
clip_position: camera.view_proj * Vec4(vertex.position, 1.0),
color: vertex.color,
uv: vertex.uv
}
}纹理片段着色器
valkyrie
# 片段输入(来自顶点着色器)
structure FragmentInput {
@location(0) color: Vec3,
@location(1) uv: Vec2
}
# 片段输出
structure FragmentOutput {
@location(0) color: Vec4
}
# 纹理和采样器
@group(1) @binding(0)
let texture: Texture2D
@group(1) @binding(1)
let sampler: Sampler
@fragment
micro fragment_main(input: FragmentInput) -> FragmentOutput {
let tex_color = texture.sample(sampler, input.uv)
let final_color = tex_color * Vec4(input.color, 1.0)
FragmentOutput {
color: final_color
}
}高级 Shader 技术
光照计算
valkyrie
# 光照数据结构
structure Light {
position: Vec3,
color: Vec3,
intensity: f32
}
# 材质属性
structure Material {
albedo: Vec3,
metallic: f32,
roughness: f32,
normal: Vec3
}
# Phong 光照模型
micro phong_lighting(material: Material, light: Light, view_dir: Vec3, light_dir: Vec3) -> Vec3 {
let normal = normalize(material.normal)
let light_dir = normalize(light_dir)
let view_dir = normalize(view_dir)
# 环境光
let ambient = 0.1 * material.albedo
# 漫反射
let diff = max(dot(normal, light_dir), 0.0)
let diffuse = diff * light.color * material.albedo
# 镜面反射
let reflect_dir = reflect(-light_dir, normal)
let spec = pow(max(dot(view_dir, reflect_dir), 0.0), 32.0)
let specular = spec * light.color
(ambient + diffuse + specular) * light.intensity
}
# PBR 光照模型
micro pbr_lighting(material: Material, light: Light, view_dir: Vec3, light_dir: Vec3) -> Vec3 {
let normal = normalize(material.normal)
let light_dir = normalize(light_dir)
let view_dir = normalize(view_dir)
let half_dir = normalize(light_dir + view_dir)
# 菲涅尔反射
let f0 = mix(Vec3(0.04), material.albedo, material.metallic)
let fresnel = fresnel_schlick(max(dot(half_dir, view_dir), 0.0), f0)
# 法线分布函数
let ndf = distribution_ggx(normal, half_dir, material.roughness)
# 几何函数
let geometry = geometry_smith(normal, view_dir, light_dir, material.roughness)
# Cook-Torrance BRDF
let numerator = ndf * geometry * fresnel
let denominator = 4.0 * max(dot(normal, view_dir), 0.0) * max(dot(normal, light_dir), 0.0) + 0.0001
let specular = numerator / denominator
# 能量守恒
let ks = fresnel
let kd = (Vec3(1.0) - ks) * (1.0 - material.metallic)
let ndotl = max(dot(normal, light_dir), 0.0)
(kd * material.albedo / PI + specular) * light.color * light.intensity * ndotl
}
# 辅助函数
micro fresnel_schlick(cos_theta: f32, f0: Vec3) -> Vec3 {
f0 + (Vec3(1.0) - f0) * pow(clamp(1.0 - cos_theta, 0.0, 1.0), 5.0)
}
micro distribution_ggx(normal: Vec3, half_dir: Vec3, roughness: f32) -> f32 {
let a = roughness * roughness
let a2 = a * a
let ndoth = max(dot(normal, half_dir), 0.0)
let ndoth2 = ndoth * ndoth
let num = a2
let denom = ndoth2 * (a2 - 1.0) + 1.0
num / (PI * denom * denom)
}
micro geometry_smith(normal: Vec3, view_dir: Vec3, light_dir: Vec3, roughness: f32) -> f32 {
let ndotv = max(dot(normal, view_dir), 0.0)
let ndotl = max(dot(normal, light_dir), 0.0)
let ggx2 = geometry_schlick_ggx(ndotv, roughness)
let ggx1 = geometry_schlick_ggx(ndotl, roughness)
ggx1 * ggx2
}
micro geometry_schlick_ggx(ndotv: f32, roughness: f32) -> f32 {
let r = roughness + 1.0
let k = (r * r) / 8.0
let num = ndotv
let denom = ndotv * (1.0 - k) + k
num / denom
}后处理效果
valkyrie
# 屏幕空间效果的片段着色器
@fragment
micro post_process_main(input: FragmentInput) -> FragmentOutput {
let uv = input.uv
let color = texture.sample(sampler, uv)
# 应用多种后处理效果
let processed = color
|> apply_bloom
|> apply_tone_mapping
|> apply_gamma_correction
|> apply_vignette(uv)
FragmentOutput { color: processed }
}
# Bloom 效果
micro apply_bloom(color: Vec4) -> Vec4 {
let brightness = dot(color.rgb, Vec3(0.2126, 0.7152, 0.0722))
if brightness > 1.0 {
color * (brightness - 1.0) * 0.5
} else {
color
}
}
# 色调映射
micro apply_tone_mapping(color: Vec4) -> Vec4 {
# Reinhard 色调映射
let mapped = color.rgb / (color.rgb + Vec3(1.0))
Vec4(mapped, color.a)
}
# 伽马校正
micro apply_gamma_correction(color: Vec4) -> Vec4 {
let gamma = 2.2
Vec4(pow(color.rgb, Vec3(1.0 / gamma)), color.a)
}
# 暗角效果
micro apply_vignette(color: Vec4, uv: Vec2) -> Vec4 {
let center = Vec2(0.5, 0.5)
let distance = length(uv - center)
let vignette = smoothstep(0.8, 0.2, distance)
Vec4(color.rgb * vignette, color.a)
}计算着色器
粒子系统
valkyrie
# 粒子数据结构
structure Particle {
position: Vec3,
velocity: Vec3,
life: f32,
size: f32
}
# 计算着色器输入
@group(0) @binding(0)
let particles: storage<array<Particle>, read_write>
@group(0) @binding(1)
structure SimulationParams {
delta_time: f32,
gravity: Vec3,
damping: f32
}
@compute(workgroup_size = [64, 1, 1])
micro update_particles(id: ComputeInput, params: SimulationParams) {
let index = id.global_invocation_id.x
if index >= particles.len() {
return
}
let mut particle = particles[index]
# 更新粒子物理
particle.velocity += params.gravity * params.delta_time
particle.velocity *= params.damping
particle.position += particle.velocity * params.delta_time
# 更新生命周期
particle.life -= params.delta_time
# 重置死亡粒子
if particle.life <= 0.0 {
particle = spawn_new_particle()
}
particles[index] = particle
}
micro spawn_new_particle() -> Particle {
Particle {
position: Vec3(0.0, 0.0, 0.0),
velocity: random_vec3() * 5.0,
life: 3.0,
size: 1.0
}
}GPU 加速的图像处理
valkyrie
# 图像卷积计算着色器
@group(0) @binding(0)
let input_texture: texture_2d<f32>
@group(0) @binding(1)
let output_texture: texture_storage_2d<rgba8unorm, write>
# 卷积核
const SOBEL_X: [[f32; 3]; 3] = [
[-1.0, 0.0, 1.0],
[-2.0, 0.0, 2.0],
[-1.0, 0.0, 1.0]
]
const SOBEL_Y: [[f32; 3]; 3] = [
[-1.0, -2.0, -1.0],
[ 0.0, 0.0, 0.0],
[ 1.0, 2.0, 1.0]
]
@compute(workgroup_size = [8, 8, 1])
micro edge_detection(id: ComputeInput) {
let coords = id.global_invocation_id.xy
let dimensions = textureDimensions(input_texture)
if coords.x >= dimensions.x || coords.y >= dimensions.y {
return
}
let mut gx = 0.0
let mut gy = 0.0
# 应用 Sobel 算子
for i in 0..3 {
for j in 0..3 {
let sample_coords = coords + Vec2(i - 1, j - 1)
let color = textureLoad(input_texture, sample_coords, 0)
let gray = dot(color.rgb, Vec3(0.299, 0.587, 0.114))
gx += gray * SOBEL_X[i][j]
gy += gray * SOBEL_Y[i][j]
}
}
let magnitude = sqrt(gx * gx + gy * gy)
let edge_color = Vec4(magnitude, magnitude, magnitude, 1.0)
textureStore(output_texture, coords, edge_color)
}wgpu 集成
渲染管线设置
valkyrie
# wgpu 设备和队列
structure GraphicsContext {
device: wgpu::Device,
queue: wgpu::Queue,
surface: wgpu::Surface,
config: wgpu::SurfaceConfiguration
}
# 渲染管线创建
micro create_render_pipeline(context: GraphicsContext) -> wgpu::RenderPipeline {
# 编译 Valkyrie shader 到 WGSL
let vertex_shader = compile_valkyrie_shader("
@vertex
vertex_main(vertex: VertexInput, camera: CameraUniform) -> VertexOutput {
VertexOutput {
clip_position: camera.view_proj * Vec4(vertex.position, 1.0),
color: vertex.color,
uv: vertex.uv
}
}
")
let fragment_shader = compile_valkyrie_shader("
@fragment
fragment_main(input: FragmentInput) -> FragmentOutput {
let tex_color = texture.sample(sampler, input.uv)
FragmentOutput { color: tex_color }
}
")
# 创建 shader 模块
let vs_module = context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Vertex Shader"),
source: wgpu::ShaderSource::Wgsl(vertex_shader.into())
})
let fs_module = context.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Fragment Shader"),
source: wgpu::ShaderSource::Wgsl(fragment_shader.into())
})
# 创建渲染管线
context.device.create_render_pipeline(wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(pipeline_layout),
vertex: wgpu::VertexState {
module: vs_module,
entry_point: "vertex_main",
buffers: vertex_buffer_layout
},
fragment: Some(wgpu::FragmentState {
module: fs_module,
entry_point: "fragment_main",
targets: color_target_states
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None
})
}资源管理
valkyrie
# 纹理加载和管理
class TextureManager {
textures: HashMap<String, wgpu::Texture>,
device: wgpu::Device,
queue: wgpu::Queue
new(device: wgpu::Device, queue: wgpu::Queue) -> TextureManager {
TextureManager {
textures: HashMap::new(),
device,
queue
}
}
load_texture(mut self, name: String, path: String) -> Result<(), Error> {
let image_data = load_image_from_file(path)?
let texture = self.device.create_texture(wgpu::TextureDescriptor {
label: Some(name.as_str()),
size: wgpu::Extent3d {
width: image_data.width,
height: image_data.height,
depth_or_array_layers: 1
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: []
})
self.queue.write_texture(
wgpu::ImageCopyTexture {
texture: texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All
},
image_data.data,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * image_data.width),
rows_per_image: Some(image_data.height)
},
texture.size()
)
self.textures.insert(name, texture)
Ok(())
}
get_texture(self, name: String) -> Option<wgpu::Texture> {
self.textures.get(name).cloned()
}
}高级图形技术
延迟渲染
valkyrie
# G-Buffer 结构
structure GBuffer {
albedo: wgpu::Texture, # RGB: 反照率, A: 金属度
normal: wgpu::Texture, # RGB: 世界空间法线
material: wgpu::Texture, # R: 粗糙度, G: AO, B: 自发光
depth: wgpu::Texture # 深度缓冲
}
# 几何阶段着色器
@fragment
micro geometry_pass_fragment(input: FragmentInput) -> GBufferOutput {
let albedo = texture_albedo.sample(sampler, input.uv)
let normal_map = texture_normal.sample(sampler, input.uv)
let material_props = texture_material.sample(sampler, input.uv)
# 计算世界空间法线
let world_normal = calculate_world_normal(input.normal, input.tangent, normal_map)
GBufferOutput {
albedo: Vec4(albedo.rgb, material_props.r), # 金属度存储在 alpha 通道
normal: Vec4(world_normal * 0.5 + 0.5, 1.0), # 法线编码到 [0,1] 范围
material: Vec4(material_props.g, material_props.b, material_props.a, 1.0)
}
}
# 光照阶段着色器
@fragment
micro lighting_pass_fragment(input: ScreenQuadInput) -> Vec4 {
let uv = input.uv
# 从 G-Buffer 读取数据
let albedo_metallic = g_buffer_albedo.sample(sampler, uv)
let encoded_normal = g_buffer_normal.sample(sampler, uv)
let material_data = g_buffer_material.sample(sampler, uv)
let depth = g_buffer_depth.sample(sampler, uv).r
# 重建世界位置
let world_pos = reconstruct_world_position(uv, depth, inv_view_proj)
# 解码法线
let world_normal = normalize(encoded_normal.xyz * 2.0 - 1.0)
# 材质属性
let albedo = albedo_metallic.rgb
let metallic = albedo_metallic.a
let roughness = material_data.r
let ao = material_data.g
# 计算光照
let view_dir = normalize(camera_pos - world_pos)
let mut final_color = Vec3(0.0)
# 处理所有光源
for light in lights {
let light_dir = normalize(light.position - world_pos)
let light_contribution = pbr_lighting(
Material { albedo, metallic, roughness, normal: world_normal },
light,
view_dir,
light_dir
)
final_color += light_contribution
}
# 应用环境遮蔽
final_color *= ao
Vec4(final_color, 1.0)
}阴影映射
valkyrie
# 阴影映射顶点着色器
@vertex
micro shadow_vertex_main(vertex: VertexInput, light_space: LightSpaceUniform) -> ShadowVertexOutput {
ShadowVertexOutput {
clip_position: light_space.light_space_matrix * Vec4(vertex.position, 1.0)
}
}
# 阴影映射片段着色器(深度写入)
@fragment
micro shadow_fragment_main(input: ShadowVertexOutput) {
# 只写入深度,不需要颜色输出
}
# 使用阴影的主渲染着色器
@fragment
micro main_fragment_with_shadow(input: FragmentInput) -> Vec4 {
let world_pos = input.world_position
let normal = normalize(input.normal)
# 转换到光空间
let light_space_pos = light_space_matrix * Vec4(world_pos, 1.0)
let proj_coords = light_space_pos.xyz / light_space_pos.w
let shadow_coords = proj_coords * 0.5 + 0.5
# 采样阴影贴图
let shadow_depth = shadow_map.sample(shadow_sampler, shadow_coords.xy).r
let current_depth = shadow_coords.z
# 阴影偏移以减少阴影痤疮
let bias = max(0.05 * (1.0 - dot(normal, light_dir)), 0.005)
let shadow = if current_depth - bias > shadow_depth { 0.0 } else { 1.0 }
# PCF 软阴影
let shadow_soft = pcf_shadow(shadow_map, shadow_coords, bias)
# 计算最终光照
let lighting = calculate_lighting(world_pos, normal)
let final_color = lighting * shadow_soft
Vec4(final_color, 1.0)
}
# PCF (Percentage Closer Filtering) 软阴影
micro pcf_shadow(shadow_map: Texture2D, shadow_coords: Vec3, bias: f32) -> f32 {
let texel_size = 1.0 / textureDimensions(shadow_map)
let mut shadow = 0.0
for x in -1..=1 {
for y in -1..=1 {
let offset = Vec2(x as f32, y as f32) * texel_size
let sample_coords = shadow_coords.xy + offset
let pcf_depth = shadow_map.sample(shadow_sampler, sample_coords).r
shadow += if shadow_coords.z - bias > pcf_depth { 0.0 } else { 1.0 }
}
}
shadow / 9.0
}性能优化
GPU 性能分析
valkyrie
# GPU 时间戳查询
class GPUProfiler {
query_set: wgpu::QuerySet,
query_buffer: wgpu::Buffer,
timestamps: [f64]
new(device: wgpu::Device) -> GPUProfiler {
let query_set = device.create_query_set(wgpu::QuerySetDescriptor {
label: Some("Timestamp Queries"),
ty: wgpu::QueryType::Timestamp,
count: 32
})
let query_buffer = device.create_buffer(wgpu::BufferDescriptor {
label: Some("Query Buffer"),
size: 32 * 8, # 32 queries * 8 bytes per timestamp
usage: wgpu::BufferUsages::QUERY_RESOLVE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false
})
GPUProfiler {
query_set,
query_buffer,
timestamps: []
}
}
begin_pass(self, encoder: wgpu::CommandEncoder, label: String) {
encoder.write_timestamp(self.query_set, self.timestamps.len() as u32)
self.timestamps.push(0.0) # 占位符
}
end_pass(self, encoder: wgpu::CommandEncoder) {
encoder.write_timestamp(self.query_set, self.timestamps.len() as u32)
self.timestamps.push(0.0) # 占位符
}
resolve_queries(self, encoder: wgpu::CommandEncoder) {
encoder.resolve_query_set(
self.query_set,
0..self.timestamps.len() as u32,
self.query_buffer,
0
)
}
}批处理和实例化
valkyrie
# 实例化渲染数据
structure InstanceData {
model_matrix: Mat4,
color: Vec4
}
# 实例化顶点着色器
@vertex
micro instanced_vertex_main(
vertex: VertexInput,
instance: InstanceData,
camera: CameraUniform
) -> VertexOutput {
let world_position = instance.model_matrix * Vec4(vertex.position, 1.0)
VertexOutput {
clip_position: camera.view_proj * world_position,
world_position: world_position.xyz,
color: vertex.color * instance.color,
uv: vertex.uv
}
}
# 批处理管理器
class BatchRenderer {
instance_buffer: wgpu::Buffer,
instance_data: [InstanceData],
max_instances: usize
new(device: wgpu::Device, max_instances: usize) -> BatchRenderer {
let instance_buffer = device.create_buffer(wgpu::BufferDescriptor {
label: Some("Instance Buffer"),
size: (max_instances * size_of::<InstanceData>()) as u64,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false
})
BatchRenderer {
instance_buffer,
instance_data: Vec::with_capacity(max_instances),
max_instances
}
}
add_instance(mut self, transform: Mat4, color: Vec4) {
if self.instance_data.len() < self.max_instances {
self.instance_data.push(InstanceData {
model_matrix: transform,
color
})
}
}
flush(mut self, queue: wgpu::Queue) {
if !self.instance_data.is_empty() {
queue.write_buffer(
self.instance_buffer,
0,
bytemuck::cast_slice(self.instance_data.as_slice())
)
}
}
render(self, render_pass: wgpu::RenderPass) {
render_pass.set_vertex_buffer(1, self.instance_buffer.slice(..))
render_pass.draw_indexed(0..index_count, 0, 0..self.instance_data.len() as u32)
self.instance_data.clear()
}
}调试和工具
Shader 调试
valkyrie
# 调试输出宏
macro debug_color(color: Vec3) {
# 在调试模式下输出颜色到特殊缓冲区
↯[cfg(debug)]
debug_output.color = Vec4(color, 1.0)
}
macro debug_value(name: String, value: f32) {
# 在调试模式下输出数值
↯[cfg(debug)]
debug_output.values[name] = value
}
# 带调试信息的片段着色器
@fragment
micro debug_fragment_main(input: FragmentInput) -> FragmentOutput {
let uv = input.uv
let color = texture.sample(sampler, uv)
# 调试 UV 坐标
debug_color(Vec3(uv, 0.0))
debug_value("brightness", dot(color.rgb, Vec3(0.299, 0.587, 0.114)))
# 可视化法线
if debug_mode == DebugMode::Normals {
return FragmentOutput {
color: Vec4(input.normal * 0.5 + 0.5, 1.0)
}
}
# 可视化深度
if debug_mode == DebugMode::Depth {
let depth = linearize_depth(input.depth)
return FragmentOutput {
color: Vec4(depth, depth, depth, 1.0)
}
}
FragmentOutput { color }
}热重载支持
valkyrie
# Shader 热重载管理器
class ShaderHotReload {
shader_files: HashMap<String, FileWatcher>,
pipelines: HashMap<String, wgpu::RenderPipeline>,
device: wgpu::Device
new(device: wgpu::Device) -> ShaderHotReload {
ShaderHotReload {
shader_files: HashMap::new(),
pipelines: HashMap::new(),
device
}
}
watch_shader(mut self, name: String, path: String) {
let watcher = FileWatcher::new(path, || {
self.reload_shader(name.clone())
})
self.shader_files.insert(name, watcher)
}
reload_shader(mut self, name: String) {
try {
let shader_source = read_file(self.shader_files[name].path)?
let compiled_shader = compile_valkyrie_shader(shader_source)?
# 重新创建管线
let new_pipeline = create_pipeline_from_shader(compiled_shader)
self.pipelines.insert(name.clone(), new_pipeline)
print("Shader '${name}' 重载成功")
}
.catch {
case e:
print("Shader '${name}' 重载失败: ${e}")
}
}
}总结
Valkyrie 语言提供了完整的图形编程解决方案:
- 原生 Shader 支持 - 无需学习 GLSL/HLSL,直接用 Valkyrie 编写 shader
- 现代图形 API - 完整的 wgpu 集成和现代渲染管线支持
- 高级图形技术 - PBR、延迟渲染、阴影映射等现代渲染技术
- 性能优化 - 批处理、实例化、GPU 性能分析等优化工具
- 开发体验 - 热重载、调试工具、类型安全的 GPU 编程
通过 Valkyrie 的图形编程能力,开发者可以构建高性能的游戏和图形应用,同时享受现代编程语言的所有优势。