Lighting fixes on transparent textures.
This commit is contained in:
parent
caac38336c
commit
b4376c0cfc
11 changed files with 269 additions and 36 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -22,3 +22,4 @@ docs/.asciidoctor/
|
||||||
/Menu.bat
|
/Menu.bat
|
||||||
/data/
|
/data/
|
||||||
screenshots/
|
screenshots/
|
||||||
|
screenshots-hw/
|
||||||
|
|
|
||||||
|
|
@ -649,6 +649,13 @@ API Changes
|
||||||
- navAgentNew keeps the radius and height at 0.01 or more, as navNew
|
- navAgentNew keeps the radius and height at 0.01 or more, as navNew
|
||||||
does, so onNavArrived fires for an agent given a radius of zero.
|
does, so onNavArrived fires for an agent given a radius of zero.
|
||||||
|
|
||||||
|
- materialSetCutoff(material, cutoff) discards the texels whose base
|
||||||
|
colour alpha falls below the cutoff, in the lit pass and in the shadow
|
||||||
|
pass alike, so cutout foliage casts cutout shadows. glTF's MASK alpha
|
||||||
|
mode now sets it from the file's alphaCutoff instead of warning that
|
||||||
|
the material would be drawn opaque; BLEND and OPAQUE are unchanged.
|
||||||
|
Materials that never set a cutoff keep drawing every texel.
|
||||||
|
|
||||||
- --deterministic[=MS] runs the engine on a virtual clock stepped MS
|
- --deterministic[=MS] runs the engine on a virtual clock stepped MS
|
||||||
milliseconds (default 15) once a frame instead of on real time, steps
|
milliseconds (default 15) once a frame instead of on real time, steps
|
||||||
the disc one video frame a frame with it, and seeds Lua's generator
|
the disc one video frame a frame with it, and seeds Lua's generator
|
||||||
|
|
|
||||||
17
INSTALL
17
INSTALL
|
|
@ -59,9 +59,20 @@ library (zlib, zstd, SDL3 and its satellites, OpenSSL, FFmpeg) into
|
||||||
copied to .builddir/Singe-v<version>-<Os>-<arch>.
|
copied to .builddir/Singe-v<version>-<Os>-<arch>.
|
||||||
|
|
||||||
Host packages (what build-all.sh installs): build-essential cmake
|
Host packages (what build-all.sh installs): build-essential cmake
|
||||||
pkg-config perl nasm llvm imagemagick lua5.4 ffmpeg asciidoctor
|
git-lfs pkg-config perl nasm llvm autoconf automake libtool imagemagick
|
||||||
ruby-asciidoctor-pdf autoconf automake libtool libasound-dev libxi-dev
|
ffmpeg lua5.4 asciidoctor ruby-asciidoctor-pdf libva-dev libvdpau-dev
|
||||||
libvdpau-dev libva-dev libdrm-dev libgl-dev libx11-dev.
|
libdrm-dev libgl-dev libegl-dev libgles-dev libgbm-dev libasound2-dev
|
||||||
|
libpulse-dev libpipewire-0.3-dev libjack-jackd2-dev libsndio-dev
|
||||||
|
libudev-dev libdbus-1-dev libibus-1.0-dev libxkbcommon-dev libx11-dev
|
||||||
|
libxext-dev libxfixes-dev libxi-dev libxcursor-dev libxrandr-dev
|
||||||
|
libxss-dev libxtst-dev libwayland-dev wayland-protocols libdecor-0-dev.
|
||||||
|
|
||||||
|
The artwork, the font and the menu video in assets/ are stored with Git
|
||||||
|
LFS. Clone with git-lfs present, or those files arrive as small text
|
||||||
|
pointers and the build embeds the pointers instead of the assets; a
|
||||||
|
checkout already made without it is repaired with "git lfs install &&
|
||||||
|
git lfs pull". build-all.sh stops with an error rather than build a
|
||||||
|
binary around them.
|
||||||
|
|
||||||
The Linux release build and the Windows build use zig as the compiler:
|
The Linux release build and the Windows build use zig as the compiler:
|
||||||
the build fetches a pinned zig release (version and SHA-256 in
|
the build fetches a pinned zig release (version and SHA-256 in
|
||||||
|
|
|
||||||
176
build-all.sh
176
build-all.sh
|
|
@ -32,6 +32,66 @@
|
||||||
# ./build-all.sh linux x86_64 --target rebuild-ffmpeg
|
# ./build-all.sh linux x86_64 --target rebuild-ffmpeg
|
||||||
|
|
||||||
G_BUILDDIR=.builddir
|
G_BUILDDIR=.builddir
|
||||||
|
G_INSTALL="$(dirname "$0")/INSTALL"
|
||||||
|
|
||||||
|
# The one copy of the host package list. Everything else that names these packages derives from it.
|
||||||
|
# Grouped by what wants them, because the SDL3 half is long and a bare list gives no clue why a
|
||||||
|
# package is here: SDL3 compiles a backend whenever it finds the headers, so a package missing from
|
||||||
|
# this list does not fail the build, it quietly drops a backend from the binary.
|
||||||
|
G_HOSTPACKAGES=(
|
||||||
|
# Toolchain
|
||||||
|
build-essential
|
||||||
|
cmake
|
||||||
|
git-lfs
|
||||||
|
pkg-config
|
||||||
|
perl
|
||||||
|
nasm
|
||||||
|
llvm
|
||||||
|
autoconf
|
||||||
|
automake
|
||||||
|
libtool
|
||||||
|
# Content the build generates: embedded images, the menu video, the LuaSec table, the manual
|
||||||
|
imagemagick
|
||||||
|
ffmpeg
|
||||||
|
lua5.4
|
||||||
|
asciidoctor
|
||||||
|
ruby-asciidoctor-pdf
|
||||||
|
# Video decode and display
|
||||||
|
libva-dev
|
||||||
|
libvdpau-dev
|
||||||
|
libdrm-dev
|
||||||
|
libgl-dev
|
||||||
|
libegl-dev
|
||||||
|
libgles-dev
|
||||||
|
libgbm-dev
|
||||||
|
# SDL3 audio backends
|
||||||
|
libasound2-dev
|
||||||
|
libpulse-dev
|
||||||
|
libpipewire-0.3-dev
|
||||||
|
libjack-jackd2-dev
|
||||||
|
libsndio-dev
|
||||||
|
# SDL3 device hotplug, desktop integration and input methods
|
||||||
|
libudev-dev
|
||||||
|
libdbus-1-dev
|
||||||
|
libibus-1.0-dev
|
||||||
|
libxkbcommon-dev
|
||||||
|
# SDL3 X11 video backend and the extensions it uses
|
||||||
|
libx11-dev
|
||||||
|
libxext-dev
|
||||||
|
libxfixes-dev
|
||||||
|
libxi-dev
|
||||||
|
libxcursor-dev
|
||||||
|
libxrandr-dev
|
||||||
|
libxss-dev
|
||||||
|
libxtst-dev
|
||||||
|
# SDL3 Wayland video backend
|
||||||
|
libwayland-dev
|
||||||
|
wayland-protocols
|
||||||
|
libdecor-0-dev
|
||||||
|
)
|
||||||
|
|
||||||
|
G_INSTALLLEAD="Host packages (what build-all.sh installs):"
|
||||||
|
G_INSTALLWIDTH=72
|
||||||
|
|
||||||
|
|
||||||
function buildAll() {
|
function buildAll() {
|
||||||
|
|
@ -54,37 +114,107 @@ function buildAll() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# assets/ holds the artwork, the font and the menu video in Git LFS. A clone made without git-lfs
|
||||||
|
# leaves pointer stubs -- a hundred and thirty bytes of text where a video should be -- and the
|
||||||
|
# resource generators embed them without complaining, so the failure only shows up at run time as a
|
||||||
|
# menu with no background. One sentinel is enough to tell the two apart.
|
||||||
|
function checkoutHydrated() {
|
||||||
|
local SENTINEL="$(dirname "$0")/assets/180503_01_PurpleGrid.mp4"
|
||||||
|
|
||||||
|
# A sentinel that is not there at all is not a hydrated checkout either, and saying so beats
|
||||||
|
# passing quietly because the path was wrong.
|
||||||
|
[[ -f "${SENTINEL}" ]] || return 1
|
||||||
|
! head -c 64 "${SENTINEL}" | grep -q "git-lfs.github.com/spec"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# The package list as INSTALL wants to read it: one sentence, wrapped, ending in a full stop.
|
||||||
|
function installParagraph() {
|
||||||
|
local LINE="${G_INSTALLLEAD}"
|
||||||
|
local SEP=" "
|
||||||
|
local WORD
|
||||||
|
|
||||||
|
for WORD in "${G_HOSTPACKAGES[@]}"; do
|
||||||
|
if (( ${#LINE} + ${#SEP} + ${#WORD} > G_INSTALLWIDTH )); then
|
||||||
|
printf '%s\n' "${LINE}"
|
||||||
|
LINE="${WORD}"
|
||||||
|
else
|
||||||
|
LINE="${LINE}${SEP}${WORD}"
|
||||||
|
fi
|
||||||
|
SEP=" "
|
||||||
|
done
|
||||||
|
printf '%s.\n' "${LINE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# What INSTALL says today: from the lead line to the first line that ends the sentence.
|
||||||
|
function installCurrent() {
|
||||||
|
awk -v lead="${G_INSTALLLEAD}" '
|
||||||
|
index($0, lead) == 1 { found = 1 }
|
||||||
|
found { print; if (/\.$/) exit }
|
||||||
|
' "${G_INSTALL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function installMatches() {
|
||||||
|
# A checkout without INSTALL is not worth nagging about; there is nothing to disagree with.
|
||||||
|
[[ ! -f "${G_INSTALL}" ]] || [[ "$(installParagraph)" == "$(installCurrent)" ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Writes the paragraph back into INSTALL, replacing whatever sentence is there now.
|
||||||
|
function installSync() {
|
||||||
|
local TEMP
|
||||||
|
|
||||||
|
TEMP=$(mktemp)
|
||||||
|
awk -v lead="${G_INSTALLLEAD}" -v replacement="$(installParagraph)" '
|
||||||
|
index($0, lead) == 1 && !done {
|
||||||
|
print replacement
|
||||||
|
skip = 1
|
||||||
|
}
|
||||||
|
skip { if (/\.$/) { skip = 0; done = 1 } next }
|
||||||
|
{ print }
|
||||||
|
' "${G_INSTALL}" > "${TEMP}"
|
||||||
|
mv "${TEMP}" "${G_INSTALL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# -e = stop script on errors
|
# -e = stop script on errors
|
||||||
# -u = stop script on undefined variable
|
# -u = stop script on undefined variable
|
||||||
# -o pipefail = stop pipeline if any step fails
|
# -o pipefail = stop pipeline if any step fails
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
--packages)
|
||||||
|
printf '%s\n' "${G_HOSTPACKAGES[@]}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--sync-install)
|
||||||
|
installSync
|
||||||
|
echo "INSTALL rewritten from build-all.sh."
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
mkdir -p ${G_BUILDDIR}
|
mkdir -p ${G_BUILDDIR}
|
||||||
|
|
||||||
|
|
||||||
# Host packages the build needs on a Debian based system (the same list is in INSTALL).
|
# Host packages the build needs on a Debian based system. INSTALL prints the same list for a
|
||||||
sudo apt-get install -y \
|
# reader who has not run anything yet, so it is written from this one: --sync-install rewrites
|
||||||
build-essential \
|
# that paragraph and every build checks it, which is why there is no second list to keep in step.
|
||||||
cmake \
|
if ! installMatches; then
|
||||||
pkg-config \
|
echo "warning: INSTALL's host package list no longer matches this script."
|
||||||
perl \
|
echo " Run ./build-all.sh --sync-install to write it from here."
|
||||||
nasm \
|
fi
|
||||||
llvm \
|
sudo apt-get install -y "${G_HOSTPACKAGES[@]}"
|
||||||
imagemagick \
|
|
||||||
lua5.4 \
|
|
||||||
ffmpeg \
|
if ! checkoutHydrated; then
|
||||||
asciidoctor \
|
echo "error: assets/ holds git-lfs pointer files, not the real artwork, font and video."
|
||||||
ruby-asciidoctor-pdf \
|
echo " The build would embed those stubs and produce a broken binary."
|
||||||
autoconf \
|
echo " Fix the checkout with: git lfs install && git lfs pull"
|
||||||
automake \
|
exit 1
|
||||||
libtool \
|
fi
|
||||||
libasound-dev \
|
|
||||||
libxi-dev \
|
|
||||||
libvdpau-dev \
|
|
||||||
libva-dev \
|
|
||||||
libdrm-dev \
|
|
||||||
libgl-dev \
|
|
||||||
libx11-dev
|
|
||||||
|
|
||||||
|
|
||||||
if [[ $# -ge 2 ]]; then
|
if [[ $# -ge 2 ]]; then
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
# Invoked in script mode: -DSHADERCROSS -DSOURCE=<file.hlsl> -DOUTPUT=<header> -DENTRIES=<name:stage;...>
|
# Invoked in script mode: -DSHADERCROSS -DSOURCE=<file.hlsl> -DOUTPUT=<header> -DENTRIES=<name:stage;...>
|
||||||
# -DPREFIX=<lowerCamel prefix> -DTYPE=<struct name>. ENTRIES, PREFIX and TYPE default to the scene's.
|
# -DPREFIX=<lowerCamel prefix> -DTYPE=<struct name>. ENTRIES, PREFIX and TYPE default to the scene's.
|
||||||
if(NOT ENTRIES)
|
if(NOT ENTRIES)
|
||||||
set(ENTRIES vertexStatic:vertex vertexSkinned:vertex fragmentMain:fragment depthMain:fragment particleVertex:vertex particleFragment:fragment lineVertex:vertex lineFragment:fragment postVertex:vertex postFragment:fragment skyFragment:fragment bloomDown:fragment bloomUp:fragment)
|
set(ENTRIES vertexStatic:vertex vertexSkinned:vertex fragmentMain:fragment depthMain:fragment depthCutoutMain:fragment particleVertex:vertex particleFragment:fragment lineVertex:vertex lineFragment:fragment postVertex:vertex postFragment:fragment skyFragment:fragment bloomDown:fragment bloomUp:fragment)
|
||||||
endif()
|
endif()
|
||||||
if(NOT PREFIX)
|
if(NOT PREFIX)
|
||||||
set(PREFIX sceneShader)
|
set(PREFIX sceneShader)
|
||||||
|
|
|
||||||
|
|
@ -1976,6 +1976,14 @@ Beyond that:
|
||||||
* `materialSetBlend` makes the alpha count, for glass, ghosts and water.
|
* `materialSetBlend` makes the alpha count, for glass, ghosts and water.
|
||||||
Blended meshes draw after everything opaque, sorted by distance, and do
|
Blended meshes draw after everything opaque, sorted by distance, and do
|
||||||
not cast shadows.
|
not cast shadows.
|
||||||
|
* `materialSetCutoff` keeps only the texels whose alpha reaches the
|
||||||
|
cutoff and throws the rest away, which is how leaves, fences, grates
|
||||||
|
and chain link are drawn: one quad, most of it gone. Unlike blending it
|
||||||
|
needs no sorting and it still casts a shadow, and the shadow is cut out
|
||||||
|
too, so a tree throws leaf shadows rather than the shadow of its quad.
|
||||||
|
A cutoff of 0 (the default) turns masking off. A model loaded from glTF
|
||||||
|
brings its own: `MASK` sets the cutoff from the file, `BLEND` turns
|
||||||
|
blending on, and `OPAQUE` does neither.
|
||||||
|
|
||||||
The texture of a material can be any loaded sprite's image
|
The texture of a material can be any loaded sprite's image
|
||||||
(`materialSetTexture`), the laserdisc itself or a loaded video
|
(`materialSetTexture`), the laserdisc itself or a loaded video
|
||||||
|
|
|
||||||
|
|
@ -854,9 +854,10 @@ static int32_t _loadMaterial(ImageCacheT *cache, const cgltf_material *material)
|
||||||
_applyTexture(handle, cache, &material->occlusion_texture, MAP_OCCLUSION, material->occlusion_texture.scale);
|
_applyTexture(handle, cache, &material->occlusion_texture, MAP_OCCLUSION, material->occlusion_texture.scale);
|
||||||
_applyTexture(handle, cache, &material->emissive_texture, MAP_EMISSIVE, 1.0f);
|
_applyTexture(handle, cache, &material->emissive_texture, MAP_EMISSIVE, 1.0f);
|
||||||
materialSetEmissiveLinear(handle, material->emissive_factor[0] * strength, material->emissive_factor[1] * strength, material->emissive_factor[2] * strength);
|
materialSetEmissiveLinear(handle, material->emissive_factor[0] * strength, material->emissive_factor[1] * strength, material->emissive_factor[2] * strength);
|
||||||
if (material->alpha_mode == cgltf_alpha_mode_mask) {
|
// glTF's three alpha modes: OPAQUE ignores alpha, BLEND sorts and blends, MASK keeps the
|
||||||
_warn("Material %s uses alpha masking (cutoff %.2f), which is drawn opaque.", material->name ? material->name : "(unnamed)", material->alpha_cutoff);
|
// texels at or above a cutoff and discards the rest. A cutoff of zero is masking that keeps
|
||||||
}
|
// everything, so the mode decides whether one is set at all rather than the value.
|
||||||
|
materialSetCutoff(handle, (material->alpha_mode == cgltf_alpha_mode_mask) ? material->alpha_cutoff : 0.0f);
|
||||||
materialSetBlend(handle, material->alpha_mode == cgltf_alpha_mode_blend);
|
materialSetBlend(handle, material->alpha_mode == cgltf_alpha_mode_blend);
|
||||||
materialSetDoubleSided(handle, material->double_sided);
|
materialSetDoubleSided(handle, material->double_sided);
|
||||||
materialSetUnlit(handle, material->unlit);
|
materialSetUnlit(handle, material->unlit);
|
||||||
|
|
|
||||||
53
src/scene.c
53
src/scene.c
|
|
@ -65,9 +65,10 @@
|
||||||
#define PIPELINE_SKINNED 1
|
#define PIPELINE_SKINNED 1
|
||||||
#define PIPELINE_BLEND 2
|
#define PIPELINE_BLEND 2
|
||||||
#define PIPELINE_TWO_SIDED 4
|
#define PIPELINE_TWO_SIDED 4
|
||||||
#define SHADOW_PIPELINES 4 // skinned x double sided
|
#define SHADOW_PIPELINES 8 // skinned x double sided x cutout
|
||||||
#define SHADOW_PIPELINE_SKINNED 1
|
#define SHADOW_PIPELINE_SKINNED 1
|
||||||
#define SHADOW_PIPELINE_TWO_SIDED 2
|
#define SHADOW_PIPELINE_TWO_SIDED 2
|
||||||
|
#define SHADOW_PIPELINE_CUTOUT 4
|
||||||
#define SAMPLE_SETS 2 // Pipelines per target sample count ...
|
#define SAMPLE_SETS 2 // Pipelines per target sample count ...
|
||||||
#define SAMPLE_SET_SINGLE 0 // ... single sample (views, and the window without antialiasing) ...
|
#define SAMPLE_SET_SINGLE 0 // ... single sample (views, and the window without antialiasing) ...
|
||||||
#define SAMPLE_SET_MULTI 1 // ... and the window's multisampled targets
|
#define SAMPLE_SET_MULTI 1 // ... and the window's multisampled targets
|
||||||
|
|
@ -166,7 +167,7 @@ typedef struct MaterialUniformsS {
|
||||||
float baseColor[4];
|
float baseColor[4];
|
||||||
float emissive[4];
|
float emissive[4];
|
||||||
float material[4]; // x = metallic, y = roughness, z = unlit (1/0), w = TEXTURE_*
|
float material[4]; // x = metallic, y = roughness, z = unlit (1/0), w = TEXTURE_*
|
||||||
float maps[4]; // x = normal map strength (0 = none), y = occlusion strength (0 = none)
|
float maps[4]; // x = normal map strength (0 = none), y = occlusion strength (0 = none), z = alpha cutoff (0 = none)
|
||||||
float tiling[4]; // x, y = texture repeats across the surface
|
float tiling[4]; // x, y = texture repeats across the surface
|
||||||
} MaterialUniformsT;
|
} MaterialUniformsT;
|
||||||
|
|
||||||
|
|
@ -225,6 +226,7 @@ typedef struct MaterialS {
|
||||||
Vec3T emissive;
|
Vec3T emissive;
|
||||||
float metallic;
|
float metallic;
|
||||||
float roughness;
|
float roughness;
|
||||||
|
float cutoff; // glTF alpha masking: texels below this are discarded, 0 = off
|
||||||
SDL_GPUTexture *texture; // Owned; NULL means untextured
|
SDL_GPUTexture *texture; // Owned; NULL means untextured
|
||||||
SDL_GPUTexture *normalMap; // Owned, each NULL when absent
|
SDL_GPUTexture *normalMap; // Owned, each NULL when absent
|
||||||
SDL_GPUTexture *occlusionMap;
|
SDL_GPUTexture *occlusionMap;
|
||||||
|
|
@ -448,6 +450,7 @@ typedef struct SceneS {
|
||||||
SDL_GPUShader *vertexSkinned;
|
SDL_GPUShader *vertexSkinned;
|
||||||
SDL_GPUShader *fragment;
|
SDL_GPUShader *fragment;
|
||||||
SDL_GPUShader *depthFragment; // Empty; the shadow pass writes depth only
|
SDL_GPUShader *depthFragment; // Empty; the shadow pass writes depth only
|
||||||
|
SDL_GPUShader *depthCutoutFragment; // Samples the base texture to mask, for cutout casters
|
||||||
SDL_GPUShader *particleVertex;
|
SDL_GPUShader *particleVertex;
|
||||||
SDL_GPUShader *particleFragment;
|
SDL_GPUShader *particleFragment;
|
||||||
SDL_GPUShader *lineVertex;
|
SDL_GPUShader *lineVertex;
|
||||||
|
|
@ -1307,6 +1310,7 @@ static bool _createShaders(void) {
|
||||||
_scene.vertexSkinned = _createShader(&sceneShaderVertexSkinned, SDL_GPU_SHADERSTAGE_VERTEX, 0, 2, 2);
|
_scene.vertexSkinned = _createShader(&sceneShaderVertexSkinned, SDL_GPU_SHADERSTAGE_VERTEX, 0, 2, 2);
|
||||||
_scene.fragment = _createShader(&sceneShaderFragmentMain, SDL_GPU_SHADERSTAGE_FRAGMENT, MATERIAL_SAMPLERS, 2, 0);
|
_scene.fragment = _createShader(&sceneShaderFragmentMain, SDL_GPU_SHADERSTAGE_FRAGMENT, MATERIAL_SAMPLERS, 2, 0);
|
||||||
_scene.depthFragment = _createShader(&sceneShaderDepthMain, SDL_GPU_SHADERSTAGE_FRAGMENT, 0, 0, 0);
|
_scene.depthFragment = _createShader(&sceneShaderDepthMain, SDL_GPU_SHADERSTAGE_FRAGMENT, 0, 0, 0);
|
||||||
|
_scene.depthCutoutFragment = _createShader(&sceneShaderDepthCutoutMain, SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 2, 0);
|
||||||
_scene.particleVertex = _createShader(&sceneShaderParticleVertex, SDL_GPU_SHADERSTAGE_VERTEX, 0, 1, 0);
|
_scene.particleVertex = _createShader(&sceneShaderParticleVertex, SDL_GPU_SHADERSTAGE_VERTEX, 0, 1, 0);
|
||||||
_scene.particleFragment = _createShader(&sceneShaderParticleFragment, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 2, 0);
|
_scene.particleFragment = _createShader(&sceneShaderParticleFragment, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 2, 0);
|
||||||
_scene.lineVertex = _createShader(&sceneShaderLineVertex, SDL_GPU_SHADERSTAGE_VERTEX, 0, 1, 0);
|
_scene.lineVertex = _createShader(&sceneShaderLineVertex, SDL_GPU_SHADERSTAGE_VERTEX, 0, 1, 0);
|
||||||
|
|
@ -1316,7 +1320,7 @@ static bool _createShaders(void) {
|
||||||
_scene.postFragment = _createShader(&sceneShaderPostFragment, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 1, 0);
|
_scene.postFragment = _createShader(&sceneShaderPostFragment, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 1, 0);
|
||||||
_scene.bloomDownFragment = _createShader(&sceneShaderBloomDown, SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1, 0);
|
_scene.bloomDownFragment = _createShader(&sceneShaderBloomDown, SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 1, 0);
|
||||||
_scene.bloomUpFragment = _createShader(&sceneShaderBloomUp, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 1, 0);
|
_scene.bloomUpFragment = _createShader(&sceneShaderBloomUp, SDL_GPU_SHADERSTAGE_FRAGMENT, 2, 1, 0);
|
||||||
return (_scene.vertexStatic != NULL) && (_scene.vertexSkinned != NULL) && (_scene.fragment != NULL) && (_scene.depthFragment != NULL) && (_scene.particleVertex != NULL) && (_scene.particleFragment != NULL) && (_scene.postVertex != NULL) && (_scene.postFragment != NULL) && (_scene.skyFragment != NULL) && (_scene.bloomDownFragment != NULL) && (_scene.bloomUpFragment != NULL) && (_scene.lineVertex != NULL) && (_scene.lineFragment != NULL);
|
return (_scene.vertexStatic != NULL) && (_scene.vertexSkinned != NULL) && (_scene.fragment != NULL) && (_scene.depthFragment != NULL) && (_scene.depthCutoutFragment != NULL) && (_scene.particleVertex != NULL) && (_scene.particleFragment != NULL) && (_scene.postVertex != NULL) && (_scene.postFragment != NULL) && (_scene.skyFragment != NULL) && (_scene.bloomDownFragment != NULL) && (_scene.bloomUpFragment != NULL) && (_scene.lineVertex != NULL) && (_scene.lineFragment != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1367,7 +1371,7 @@ static bool _createShadowPipeline(int32_t variant) {
|
||||||
memset(&info, 0, sizeof(info));
|
memset(&info, 0, sizeof(info));
|
||||||
_describeMeshVertex(&buffer, attributes);
|
_describeMeshVertex(&buffer, attributes);
|
||||||
info.vertex_shader = (variant & SHADOW_PIPELINE_SKINNED) ? _scene.vertexSkinned : _scene.vertexStatic;
|
info.vertex_shader = (variant & SHADOW_PIPELINE_SKINNED) ? _scene.vertexSkinned : _scene.vertexStatic;
|
||||||
info.fragment_shader = _scene.depthFragment;
|
info.fragment_shader = (variant & SHADOW_PIPELINE_CUTOUT) ? _scene.depthCutoutFragment : _scene.depthFragment;
|
||||||
info.vertex_input_state.vertex_buffer_descriptions = &buffer;
|
info.vertex_input_state.vertex_buffer_descriptions = &buffer;
|
||||||
info.vertex_input_state.num_vertex_buffers = 1;
|
info.vertex_input_state.num_vertex_buffers = 1;
|
||||||
info.vertex_input_state.vertex_attributes = attributes;
|
info.vertex_input_state.vertex_attributes = attributes;
|
||||||
|
|
@ -1805,7 +1809,7 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// A bulb inside a closed mesh sees only its back faces; they must still cast.
|
// A bulb inside a closed mesh sees only its back faces; they must still cast.
|
||||||
variant = ((variant & PIPELINE_SKINNED) ? SHADOW_PIPELINE_SKINNED : 0) | ((twoSided || (variant & PIPELINE_TWO_SIDED)) ? SHADOW_PIPELINE_TWO_SIDED : 0);
|
variant = ((variant & PIPELINE_SKINNED) ? SHADOW_PIPELINE_SKINNED : 0) | ((twoSided || (variant & PIPELINE_TWO_SIDED)) ? SHADOW_PIPELINE_TWO_SIDED : 0) | ((material->cutoff > 0.0f) ? SHADOW_PIPELINE_CUTOUT : 0);
|
||||||
if ((_scene.shadowPipelines[variant] == NULL) && !_createShadowPipeline(variant)) {
|
if ((_scene.shadowPipelines[variant] == NULL) && !_createShadowPipeline(variant)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -1882,6 +1886,28 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i
|
||||||
if (_scene.draws[index].skin != NO_HANDLE) {
|
if (_scene.draws[index].skin != NO_HANDLE) {
|
||||||
SDL_PushGPUVertexUniformData(commands, SKIN_UNIFORMS, &_scene.skins[_scene.draws[index].skin], sizeof(SkinUniformsT));
|
SDL_PushGPUVertexUniformData(commands, SKIN_UNIFORMS, &_scene.skins[_scene.draws[index].skin], sizeof(SkinUniformsT));
|
||||||
}
|
}
|
||||||
|
// A cutout caster binds what the masking depth shader reads and nothing else: the base
|
||||||
|
// texture, the colour whose alpha it multiplies, the cutoff and the tiling. The shader
|
||||||
|
// declares the frame's buffer it never reads, so a zeroed one goes in to fill the slot.
|
||||||
|
if (shadowPass && ((variant & SHADOW_PIPELINE_CUTOUT) != 0)) {
|
||||||
|
SDL_GPUTextureSamplerBinding cutoutBinding;
|
||||||
|
FragmentUniformsT unread;
|
||||||
|
|
||||||
|
memset(&unread, 0, sizeof(unread));
|
||||||
|
memset(&materialUniforms, 0, sizeof(materialUniforms));
|
||||||
|
baseTexture = _materialTexture(material);
|
||||||
|
materialUniforms.baseColor[3] = material->baseColor.w;
|
||||||
|
materialUniforms.material[3] = (float)((baseTexture == NULL) ? TEXTURE_NONE : TEXTURE_SRGB);
|
||||||
|
materialUniforms.maps[2] = material->cutoff;
|
||||||
|
materialUniforms.tiling[0] = material->tilingU;
|
||||||
|
materialUniforms.tiling[1] = material->tilingV;
|
||||||
|
memset(&cutoutBinding, 0, sizeof(cutoutBinding));
|
||||||
|
cutoutBinding.texture = (baseTexture != NULL) ? baseTexture : _scene.white;
|
||||||
|
cutoutBinding.sampler = (material->filter == FILTER_NEAREST) ? _scene.nearestSampler : _scene.sampler;
|
||||||
|
SDL_PushGPUFragmentUniformData(commands, FRAME_UNIFORMS, &unread, sizeof(unread));
|
||||||
|
SDL_PushGPUFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms));
|
||||||
|
SDL_BindGPUFragmentSamplers(pass, 0, &cutoutBinding, 1);
|
||||||
|
}
|
||||||
if (!shadowPass) {
|
if (!shadowPass) {
|
||||||
baseTexture = _materialTexture(material);
|
baseTexture = _materialTexture(material);
|
||||||
memset(&materialUniforms, 0, sizeof(materialUniforms));
|
memset(&materialUniforms, 0, sizeof(materialUniforms));
|
||||||
|
|
@ -1899,6 +1925,7 @@ static void _drawList(SDL_GPUCommandBuffer *commands, SDL_GPURenderPass *pass, i
|
||||||
materialUniforms.material[3] = (float)((baseTexture == NULL) ? TEXTURE_NONE : (((material->feed != NO_HANDLE) || (material->view != NO_HANDLE)) ? TEXTURE_FEED : TEXTURE_SRGB));
|
materialUniforms.material[3] = (float)((baseTexture == NULL) ? TEXTURE_NONE : (((material->feed != NO_HANDLE) || (material->view != NO_HANDLE)) ? TEXTURE_FEED : TEXTURE_SRGB));
|
||||||
materialUniforms.maps[0] = (material->normalMap != NULL) ? material->normalStrength : 0.0f;
|
materialUniforms.maps[0] = (material->normalMap != NULL) ? material->normalStrength : 0.0f;
|
||||||
materialUniforms.maps[1] = (material->occlusionMap != NULL) ? material->occlusionStrength : 0.0f;
|
materialUniforms.maps[1] = (material->occlusionMap != NULL) ? material->occlusionStrength : 0.0f;
|
||||||
|
materialUniforms.maps[2] = material->cutoff;
|
||||||
materialUniforms.tiling[0] = material->tilingU;
|
materialUniforms.tiling[0] = material->tilingU;
|
||||||
materialUniforms.tiling[1] = material->tilingV;
|
materialUniforms.tiling[1] = material->tilingV;
|
||||||
SDL_PushGPUFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms));
|
SDL_PushGPUFragmentUniformData(commands, MATERIAL_UNIFORMS, &materialUniforms, sizeof(materialUniforms));
|
||||||
|
|
@ -3945,6 +3972,18 @@ bool materialSetBlend(int32_t material, bool blend) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// glTF's alpha masking: a texel whose base colour alpha falls below the cutoff is discarded, in
|
||||||
|
// the lit pass and in the shadow pass alike. Zero turns masking off, which is the default and what
|
||||||
|
// every material that never asked for it keeps.
|
||||||
|
bool materialSetCutoff(int32_t material, float cutoff) {
|
||||||
|
if (!materialValid(material)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_scene.materials[material].cutoff = SDL_clamp(cutoff, 0.0f, 1.0f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool materialSetColor(int32_t material, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
bool materialSetColor(int32_t material, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
|
||||||
if (!materialValid(material)) {
|
if (!materialValid(material)) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -5634,6 +5673,10 @@ void sceneQuit(void) {
|
||||||
if (_scene.fragment != NULL) {
|
if (_scene.fragment != NULL) {
|
||||||
SDL_ReleaseGPUShader(_scene.device, _scene.fragment);
|
SDL_ReleaseGPUShader(_scene.device, _scene.fragment);
|
||||||
}
|
}
|
||||||
|
if (_scene.depthCutoutFragment != NULL) {
|
||||||
|
SDL_ReleaseGPUShader(_scene.device, _scene.depthCutoutFragment);
|
||||||
|
_scene.depthCutoutFragment = NULL;
|
||||||
|
}
|
||||||
if (_scene.depthFragment != NULL) {
|
if (_scene.depthFragment != NULL) {
|
||||||
SDL_ReleaseGPUShader(_scene.device, _scene.depthFragment);
|
SDL_ReleaseGPUShader(_scene.device, _scene.depthFragment);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ int32_t materialNew(void);
|
||||||
bool materialSetBlend(int32_t material, bool blend);
|
bool materialSetBlend(int32_t material, bool blend);
|
||||||
bool materialSetColor(int32_t material, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
|
bool materialSetColor(int32_t material, uint8_t r, uint8_t g, uint8_t b, uint8_t a);
|
||||||
bool materialSetColorLinear(int32_t material, float r, float g, float b, float a);
|
bool materialSetColorLinear(int32_t material, float r, float g, float b, float a);
|
||||||
|
bool materialSetCutoff(int32_t material, float cutoff);
|
||||||
bool materialSetDoubleSided(int32_t material, bool doubleSided);
|
bool materialSetDoubleSided(int32_t material, bool doubleSided);
|
||||||
bool materialSetEmissive(int32_t material, uint8_t r, uint8_t g, uint8_t b);
|
bool materialSetEmissive(int32_t material, uint8_t r, uint8_t g, uint8_t b);
|
||||||
bool materialSetEmissiveLinear(int32_t material, float r, float g, float b);
|
bool materialSetEmissiveLinear(int32_t material, float r, float g, float b);
|
||||||
|
|
|
||||||
|
|
@ -234,7 +234,7 @@ cbuffer MaterialUniforms : register(b1, space3) {
|
||||||
float4 baseColor;
|
float4 baseColor;
|
||||||
float4 emissive;
|
float4 emissive;
|
||||||
float4 material; // x = metallic, y = roughness, z = unlit (1/0), w = TEXTURE_*
|
float4 material; // x = metallic, y = roughness, z = unlit (1/0), w = TEXTURE_*
|
||||||
float4 maps; // x = normal map strength (0 = none), y = occlusion strength (0 = none)
|
float4 maps; // x = normal map strength (0 = none), y = occlusion strength (0 = none), z = alpha cutoff (0 = none)
|
||||||
float4 tiling; // x, y = texture repeats across the surface
|
float4 tiling; // x, y = texture repeats across the surface
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -372,6 +372,22 @@ void depthMain(VertexOutput input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// The shadow pass for a material with an alpha cutoff. Depth only as well, but the texel has to be
|
||||||
|
// tested or a leaf casts the shadow of the quad it is drawn on. A video or view feed never masks,
|
||||||
|
// so the plain sample covers both texture cases; only the alpha is read either way.
|
||||||
|
void depthCutoutMain(VertexOutput input) {
|
||||||
|
float4 albedo = baseColor;
|
||||||
|
float2 uv = input.uv * tiling.xy;
|
||||||
|
|
||||||
|
if (material.w > 0.5) {
|
||||||
|
albedo *= baseTexture.Sample(baseSampler, uv);
|
||||||
|
}
|
||||||
|
if ((maps.z > 0.0) && (albedo.a < maps.z)) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Distance fog toward the fog colour between near and far, when it is on.
|
// Distance fog toward the fog colour between near and far, when it is on.
|
||||||
float3 applyFog(float3 colour, float3 worldPosition) {
|
float3 applyFog(float3 colour, float3 worldPosition) {
|
||||||
float d;
|
float d;
|
||||||
|
|
@ -488,6 +504,11 @@ float4 fragmentMain(VertexOutput input) : SV_Target {
|
||||||
} else if (material.w > 0.5) {
|
} else if (material.w > 0.5) {
|
||||||
albedo *= baseTexture.Sample(baseSampler, uv);
|
albedo *= baseTexture.Sample(baseSampler, uv);
|
||||||
}
|
}
|
||||||
|
// Alpha masking: a glTF MASK material keeps only the texels at or above its cutoff. Tested
|
||||||
|
// before any lighting, so a discarded texel costs nothing beyond the sample above.
|
||||||
|
if ((maps.z > 0.0) && (albedo.a < maps.z)) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
if (material.z > 0.5) {
|
if (material.z > 0.5) {
|
||||||
return float4(applyFog(albedo.rgb, input.worldPosition), albedo.a);
|
return float4(applyFog(albedo.rgb, input.worldPosition), albedo.a);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
src/singe.c
10
src/singe.c
|
|
@ -1004,6 +1004,7 @@ static int32_t apiMaterialDelete(lua_State *L);
|
||||||
static int32_t apiMaterialNew(lua_State *L);
|
static int32_t apiMaterialNew(lua_State *L);
|
||||||
static int32_t apiMaterialSetBlend(lua_State *L);
|
static int32_t apiMaterialSetBlend(lua_State *L);
|
||||||
static int32_t apiMaterialSetColor(lua_State *L);
|
static int32_t apiMaterialSetColor(lua_State *L);
|
||||||
|
static int32_t apiMaterialSetCutoff(lua_State *L);
|
||||||
static int32_t apiMaterialSetDoubleSided(lua_State *L);
|
static int32_t apiMaterialSetDoubleSided(lua_State *L);
|
||||||
static int32_t apiMaterialSetEmissive(lua_State *L);
|
static int32_t apiMaterialSetEmissive(lua_State *L);
|
||||||
static int32_t apiMaterialSetEmissiveMap(lua_State *L);
|
static int32_t apiMaterialSetEmissiveMap(lua_State *L);
|
||||||
|
|
@ -4999,6 +5000,7 @@ static void _registerApi(lua_State *L) {
|
||||||
lua_register(L, "materialNew", apiMaterialNew); // 3.00
|
lua_register(L, "materialNew", apiMaterialNew); // 3.00
|
||||||
lua_register(L, "materialSetBlend", apiMaterialSetBlend); // 3.00
|
lua_register(L, "materialSetBlend", apiMaterialSetBlend); // 3.00
|
||||||
lua_register(L, "materialSetColor", apiMaterialSetColor); // 3.00
|
lua_register(L, "materialSetColor", apiMaterialSetColor); // 3.00
|
||||||
|
lua_register(L, "materialSetCutoff", apiMaterialSetCutoff); // 3.00
|
||||||
lua_register(L, "materialSetDoubleSided", apiMaterialSetDoubleSided); // 3.00
|
lua_register(L, "materialSetDoubleSided", apiMaterialSetDoubleSided); // 3.00
|
||||||
lua_register(L, "materialSetEmissive", apiMaterialSetEmissive); // 3.00
|
lua_register(L, "materialSetEmissive", apiMaterialSetEmissive); // 3.00
|
||||||
lua_register(L, "materialSetEmissiveMap", apiMaterialSetEmissiveMap); // 3.00
|
lua_register(L, "materialSetEmissiveMap", apiMaterialSetEmissiveMap); // 3.00
|
||||||
|
|
@ -8868,6 +8870,14 @@ static int32_t apiMaterialSetColor(lua_State *L) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// materialSetCutoff(material, cutoff)
|
||||||
|
static int32_t apiMaterialSetCutoff(lua_State *L) {
|
||||||
|
_argCheck(L, "materialSetCutoff", 2, 2);
|
||||||
|
materialSetCutoff(_argMaterial(L, "materialSetCutoff", 1), (float)_argNumber(L, "materialSetCutoff", 2));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// materialSetDoubleSided(material, bool)
|
// materialSetDoubleSided(material, bool)
|
||||||
static int32_t apiMaterialSetDoubleSided(lua_State *L) {
|
static int32_t apiMaterialSetDoubleSided(lua_State *L) {
|
||||||
_argCheck(L, "materialSetDoubleSided", 2, 2);
|
_argCheck(L, "materialSetDoubleSided", 2, 2);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue