【光影】使彩色光能穿过小缝隙

前言

使用光影是相对最完美的实现方案,不过需要花点功夫研究别人的着色器代码。在本文之前,博主已经实现了选定某个发光方块(我使用了哭泣的黑曜石)作为彩色变化光源的载体,并尝试出使光照颜色随时间(其实是iris提供的sunAngle变量)变化,和随空间位置得到相应的rgb色值的办法。在前文《MC还原广州塔灯光的设想、截至目前的所有尝试》提到【光影】方法存在穿透非完整方块效果欠佳的问题,在本篇文章中将会得到有效解决🥳🥳🥳

问题的关键

Photon Shaders的彩色光照是体素化的,那么出现光照穿透非完整方块的关键就在于着色器是怎么判断某个方块是完整方块/非完整方块/透明方块等类型

且看源码
#if !defined INCLUDE_LIGHTING_LPV_VOXELIZATION
#define INCLUDE_LIGHTING_LPV_VOXELIZATION

const ivec3 voxel_volume_size = ivec3(VOXEL_VOLUME_SIZE);

#ifdef COLORED_LIGHTS
const float voxelDistance = 32.0;
#endif

vec3 get_voxel_volume_center(vec3 look_direction) {
#if   VOXEL_VOLUME_CENTER == VOXEL_VOLUME_CENTER_AHEAD
	// Center the voxel volume in front of the player
	// Returns the integer offsets towards the center from the scene space origin

	// Fraction of the voxel volume size that is behind the player
	const float voxelization_fraction_behind_player = 0.15; // blocks

	return floor(look_direction * voxel_volume_size * (0.5 - voxelization_fraction_behind_player) * rcp(max_of(abs(look_direction))));
#else 
	// Voxel volume is centered on the player (origin in scene space)
	return vec3(0.0);
#endif
}

vec3 scene_to_voxel_space(vec3 scene_pos) {
	vec3 to_center = get_voxel_volume_center(gbufferModelViewInverse[2].xyz);
	return scene_pos + fract(cameraPosition) + (0.5 * vec3(voxel_volume_size)) + to_center;
}

vec3 voxel_to_scene_space(vec3 voxel_pos) {
	vec3 to_center = get_voxel_volume_center(gbufferModelViewInverse[2].xyz);
	return voxel_pos - fract(cameraPosition) - (0.5 * vec3(voxel_volume_size)) - to_center;
}

bool is_inside_voxel_volume(vec3 voxel_pos) {
	voxel_pos *= rcp(vec3(voxel_volume_size));
	return clamp01(voxel_pos) == voxel_pos;
}

#ifdef PROGRAM_SHADOW
bool is_voxelized(uint block_id, bool vertex_at_grid_corner) {
	bool is_terrain = any(equal(ivec4(renderStage), ivec4(MC_RENDER_STAGE_TERRAIN_SOLID, MC_RENDER_STAGE_TERRAIN_TRANSLUCENT, MC_RENDER_STAGE_TERRAIN_CUTOUT, MC_RENDER_STAGE_TERRAIN_CUTOUT_MIPPED)));

	bool is_transparent_block =
		block_id == 1u  || // Water
	    block_id == 18u || // Transparent metal objects
	    block_id == 80u;   // Miscellaneous transparent
	
	bool is_light_emitting_block = 32u <= block_id && block_id < 64u;

	return (vertex_at_grid_corner || is_light_emitting_block) && is_terrain && !is_transparent_block;
}

bvec3 disjunction(bvec3 a, bvec3 b) {
	// a || b compiles on Nvidia but apparently not with other vendors
	return bvec3(
		a.x || b.x,
		a.y || b.y,
		a.z || b.z
	);
}

// Returns true if pos is within `tolerance` of a corner of the unit cube
bool is_corner(vec3 pos, float tolerance) {
	return all(disjunction(lessThan(pos, vec3(tolerance)), greaterThan(pos, vec3(1.0 - tolerance))));
}

void update_voxel_map(uint block_id) {
	vec3 model_pos = gl_Vertex.xyz + at_midBlock * rcp(64.0);
	vec3 view_pos  = transform(gl_ModelViewMatrix, model_pos);
	vec3 scene_pos = transform(shadowModelViewInverse, view_pos);
	vec3 voxel_pos = scene_to_voxel_space(scene_pos);

	// Work out whether this vertex is in the lower corner of the block grid
	vec3 block_pos = transform(gl_ModelViewMatrix, gl_Vertex.xyz);
	     block_pos = transform(shadowModelViewInverse, block_pos);
		 block_pos = fract(block_pos + cameraPosition);
	bool vertex_at_grid_corner = is_corner(block_pos, rcp(16.0) - 1e-3);

	bool is_voxelized = is_voxelized(block_id, vertex_at_grid_corner);

	// Prevent blocks that aren't part of another category in shaders.properties from being treated as air
	block_id = max(block_id, 1u);

	// Warped and crimson stem emission
	uint is_warped_stem  = uint(19 <= block_id && block_id < 23);
	uint is_crimson_stem = uint(23 <= block_id && block_id < 27);
	block_id = block_id * (1u - is_warped_stem) + 46 * is_warped_stem;
	block_id = block_id * (1u - is_crimson_stem) + 58 * is_crimson_stem;

	// SSS blocks
	if (block_id == 5u  || // Leaves
	    block_id == 14u || // Strong SSS
	    block_id == 15u    // Weak SSS
	) {
		block_id = 79; // light gray tint
	}

	// Mark transparent light sources
	block_id = (vertex_at_grid_corner)
		? block_id
		: clamp(block_id + 128u, 0u, 255u);

	if (is_voxelized && is_inside_voxel_volume(voxel_pos)) {
		imageStore(voxel_img, ivec3(voxel_pos), uvec4(block_id, 0u, 0u, 0u));
	}
}
#endif

#endif // INCLUDE_LIGHTING_LPV_VOXELIZATION

在开发者所定义的体素空间中,一个体素的大小就是原版一个普通方块的大小
代码中使用 is_corner(block_is, pos) 方法来判断一个方块是否占用了一个体素单位
对于当前方块:

  • 如果它没有占用立方体的8个角,可以认为它是一个不完整方块,不作为一个体素加入体素空间,那么光照就可以穿透
  • 否则,它被认为是一个完整方块,但还需要 is_voxelized(block_id, vertex_at_grid_corner) 进一步判断要不要作为一个体素加入到体素空间中
    这就造成了一个很明显的问题:像栅栏这种没有占用立方体8个角的方块,光照可以任意穿透,而像楼梯或半砖这类理应允许光照穿透它们的缝隙的方块却不允许穿透。那么对于littletiles的小方块,可以在一个原版方块大小的空间中任意摆放,但只要有一个小方块占用了立方体其中一个角,都和楼梯/半砖等是同一个下场

左:没有一个角被占用,光照可穿透;右:有一个角被占用,光照穿透不了

解决办法

尝试用半透明方块,如淡灰色玻璃,发现光照能穿透,并且亮度有一定衰减。那么这不是很合适 用于增强灯光在广州塔腰部小缝隙中穿透吗?经过几个小时摸索,上面代码中 block_id 是参考propertiesFiles设计的,其中 block.properties 将各种方块分成了半透明方块和其他类型方块。那么只要把广州塔的柱子方块(quartz_block)改为和light_gray_glass一类就行了

实现效果

左:未修改类型为半透明的效果;中:修改为半透明的效果;右:减少亮度衰减率后的增强效果

现实对比

本文追求的是还原现实中广州塔柱子背光看起来很暗的效果,如果全透就会这样:

huge_2025-09-24_18.44.58_0000

posted @ 2025-09-29 20:15  鼓舞人心的杰克森  阅读(83)  评论(0)    收藏  举报