Files
armorpaint/paint/sources/render/make_sculpt.c
T
2026-06-11 20:01:25 +02:00

1081 lines
56 KiB
C

#include "../global.h"
i32 sculpt_object_vertex_offset(mesh_object_t *o) {
i32 offset = 0;
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
mesh_object_t *p = g_project->_->paint_objects->buffer[i];
if (p == o) {
break;
}
offset += p->data->index_array->length;
}
return offset;
}
void sculpt_import_mesh_pack_to_texture(gpu_texture_t *target) {
// Pack positions and normals into texture
u32 capacity = config_get_texture_res_x() * config_get_texture_res_y();
buffer_t *b = buffer_create(capacity * 4 * 4);
i32 vertex_offset = 0;
for (i32 o = 0; o < g_project->_->paint_objects->length; ++o) {
mesh_data_t *mesh = g_project->_->paint_objects->buffer[o]->data;
for (i32 i = 0; i < math_floor(mesh->index_array->length); ++i) {
i32 index = mesh->index_array->buffer[i];
i32 t = i + vertex_offset;
if (t >= capacity) {
break;
}
buffer_set_f32(b, 4 * t * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4] / 32767.0);
buffer_set_f32(b, 4 * t * 4 + 1 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] / 32767.0);
buffer_set_f32(b, 4 * t * 4 + 2 * 4, mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] / 32767.0);
f32 nor_x = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2] / 32767.0f;
f32 nor_y = mesh->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1] / 32767.0f;
f32 nor_z = mesh->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3] / 32767.0f;
f32 l1 = math_abs(nor_x) + math_abs(nor_y) + math_abs(nor_z);
f32 oct_x = l1 > 0.0f ? nor_x / l1 : 0.0f;
f32 oct_y = l1 > 0.0f ? nor_y / l1 : 0.0f;
if (nor_z < 0.0f) {
f32 ox = oct_x;
f32 oy = oct_y;
oct_x = (1.0f - math_abs(oy)) * (ox >= 0.0f ? 1.0f : -1.0f);
oct_y = (1.0f - math_abs(ox)) * (oy >= 0.0f ? 1.0f : -1.0f);
}
buffer_set_f32(b, 4 * t * 4 + 3 * 4, (oct_x + 1.0f) * 0.5f + math_floor((oct_y + 1.0f) * 0.5f * 255.0f + 0.5f));
}
vertex_offset += mesh->index_array->length;
}
gpu_texture_t *imgmesh = gpu_create_texture_from_bytes(b, config_get_texture_res_x(), config_get_texture_res_y(), GPU_TEXTURE_FORMAT_RGBA128);
draw_begin(target, false, 0);
draw_set_pipeline(pipes_copy128);
draw_scaled_image(imgmesh, 0, 0, config_get_texture_res_x(), config_get_texture_res_y());
draw_set_pipeline(NULL);
draw_end();
}
static char *sculpt_blend_mode(node_shader_t *kong, i32 blending, char *cola, char *colb, char *opac) {
// mix/add/screen/lighten/difference (raise), subtract/darken (carve) and linear light (bidirectional)
if (blending == BLEND_TYPE_DARKEN) {
return string("lerp(%s, min(%s, %s), %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_MULTIPLY) {
return string("lerp(%s, %s * %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_BURN) {
return string("lerp(%s, 1.0 - (1.0 - %s) / %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_LIGHTEN) {
return string("max(%s, %s * %s)", cola, colb, opac);
}
else if (blending == BLEND_TYPE_SCREEN) {
return string("(1.0 - (1.0 - %s * %s) * (1.0 - %s))", colb, opac, cola);
}
else if (blending == BLEND_TYPE_DODGE) {
return string("lerp(%s, %s / (1.0 - %s), %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_ADD) {
return string("lerp(%s, %s + %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_OVERLAY) {
char *res = "sculpt_overlay_res";
node_shader_write_frag(kong, string("var %s: float;", res));
node_shader_write_frag(kong, string("if (%s < 0.5) { %s = 2.0 * %s * %s; } else { %s = 1.0 - 2.0 * (1.0 - %s) * (1.0 - %s); }", cola, res, cola, colb,
res, cola, colb));
return string("lerp(%s, %s, %s)", cola, res, opac);
}
else if (blending == BLEND_TYPE_SOFT_LIGHT) {
return string("((1.0 - %s) * %s + %s * ((1.0 - %s) * %s * %s + %s * (1.0 - (1.0 - %s) * (1.0 - %s))))", opac, cola, opac, cola, colb, cola, cola, colb,
cola);
}
else if (blending == BLEND_TYPE_LINEAR_LIGHT) {
return string("(%s + %s * (2.0 * (%s - 0.5)))", cola, opac, colb);
}
else if (blending == BLEND_TYPE_DIFFERENCE) {
return string("lerp(%s, abs(%s - %s), %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_SUBTRACT) {
return string("lerp(%s, %s - %s, %s)", cola, cola, colb, opac);
}
else if (blending == BLEND_TYPE_DIVIDE) {
return string("((1.0 - %s) * %s + %s * %s / %s)", opac, cola, opac, cola, colb);
}
else { // BLEND_TYPE_MIX, hue / saturation / color / value
return string("lerp(%s, %s, %s)", cola, colb, opac);
}
}
node_shader_context_t *sculpt_make_sculpt_run(material_t *data, material_context_t *matcon) {
char *context_id = "paint";
shader_context_t *props = GC_ALLOC_INIT(shader_context_t, {.name = context_id,
.depth_write = false,
.compare_mode = "always",
.cull_mode = "none",
.vertex_elements = any_array_create_from_raw(
(void *[]){
GC_ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "float2"}),
},
1),
.color_attachments = any_array_create_from_raw(
(void *[]){
"RGBA128",
"R8",
},
2)});
node_shader_context_t *con_paint = node_shader_context_create(data, props);
con_paint->data->color_writes_red = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
con_paint->data->color_writes_green = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
con_paint->data->color_writes_blue = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
con_paint->data->color_writes_alpha = u8_array_create_from_raw(
(u8[]){
true,
true,
true,
true,
},
4);
node_shader_t *kong = node_shader_context_make_kong(con_paint);
bool decal = context_is_decal();
bool particle = g_context->tool == TOOL_TYPE_PARTICLE;
// Decal fill layer: displacement must be confined to the decal box projection
bool decal_layer = g_context->layer->fill_material != NULL && g_context->layer->uv_type == UV_TYPE_PROJECT && g_context->tool == TOOL_TYPE_FILL;
node_shader_add_out(kong, "tex_coord: float2");
node_shader_write_vert(kong, "var madd: float2 = float2(0.5, 0.5);");
node_shader_write_vert(kong, "output.tex_coord = input.pos.xy * madd + madd;");
node_shader_write_vert(kong, "output.tex_coord.y = 1.0 - output.tex_coord.y;");
node_shader_write_vert(kong, "output.pos = float4(input.pos.xy, 0.0, 1.0);");
node_shader_write_attrib_frag(kong, "var tex_coord: float2 = input.tex_coord;");
node_shader_write_attrib_frag(kong, "var sculpt_uv: float2 = tex_coord;");
// Restrict displacement to the object selected in the layer's object combo
if (g_project->_->paint_objects->length > 1) {
node_shader_add_texture(kong, "texpaint_sculpt_undo", "_texpaint_sculpt_undo");
node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(_texpaint_sculpt_undo)");
node_shader_add_constant(kong, "sculpt_mask_offset: float", "_sculpt_mask_offset");
node_shader_add_constant(kong, "sculpt_mask_count: float", "_sculpt_mask_count");
node_shader_write_frag(kong, "var sculpt_mask_lin: float = floor(sculpt_uv.y * constants.texpaint_undo_size.y) * "
"constants.texpaint_undo_size.x + floor(sculpt_uv.x * constants.texpaint_undo_size.x);");
node_shader_write_frag(kong, "if (sculpt_mask_lin < constants.sculpt_mask_offset || sculpt_mask_lin >= "
"constants.sculpt_mask_offset + constants.sculpt_mask_count) { discard; }");
}
node_shader_add_constant(kong, "inp: float4", "_input_brush");
node_shader_add_constant(kong, "inplast: float4", "_input_brush_last");
node_shader_add_texture(kong, "gbufferD", NULL);
kong->frag_out = "float4[2]";
node_shader_add_constant(kong, "brush_radius: float", "_brush_radius");
node_shader_add_constant(kong, "brush_opacity: float", "_brush_opacity");
node_shader_add_constant(kong, "brush_hardness: float", "_brush_hardness");
node_shader_write_frag(kong, "var dist: float = 0.0;");
if (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE || g_context->tool == TOOL_TYPE_BLUR ||
g_context->tool == TOOL_TYPE_PARTICLE || decal) {
node_shader_write_frag(kong, "var depth: float = sample_lod(gbufferD, sampler_linear, constants.inp.xy, 0.0).r;");
node_shader_add_constant(kong, "invVP: float4x4", "_inv_view_proj_matrix");
// node_shader_write_frag(kong, "var winp: float4 = float4(float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);");
node_shader_write_frag(kong, "var winp: float4 = float4(float2(constants.inp.x, 1.0 - constants.inp.y) * 2.0 - 1.0, depth, 1.0);");
node_shader_write_frag(kong, "winp = constants.invVP * winp;");
node_shader_write_frag(kong, "winp.xyz = winp.xyz / winp.w;");
node_shader_add_constant(kong, "W: float4x4", "_world_matrix");
node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * constants.texpaint_undo_size.x), "
"uint(tex_coord.y * constants.texpaint_undo_size.y))];");
node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * float4(read_undo.xyz, 1.0)).xyz;");
node_shader_write_frag(kong, "var depthlast: float = sample_lod(gbufferD, sampler_linear, constants.inplast.xy, 0.0).r;");
node_shader_write_frag(kong, "var winplast: float4 = float4(float2(constants.inplast.x, 1.0 - constants.inplast.y) * 2.0 - 1.0, depthlast, 1.0);");
node_shader_write_frag(kong, "winplast = constants.invVP * winplast;");
node_shader_write_frag(kong, "winplast.xyz = winplast.xyz / winplast.w;");
if (particle) {
node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit");
node_shader_add_constant(kong, "particle_hit_last: float3", "_particle_hit_last");
node_shader_add_constant(kong, "particle_radius: float", "_particle_radius");
node_shader_write_frag(kong, "var ppa: float3 = wposition.xyz - constants.particle_hit;");
node_shader_write_frag(kong, "var pba: float3 = constants.particle_hit_last - constants.particle_hit;");
node_shader_write_frag(kong, "var pph: float = clamp(dot(ppa, pba) / max(dot(pba, pba), 0.00000001), 0.0, 1.0);");
node_shader_write_frag(kong, "dist = length(ppa - pba * pph) * (5.0 / constants.particle_radius);");
node_shader_write_frag(kong, "if (dist > 1.0) { discard; }");
}
else if (!decal) {
if (g_context->brush_lazy_radius > 0 && g_context->brush_lazy_step > 0) { // Sphere
node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);");
}
else { // Capsule
node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "var ba: float3 = winplast.xyz - winp.xyz;");
node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);");
node_shader_write_frag(kong, "dist = length(pa - ba * h);");
}
node_shader_write_frag(kong, "if (dist > constants.brush_radius) { discard; }");
}
else { // decal
node_shader_add_constant(kong, "decal_mask: float4", "_decal_mask");
node_shader_write_frag(kong, "if (constants.decal_mask.z > 0.0) {");
if (g_context->brush_lazy_radius > 0 && g_context->brush_lazy_step > 0) {
node_shader_write_frag(kong, "dist = distance(wposition.xyz, winp.xyz);");
}
else {
node_shader_write_frag(kong, "var pa: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "var ba: float3 = winplast.xyz - winp.xyz;");
node_shader_write_frag(kong, "var h: float = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);");
node_shader_write_frag(kong, "dist = length(pa - ba * h);");
}
node_shader_write_frag(kong, "if (dist > constants.brush_radius) { discard; }");
node_shader_write_frag(kong, "}");
}
}
else if (g_context->tool == TOOL_TYPE_FILL) {
node_shader_add_constant(kong, "W: float4x4", "_world_matrix");
node_shader_write_attrib_frag(kong, "var read_undo: float4 = texpaint_sculpt_undo[uint2(uint(tex_coord.x * constants.texpaint_undo_size.x), "
"uint(tex_coord.y * constants.texpaint_undo_size.y))];");
node_shader_write_attrib_frag(kong, "var wposition: float3 = (constants.W * float4(read_undo.xyz, 1.0)).xyz;");
}
if (decal_layer) {
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_constant(kong, "decal_layer_matrix: float4x4", "_decal_layer_matrix");
node_shader_write_frag(kong, "var decal_proj: float4 = constants.decal_layer_matrix * float4(read_undo.xyz, 1.0);");
node_shader_write_frag(kong, "var uvsp: float2 = decal_proj.xy / decal_proj.w;");
node_shader_write_frag(kong, "uvsp.x = uvsp.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "uvsp.y = 1.0 - (uvsp.y * 0.5 + 0.5);");
node_shader_write_frag(kong, "if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) { discard; }");
f32 uv_angle = g_context->layer->angle;
if (uv_angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_frag(kong, "uvsp = float2(uvsp.x * constants.brush_angle.x - uvsp.y * constants.brush_angle.y, uvsp.x * "
"constants.brush_angle.y + uvsp.y * constants.brush_angle.x);");
}
// Reject surfaces not facing the decal direction
node_shader_write_frag(kong, "var dproj_undo: float4 = texpaint_sculpt_undo[uint2(uint(sculpt_uv.x * constants.texpaint_undo_size.x), uint(sculpt_uv.y "
"* constants.texpaint_undo_size.y))];");
node_shader_write_frag(kong, "var dproj_nv: float = floor(dproj_undo.a) / 255.0;");
node_shader_write_frag(kong, "var dproj_oct: float2 = float2(dproj_undo.a - floor(dproj_undo.a), dproj_nv) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var dproj_nz: float = 1.0 - abs(dproj_oct.x) - abs(dproj_oct.y);");
node_shader_write_frag(kong, "var dproj_nor: float3 = float3(dproj_oct.xy, dproj_nz);");
node_shader_write_frag(kong, "if (dproj_nz < 0.0) { dproj_nor.xy = octahedron_wrap(dproj_oct.xy); }");
node_shader_write_frag(kong, "dproj_nor = normalize((constants.W * float4(normalize(dproj_nor), 0.0)).xyz);");
node_shader_add_constant(kong, "decal_layer_nor: float3", "_decal_layer_nor");
f32 dot_angle = g_context->brush_angle_reject_dot;
node_shader_write_frag(kong, string("if (abs(dot(dproj_nor, constants.decal_layer_nor) - 1.0) > %s) { discard; }", f32_to_string(dot_angle)));
// Reject surfaces outside the decal box
node_shader_add_constant(kong, "decal_layer_loc: float3", "_decal_layer_loc");
node_shader_add_constant(kong, "decal_layer_dim: float", "_decal_layer_dim");
node_shader_write_frag(kong,
"if (abs(dot(constants.decal_layer_nor, constants.decal_layer_loc - wposition.xyz)) > constants.decal_layer_dim) { discard; }");
node_shader_add_constant(kong, "brush_scale: float", "_brush_scale");
node_shader_write_frag(kong, "tex_coord = uvsp * constants.brush_scale;");
}
if (decal) {
// Tangent-space decal projection
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_texture(kong, "gbuffer0_undo", NULL);
node_shader_add_constant(kong, "decal_mask: float4", "_decal_mask");
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window");
node_shader_add_constant(kong, "camera_right: float3", "_camera_right");
node_shader_add_constant(kong, "camera_up: float3", "_camera_up");
node_shader_add_constant(kong, "camera_align: float", "_decal_camera_align");
node_shader_write_attrib_frag(kong, "var uvsp: float2 = float2(0.0, 0.0);");
node_shader_write_attrib_frag(kong, "if (constants.camera_align > 0.0) {");
// Project the surface point to screen-space for the planar, camera-aligned decal
node_shader_write_attrib_frag(kong, "var sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);");
node_shader_write_attrib_frag(kong, "var sp: float2 = sp4.xy / sp4.w;");
node_shader_write_attrib_frag(kong, "sp.x = sp.x * 0.5 + 0.5;");
node_shader_write_attrib_frag(kong, "sp.y = 1.0 - (sp.y * 0.5 + 0.5);");
node_shader_write_attrib_frag(kong, "var ca_depth: float = sample_lod(gbufferD, sampler_linear, constants.decal_mask.xy, 0.0).r;");
node_shader_write_attrib_frag(kong, "var ca_coord: float2 = float2(constants.decal_mask.x * 2.0 - 1.0, 1.0 - constants.decal_mask.y * 2.0);");
node_shader_write_attrib_frag(kong, "var ca_homog: float4 = constants.invVP * float4(ca_coord.x, ca_coord.y, ca_depth, 1.0);");
node_shader_write_attrib_frag(kong, "var ca_clip_w: float = 1.0 / ca_homog.w;");
node_shader_write_attrib_frag(kong, "var vp_up_y: float = (constants.VP * float4(constants.camera_up, 0.0)).y;");
node_shader_write_attrib_frag(kong, "uvsp = sp.xy - constants.decal_mask.xy;");
node_shader_write_attrib_frag(kong, "uvsp.x *= constants.aspect_ratio;");
node_shader_write_attrib_frag(kong, "uvsp = uvsp * (ca_clip_w / (vp_up_y * constants.brush_radius));");
node_shader_write_attrib_frag(kong, "}");
node_shader_write_attrib_frag(kong, "else {");
// When mask is active, anchor the decal at the frozen position
node_shader_write_attrib_frag(kong, "var decal_xy: float2 = constants.inp.xy;");
node_shader_write_attrib_frag(kong, "if (constants.decal_mask.z > 0.0) { decal_xy = constants.decal_mask.xy; }");
// Unproject the decal anchor point from the depth buffer
node_shader_write_attrib_frag(kong, "var decal_depth: float = sample_lod(gbufferD, sampler_linear, decal_xy, 0.0).r;");
node_shader_write_attrib_frag(kong, "var decal_wpos4: float4 = float4(float2(decal_xy.x, 1.0 - decal_xy.y) * 2.0 - 1.0, decal_depth, 1.0);");
node_shader_write_attrib_frag(kong, "decal_wpos4 = constants.invVP * decal_wpos4;");
node_shader_write_attrib_frag(kong, "var decal_wpos: float3 = decal_wpos4.xyz / decal_wpos4.w;");
// Decode face normal at anchor point
node_shader_write_attrib_frag(kong, "var dg0: float2 = sample_lod(gbuffer0_undo, sampler_linear, decal_xy, 0.0).rg;");
node_shader_write_attrib_frag(kong, "var dn: float3;");
node_shader_write_attrib_frag(kong, "dn.z = 1.0 - abs(dg0.x) - abs(dg0.y);");
node_shader_write_attrib_frag(
kong, "if (dn.z >= 0.0) { dn.x = dg0.x; dn.y = dg0.y; } else { var fw: float2 = octahedron_wrap(dg0.xy); dn.x = fw.x; dn.y = fw.y; }");
node_shader_write_attrib_frag(kong, "dn = normalize(dn);");
// Build tangent basis
node_shader_write_attrib_frag(kong, "var d_right: float3 = constants.camera_right;");
node_shader_write_attrib_frag(kong, "if (abs(dot(dn, d_right)) > 0.999) { d_right = float3(0.0, 0.0, 1.0); }");
node_shader_write_attrib_frag(kong, "var d_tan: float3 = normalize(d_right - dn * dot(d_right, dn));");
node_shader_write_attrib_frag(kong, "var d_bin: float3 = cross(d_tan, dn);");
node_shader_write_attrib_frag(kong, "var d_offset: float3 = wposition.xyz - decal_wpos;");
node_shader_write_attrib_frag(kong, "var decal_radius: float = constants.brush_radius;");
node_shader_write_attrib_frag(kong, "if (constants.decal_mask.z > 0.0) { decal_radius = constants.decal_mask.w; }");
node_shader_write_attrib_frag(kong, "if (abs(dot(d_offset, dn)) > decal_radius) { discard; }");
node_shader_write_attrib_frag(kong, "uvsp = float2(dot(d_offset, d_tan), dot(d_offset, d_bin));");
node_shader_write_attrib_frag(kong, "uvsp = uvsp / decal_radius * 0.5;");
node_shader_write_attrib_frag(kong, "}");
if (g_context->brush_directional) {
node_shader_add_constant(kong, "brush_direction: float3", "_brush_direction");
node_shader_write_attrib_frag(kong, "if (constants.brush_direction.z == 0.0) { discard; }");
node_shader_write_attrib_frag(kong, "uvsp = float2(uvsp.x * constants.brush_direction.x - uvsp.y * constants.brush_direction.y, uvsp.x * "
"constants.brush_direction.y + uvsp.y * constants.brush_direction.x);");
}
f32 angle = g_context->brush_angle + g_context->brush_nodes_angle;
if (angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_attrib_frag(kong, "uvsp = float2(uvsp.x * constants.brush_angle.x - uvsp.y * constants.brush_angle.y, uvsp.x * "
"constants.brush_angle.y + uvsp.y * constants.brush_angle.x);");
}
node_shader_add_constant(kong, "brush_scale_x: float", "_brush_scale_x");
node_shader_write_attrib_frag(kong, "uvsp.x *= constants.brush_scale_x;");
node_shader_write_attrib_frag(kong, "uvsp += float2(0.5, 0.5);");
node_shader_write_attrib_frag(kong, "if (uvsp.x < 0.0 || uvsp.y < 0.0 || uvsp.x > 1.0 || uvsp.y > 1.0) { discard; }");
node_shader_add_constant(kong, "brush_scale: float", "_brush_scale");
node_shader_write_attrib_frag(kong, "tex_coord = uvsp * constants.brush_scale;");
}
// parser_material_parse may add vertex elements
i32 velen = con_paint->data->vertex_elements->length;
// parser_material_parse_height = true;
// parser_material_parse_height_as_channel = true;
shader_out_t *sout = parser_material_parse(g_context->material->canvas, con_paint, kong, matcon);
con_paint->data->vertex_elements->length = velen;
// node_shader_write_frag(kong, string("var height: float = %s.r;", sout->out_basecol));
node_shader_write_frag(kong, string("var disp: float3 = %s;", sout->out_basecol));
if (kong->frag_bposition) {
kong->frag_bposition = false;
node_shader_write_attrib_frag(kong, "var bposition: float3 = wposition.xyz;");
}
node_shader_write_frag(kong, "var basecol: float3 = float3(1.0, 1.0, 1.0);");
node_shader_write_frag(kong, string("var opacity: float = %s;", sout->out_opacity));
if (g_context->layer->fill_material == NULL) {
node_shader_write_frag(kong, "opacity *= constants.brush_opacity;");
}
else {
node_shader_write_frag(kong, "opacity *= 20.0;");
}
if (g_context->brush_mask_image != NULL && g_context->tool == TOOL_TYPE_DECAL) {
node_shader_add_texture(kong, "texbrushmask", "_texbrushmask");
node_shader_write_frag(kong, "var mask_sample: float4 = sample_lod(texbrushmask, sampler_linear, uvsp, 0.0);");
if (g_context->brush_mask_image_is_alpha) {
node_shader_write_frag(kong, "opacity *= mask_sample.a;");
}
else {
node_shader_write_frag(kong, "opacity *= mask_sample.r * mask_sample.a;");
}
}
else if (g_context->tool == TOOL_TYPE_TEXT) {
node_shader_add_texture(kong, "textexttool", "_textexttool");
node_shader_write_frag(kong, "opacity *= sample_lod(textexttool, sampler_linear, uvsp, 0.0).r;");
}
if (g_context->brush_mask_image != NULL && (g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER)) {
node_shader_add_texture(kong, "texbrushmask", "_texbrushmask");
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_texture(kong, "gbuffer0_undo", NULL);
node_shader_add_constant(kong, "camera_right: float3", "_camera_right");
node_shader_write_frag(kong, "var mn0: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;");
node_shader_write_frag(kong, "var mn: float3;");
node_shader_write_frag(kong, "mn.z = 1.0 - abs(mn0.x) - abs(mn0.y);");
node_shader_write_frag(
kong, "if (mn.z >= 0.0) { mn.x = mn0.x; mn.y = mn0.y; } else { var mfw: float2 = octahedron_wrap(mn0.xy); mn.x = mfw.x; mn.y = mfw.y; }");
node_shader_write_frag(kong, "mn = normalize(mn);");
node_shader_write_frag(kong, "var mr: float3 = constants.camera_right;");
node_shader_write_frag(kong, "if (abs(dot(mn, mr)) > 0.999) { mr = float3(0.0, 0.0, 1.0); }");
node_shader_write_frag(kong, "var mt: float3 = normalize(mr - mn * dot(mr, mn));");
node_shader_write_frag(kong, "var mb: float3 = cross(mt, mn);");
node_shader_write_frag(kong, "var pa_mask_3d: float3 = wposition.xyz - winp.xyz;");
node_shader_write_frag(kong, "var pa_mask: float2 = float2(dot(pa_mask_3d, mt), dot(pa_mask_3d, mb));");
if (g_context->brush_directional) {
node_shader_add_constant(kong, "brush_direction: float3", "_brush_direction");
node_shader_write_frag(kong, "if (constants.brush_direction.z == 0.0) { discard; }");
node_shader_write_frag(kong, "pa_mask = float2(pa_mask.x * constants.brush_direction.x - pa_mask.y * constants.brush_direction.y, pa_mask.x * "
"constants.brush_direction.y + pa_mask.y * constants.brush_direction.x);");
}
f32 angle = g_context->brush_angle + g_context->brush_nodes_angle;
if (angle != 0.0) {
node_shader_add_constant(kong, "brush_angle: float2", "_brush_angle");
node_shader_write_frag(kong, "pa_mask.xy = float2(pa_mask.x * constants.brush_angle.x - pa_mask.y * constants.brush_angle.y, pa_mask.x * "
"constants.brush_angle.y + pa_mask.y * constants.brush_angle.x);");
}
node_shader_write_frag(kong, "pa_mask = pa_mask / constants.brush_radius * 0.5 + 0.5;");
node_shader_write_frag(kong, "var mask_sample: float4 = sample_lod(texbrushmask, sampler_linear, pa_mask, 0.0);");
if (g_context->brush_mask_image_is_alpha) {
node_shader_write_frag(kong, "opacity *= mask_sample.a;");
}
else {
node_shader_write_frag(kong, "opacity *= mask_sample.r * mask_sample.a;");
}
}
bool has_wposition = g_context->tool == TOOL_TYPE_BRUSH || g_context->tool == TOOL_TYPE_ERASER || g_context->tool == TOOL_TYPE_CLONE ||
g_context->tool == TOOL_TYPE_BLUR || g_context->tool == TOOL_TYPE_PARTICLE || g_context->tool == TOOL_TYPE_FILL || decal;
if (g_context->select_active && has_wposition) {
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "select_mask: float4", "_select_mask");
node_shader_write_frag(kong, "var select_sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);");
node_shader_write_frag(kong, "var select_sp: float2 = select_sp4.xy / select_sp4.w;");
node_shader_write_frag(kong, "select_sp.x = select_sp.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "select_sp.y = 1.0 - (select_sp.y * 0.5 + 0.5);");
node_shader_write_frag(kong, "if (select_sp.x < constants.select_mask.x || select_sp.x > constants.select_mask.z || select_sp.y < "
"constants.select_mask.y || select_sp.y > constants.select_mask.w) { discard; }");
}
if (g_context->brush_stencil_image != NULL && has_wposition) {
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "aspect_ratio: float", "_aspect_ratio_window");
node_shader_add_texture(kong, "texbrushstencil", "_texbrushstencil");
node_shader_add_constant(kong, "texbrushstencil_size: float2", "_size(_texbrushstencil)");
node_shader_add_constant(kong, "stencil_transform: float4", "_stencil_transform");
node_shader_write_frag(kong, "var stencil_sp4: float4 = constants.VP * float4(wposition.xyz, 1.0);");
node_shader_write_frag(kong, "var stencil_sp: float2 = stencil_sp4.xy / stencil_sp4.w;");
node_shader_write_frag(kong, "stencil_sp.x = stencil_sp.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "stencil_sp.y = 1.0 - (stencil_sp.y * 0.5 + 0.5);");
node_shader_write_frag(
kong,
"var stencil_uv: float2 = (stencil_sp.xy - constants.stencil_transform.xy) / constants.stencil_transform.z * float2(constants.aspect_ratio, 1.0);");
node_shader_write_frag(kong, "var stencil_size: float2 = constants.texbrushstencil_size;");
node_shader_write_frag(kong, "var stencil_ratio: float = stencil_size.y / stencil_size.x;");
node_shader_write_frag(kong, "stencil_uv -= float2(0.5 / stencil_ratio, 0.5);");
node_shader_write_frag(kong,
"stencil_uv = float2(stencil_uv.x * cos(constants.stencil_transform.w) - stencil_uv.y * sin(constants.stencil_transform.w),\
stencil_uv.x * sin(constants.stencil_transform.w) + stencil_uv.y * cos(constants.stencil_transform.w));");
node_shader_write_frag(kong, "stencil_uv += float2(0.5 / stencil_ratio, 0.5);");
node_shader_write_frag(kong, "stencil_uv.x *= stencil_ratio;");
node_shader_write_frag(kong, "if (stencil_uv.x < 0.0 || stencil_uv.x > 1.0 || stencil_uv.y < 0.0 || stencil_uv.y > 1.0) { discard; }");
node_shader_write_frag(kong, "var texbrushstencil_sample: float4 = sample_lod(texbrushstencil, sampler_linear, stencil_uv, 0.0);");
if (g_context->brush_stencil_image_is_alpha) {
node_shader_write_frag(kong, "opacity *= texbrushstencil_sample.a;");
}
else {
node_shader_write_frag(kong, "opacity *= texbrushstencil_sample.r * texbrushstencil_sample.a;");
}
}
node_shader_write_frag(kong, "if (opacity == 0.0) { discard; }");
if (particle) {
node_shader_write_frag(kong, "var str: float = clamp(pow(dist * constants.brush_hardness * 0.2, 2.0) / 10.0, 0.0, 1.0) * opacity;");
}
else {
node_shader_write_frag(kong, "var t: float = clamp(dist / constants.brush_radius, 0.0, 1.0);");
node_shader_write_frag(kong, "var t2: float = clamp((t - constants.brush_hardness) / max(1.0 - constants.brush_hardness, 0.001), 0.0, 1.0);");
node_shader_write_frag(kong, "var falloff: float = 1.0 - t2 * t2 * (3.0 - 2.0 * t2);");
node_shader_write_frag(kong, "var str: float = falloff * constants.brush_radius * 0.05 * opacity;");
}
if (decal) {
node_shader_write_frag(kong, "str = str * 0.2;");
}
node_shader_add_texture(kong, "texpaint_sculpt_undo", "_texpaint_sculpt_undo");
node_shader_add_constant(kong, "texpaint_undo_size: float2", "_size(_texpaint_sculpt_undo)");
node_shader_write_frag(kong, "var sample_undo: float4 = sample_lod(texpaint_sculpt_undo, sampler_linear, sculpt_uv, 0.0);");
node_shader_write_frag(kong, "var raw_undo: float4 = texpaint_sculpt_undo[uint2(uint(sculpt_uv.x * constants.texpaint_undo_size.x), uint(sculpt_uv.y * "
"constants.texpaint_undo_size.y))];");
if (g_context->layer->fill_material != NULL) {
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_write_frag(kong, "var nor_v: float = floor(raw_undo.a) / 255.0;");
node_shader_write_frag(kong, "var nor_oct: float2 = float2(raw_undo.a - floor(raw_undo.a), nor_v) * 2.0 - 1.0;");
node_shader_write_frag(kong, "var nor_z: float = 1.0 - abs(nor_oct.x) - abs(nor_oct.y);");
node_shader_write_frag(kong, "var nor_xyz: float3 = float3(nor_oct.xy, nor_z);");
node_shader_write_frag(kong, "if (nor_z < 0.0) { nor_xyz.xy = octahedron_wrap(nor_oct.xy); }");
node_shader_write_frag(kong, "var n: float3 = normalize((constants.W * float4(normalize(nor_xyz), 0.0)).xyz);");
}
else {
node_shader_write_frag(kong, "if (sample_undo.r == 0.0 && sample_undo.g == 0.0 && sample_undo.b == 0.0) { discard; }");
node_shader_add_function(kong, str_octahedron_wrap);
node_shader_add_texture(kong, "gbuffer0_undo", NULL);
if (particle) {
node_shader_add_constant(kong, "VP: float4x4", "_view_proj_matrix");
node_shader_add_constant(kong, "particle_hit: float3", "_particle_hit");
node_shader_write_frag(kong, "var hit_ndc: float4 = constants.VP * float4(constants.particle_hit, 1.0);");
node_shader_write_frag(kong, "var hit_uv: float2 = hit_ndc.xy / hit_ndc.w;");
node_shader_write_frag(kong, "hit_uv.x = hit_uv.x * 0.5 + 0.5;");
node_shader_write_frag(kong, "hit_uv.y = 1.0 - (hit_uv.y * 0.5 + 0.5);");
node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, hit_uv, 0.0).rg;");
}
else {
node_shader_write_frag(kong, "var g0_undo: float2 = sample_lod(gbuffer0_undo, sampler_linear, constants.inp.xy, 0.0).rg;");
}
node_shader_write_frag(kong, "var wn: float3;");
node_shader_write_frag(kong, "wn.z = 1.0 - abs(g0_undo.x) - abs(g0_undo.y);");
node_shader_write_frag(kong, "if (wn.z >= 0.0) { wn.xy = g0_undo.xy; } else { wn.xy = octahedron_wrap(g0_undo.xy); }");
node_shader_write_frag(kong, "var n: float3 = normalize(wn);");
}
if (g_context->tool == TOOL_TYPE_BLUR) {
// Even out the surface by relaxing each vertex toward the cursors tangent plane
node_shader_write_frag(kong, "var plane_dist: float = dot(wposition.xyz - winp.xyz, n);");
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb - n * plane_dist * str, raw_undo.a);");
}
else if (g_context->tool == TOOL_TYPE_ERASER) {
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb - n * disp * str, raw_undo.a);");
}
else {
node_shader_write_frag(kong, string("var sculpt_disp: float = %s;", sculpt_blend_mode(kong, g_context->brush_blending, "0.0", "disp.x", "str")));
node_shader_write_frag(kong, "output[0] = float4(sample_undo.rgb + n * sculpt_disp, raw_undo.a);");
}
node_shader_write_frag(kong, "output[1] = float4(str, 0.0, 0.0, 1.0);");
parser_material_finalize(con_paint);
con_paint->data->shader_from_source = true;
gpu_create_shaders_from_kong(node_shader_get(kong), &con_paint->data->vertex_shader, &con_paint->data->fragment_shader,
&con_paint->data->_->vertex_shader_size, &con_paint->data->_->fragment_shader_size);
return con_paint;
}
static void sculpt_mesh_write(node_shader_t *kong, bool attrib, char *s) {
if (attrib) {
node_shader_write_attrib_vert(kong, s);
}
else {
node_shader_write_vert(kong, s);
}
}
bool sculpt_layer_has_visible_masks(slot_layer_t *l) {
slot_layer_t_array_t *masks = slot_layer_get_masks(l, true);
if (masks == NULL) {
return false;
}
for (i32 i = 0; i < masks->length; ++i) {
if (slot_layer_is_visible(masks->buffer[i])) {
return true;
}
}
return false;
}
bool sculpt_mask_value(node_shader_t *kong, slot_layer_t *l, char *out_var, bool attrib, slot_layer_t *skip) {
if (!sculpt_layer_has_visible_masks(l)) {
return false;
}
slot_layer_t_array_t *masks = slot_layer_get_masks(l, true);
sculpt_mesh_write(kong, attrib, string("var %s: float = 1.0;", out_var));
for (i32 i = 0; i < masks->length; ++i) {
slot_layer_t *m = masks->buffer[i];
if (!slot_layer_is_visible(m) || m == skip) {
continue;
}
node_shader_add_texture(kong, string("texpaint_vert%s", i32_to_string(m->id)), string("_texpaint_vert%s", i32_to_string(m->id)));
f32 opac = slot_layer_get_opacity(m);
sculpt_mesh_write(kong, attrib,
string("%s *= lerp(1.0, sample_lod(texpaint_vert%s, sampler_linear, input.tex, 0.0).r, float(%s));", out_var, i32_to_string(m->id),
f32_to_string(opac)));
}
sculpt_mesh_write(kong, attrib, string("%s = clamp(%s, 0.0, 1.0);", out_var, out_var));
return true;
}
void sculpt_make_mesh_run(node_shader_t *kong, slot_layer_t_array_t *sculpt_layers, i32_array_t *sculpt_indices) {
i32 count = sculpt_layers->length;
if (count == 0) {
return;
}
i32 idx0 = sculpt_indices->buffer[0];
node_shader_add_constant(kong, "WVP: float4x4", "_world_view_proj_matrix");
node_shader_add_constant(kong, "N: float3x3", "_normal_matrix");
// Per-object start index into the shared sculpt grid
node_shader_add_constant(kong, "sculpt_vertex_offset: int", "_sculpt_vertex_offset");
node_shader_add_out(kong, "wnormal: float3");
kong->frag_n = false;
node_shader_add_constant(kong, string("texpaint_sculpt_size%d: float2", idx0), string("_size(_texpaint_sculpt%d)", idx0));
for (i32 i = 0; i < count; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_add_texture(kong, string("texpaint_sculpt%d", idx), string("_texpaint_sculpt%d", idx));
}
// The base layer holds the absolute mesh position
bool base_masked = sculpt_layer_has_visible_masks(sculpt_layers->buffer[0]);
if (count > 1 || base_masked) {
node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base");
}
// Position
node_shader_write_vert(kong, "var sculpt_vid: uint = uint(vertex_id()) + uint(constants.sculpt_vertex_offset);");
node_shader_write_vert(kong, string("var sculpt_grid_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0));
node_shader_write_vert(kong, "var sculpt_uv: uint2 = uint2(sculpt_vid % sculpt_grid_w, sculpt_vid / sculpt_grid_w);");
node_shader_write_vert(kong, string("var texpaint_sculpt_sample: float4 = texpaint_sculpt%d[sculpt_uv];", idx0));
node_shader_write_vert(kong, "var sculpt_pos: float3 = texpaint_sculpt_sample.xyz;");
if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_pmask0", false, NULL)) {
// Blend the base layers deformation back toward the rest pose where the mask is dark
node_shader_write_vert(kong, "var sculpt_pbase0: float4 = texpaint_sculpt_base[sculpt_uv];");
node_shader_write_vert(kong, "sculpt_pos = sculpt_pbase0.xyz + (sculpt_pos - sculpt_pbase0.xyz) * sculpt_pmask0;");
}
for (i32 i = 1; i < count; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_write_vert(kong, string("var texpaint_sculpt_sample_%d: float4 = texpaint_sculpt%d[sculpt_uv];", idx, idx));
node_shader_write_vert(kong, string("var texpaint_sculpt_base_sample_%d: float4 = texpaint_sculpt_base[sculpt_uv];", idx));
if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string("sculpt_pmask_%d", idx), false, NULL)) {
node_shader_write_vert(kong,
string("sculpt_pos = sculpt_pos + (texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz) * sculpt_pmask_%d;",
idx, idx, idx));
}
else {
node_shader_write_vert(kong, string("sculpt_pos = sculpt_pos + texpaint_sculpt_sample_%d.xyz - texpaint_sculpt_base_sample_%d.xyz;", idx, idx));
}
}
node_shader_write_vert(kong, "output.pos = constants.WVP * float4(sculpt_pos, 1.0);");
// Normal
node_shader_write_attrib_vert(kong, "var sculpt_nvid: uint = uint(vertex_id()) + uint(constants.sculpt_vertex_offset);");
node_shader_write_attrib_vert(kong, "var base_vertex0: uint = sculpt_nvid - (sculpt_nvid % uint(3));");
node_shader_write_attrib_vert(kong, "var base_vertex1: uint = base_vertex0 + uint(1);");
node_shader_write_attrib_vert(kong, "var base_vertex2: uint = base_vertex0 + uint(2);");
node_shader_write_attrib_vert(kong, string("var sculpt_ngrid_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0));
node_shader_write_attrib_vert(kong, "var uv0: uint2 = uint2(base_vertex0 % sculpt_ngrid_w, base_vertex0 / sculpt_ngrid_w);");
node_shader_write_attrib_vert(kong, "var uv1: uint2 = uint2(base_vertex1 % sculpt_ngrid_w, base_vertex1 / sculpt_ngrid_w);");
node_shader_write_attrib_vert(kong, "var uv2: uint2 = uint2(base_vertex2 % sculpt_ngrid_w, base_vertex2 / sculpt_ngrid_w);");
node_shader_write_attrib_vert(kong, string("var meshpos0: float4 = texpaint_sculpt%d[uv0];", idx0));
node_shader_write_attrib_vert(kong, string("var meshpos1: float4 = texpaint_sculpt%d[uv1];", idx0));
node_shader_write_attrib_vert(kong, string("var meshpos2: float4 = texpaint_sculpt%d[uv2];", idx0));
// Mask the normal-reconstruction positions
if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_nmask0", true, NULL)) {
node_shader_write_attrib_vert(kong, "var nbase0: float4 = texpaint_sculpt_base[uv0];");
node_shader_write_attrib_vert(kong, "var nbase1: float4 = texpaint_sculpt_base[uv1];");
node_shader_write_attrib_vert(kong, "var nbase2: float4 = texpaint_sculpt_base[uv2];");
node_shader_write_attrib_vert(kong, "meshpos0 = nbase0 + (meshpos0 - nbase0) * sculpt_nmask0;");
node_shader_write_attrib_vert(kong, "meshpos1 = nbase1 + (meshpos1 - nbase1) * sculpt_nmask0;");
node_shader_write_attrib_vert(kong, "meshpos2 = nbase2 + (meshpos2 - nbase2) * sculpt_nmask0;");
}
for (i32 i = 1; i < count; ++i) {
i32 idx = sculpt_indices->buffer[i];
if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string("sculpt_nmask_%d", idx), true, NULL)) {
node_shader_write_attrib_vert(kong, string("var nsamp0_%d: float4 = texpaint_sculpt%d[uv0];", idx, idx));
node_shader_write_attrib_vert(kong, string("var nsamp1_%d: float4 = texpaint_sculpt%d[uv1];", idx, idx));
node_shader_write_attrib_vert(kong, string("var nsamp2_%d: float4 = texpaint_sculpt%d[uv2];", idx, idx));
node_shader_write_attrib_vert(kong, string("var nbasel0_%d: float4 = texpaint_sculpt_base[uv0];", idx));
node_shader_write_attrib_vert(kong, string("var nbasel1_%d: float4 = texpaint_sculpt_base[uv1];", idx));
node_shader_write_attrib_vert(kong, string("var nbasel2_%d: float4 = texpaint_sculpt_base[uv2];", idx));
node_shader_write_attrib_vert(kong, string("meshpos0 = meshpos0 + (nsamp0_%d - nbasel0_%d) * sculpt_nmask_%d;", idx, idx, idx));
node_shader_write_attrib_vert(kong, string("meshpos1 = meshpos1 + (nsamp1_%d - nbasel1_%d) * sculpt_nmask_%d;", idx, idx, idx));
node_shader_write_attrib_vert(kong, string("meshpos2 = meshpos2 + (nsamp2_%d - nbasel2_%d) * sculpt_nmask_%d;", idx, idx, idx));
}
else {
node_shader_write_attrib_vert(kong, string("meshpos0 = meshpos0 + texpaint_sculpt%d[uv0] - texpaint_sculpt_base[uv0];", idx));
node_shader_write_attrib_vert(kong, string("meshpos1 = meshpos1 + texpaint_sculpt%d[uv1] - texpaint_sculpt_base[uv1];", idx));
node_shader_write_attrib_vert(kong, string("meshpos2 = meshpos2 + texpaint_sculpt%d[uv2] - texpaint_sculpt_base[uv2];", idx));
}
}
node_shader_write_attrib_vert(kong, "var meshnor: float3 = normalize(cross(meshpos2.xyz - meshpos1.xyz, meshpos0.xyz - meshpos1.xyz));");
node_shader_write_attrib_vert(kong, "output.wnormal = constants.N * meshnor;");
node_shader_write_attrib_frag(kong, "var n: float3 = normalize(input.wnormal);");
}
void sculpt_make_paint_run(node_shader_t *kong) {
slot_layer_t_array_t *sculpt_layers = any_array_create_from_raw((void *[]){}, 0);
i32_array_t *sculpt_indices = i32_array_create(0);
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
slot_layer_t *l = g_project->_->layers->buffer[i];
if (l->texpaint_sculpt != NULL && slot_layer_is_visible(l)) {
any_array_push(sculpt_layers, l);
i32_array_push(sculpt_indices, i);
}
}
i32 scount = sculpt_layers->length;
if (scount > 0) {
i32 idx0 = sculpt_indices->buffer[0];
node_shader_add_constant(kong, string("texpaint_sculpt_size%d: float2", idx0), string("_size(_texpaint_sculpt%d)", idx0));
for (i32 i = 0; i < scount; ++i) {
i32 idx = sculpt_indices->buffer[i];
node_shader_add_texture(kong, string("texpaint_sculpt%d", idx), string("_texpaint_sculpt%d", idx));
}
bool base_masked = sculpt_layer_has_visible_masks(sculpt_layers->buffer[0]);
if (scount > 1 || base_masked) {
node_shader_add_texture(kong, "texpaint_sculpt_base", "_texpaint_sculpt_base");
}
node_shader_write_attrib_vert(kong, string("var sculpt_paint_w: uint = uint(constants.texpaint_sculpt_size%d.x);", idx0));
node_shader_write_attrib_vert(kong, "var sculpt_uv_paint: uint2 = uint2(uint(vertex_id()) % sculpt_paint_w, uint(vertex_id()) / sculpt_paint_w);");
node_shader_write_attrib_vert(kong, string("var sculpt_pos_paint: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx0));
if (sculpt_mask_value(kong, sculpt_layers->buffer[0], "sculpt_pmask0", true, g_context->layer)) {
node_shader_write_attrib_vert(kong, "var sculpt_pbase0: float4 = texpaint_sculpt_base[sculpt_uv_paint];");
node_shader_write_attrib_vert(kong, "sculpt_pos_paint = sculpt_pbase0 + (sculpt_pos_paint - sculpt_pbase0) * sculpt_pmask0;");
}
for (i32 i = 1; i < scount; ++i) {
i32 idx = sculpt_indices->buffer[i];
if (sculpt_mask_value(kong, sculpt_layers->buffer[i], string("sculpt_pmask_%d", idx), true, g_context->layer)) {
node_shader_write_attrib_vert(kong, string("var psamp_%d: float4 = texpaint_sculpt%d[sculpt_uv_paint];", idx, idx));
node_shader_write_attrib_vert(kong, string("var pbasel_%d: float4 = texpaint_sculpt_base[sculpt_uv_paint];", idx));
node_shader_write_attrib_vert(kong, string("sculpt_pos_paint = sculpt_pos_paint + (psamp_%d - pbasel_%d) * sculpt_pmask_%d;", idx, idx, idx));
}
else {
node_shader_write_attrib_vert(
kong, string("sculpt_pos_paint = sculpt_pos_paint + texpaint_sculpt%d[sculpt_uv_paint] - texpaint_sculpt_base[sculpt_uv_paint];", idx));
}
}
node_shader_write_attrib_vert(kong, "output.ndc = constants.WVP * float4(sculpt_pos_paint.xyz, 1.0);");
if (kong->frag_wposition) {
node_shader_write_attrib_vert(kong, "output.wposition = (constants.W * float4(sculpt_pos_paint.xyz, 1.0)).xyz;");
}
}
}
void sculpt_init_sculpt_texture(slot_layer_t *l) {
i32 id = l->id;
{
render_target_t *t = render_target_create();
t->name = string("texpaint_sculpt%s", i32_to_string(id));
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
l->texpaint_sculpt = render_path_create_render_target(t)->_image;
}
sculpt_import_mesh_pack_to_texture(l->texpaint_sculpt);
i32 sculpt_layer_count = 0;
for (i32 i = 0; i < g_project->_->layers->length; ++i) {
if (g_project->_->layers->buffer[i]->texpaint_sculpt != NULL) {
sculpt_layer_count++;
}
}
if (any_map_get(render_path_render_targets, "texpaint_sculpt_base") == NULL) {
render_target_t *t = render_target_create();
t->name = "texpaint_sculpt_base";
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
render_path_create_render_target(t);
}
if (sculpt_layer_count == 1) {
render_path_set_target("texpaint_sculpt_base", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target(string("texpaint_sculpt%s", i32_to_string(id)), "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass");
}
}
void sculpt_init_meshes() {
mesh_object_t_array_t *objects = g_project->_->paint_objects;
f32 max_scale = 0.0;
for (i32 o = 0; o < objects->length; ++o) {
if (objects->buffer[o]->data->scale_pos > max_scale) {
max_scale = objects->buffer[o]->data->scale_pos;
}
}
for (i32 o = 0; o < objects->length; ++o) {
mesh_object_t *object = objects->buffer[o];
mesh_data_t *md = object->data;
f32 ratio = objects->length > 1 ? md->scale_pos / max_scale : 1.0;
i16_array_t *posa = i16_array_create(md->index_array->length * 4);
i16_array_t *nora = i16_array_create(md->index_array->length * 2);
i16_array_t *texa = i16_array_create(md->index_array->length * 2);
for (i32 i = 0; i < posa->length; ++i) {
posa->buffer[i] = 32767;
}
for (i32 i = 0; i < md->index_array->length; ++i) {
i32 index = md->index_array->buffer[i];
posa->buffer[i * 4] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4] * ratio);
posa->buffer[i * 4 + 1] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 1] * ratio);
posa->buffer[i * 4 + 2] = math_floor(md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 2] * ratio);
posa->buffer[i * 4 + 3] = md->vertex_arrays->buffer[0]->values->buffer[index * 4 + 3];
nora->buffer[i * 2] = md->vertex_arrays->buffer[1]->values->buffer[index * 2];
nora->buffer[i * 2 + 1] = md->vertex_arrays->buffer[1]->values->buffer[index * 2 + 1];
texa->buffer[i * 2] = md->vertex_arrays->buffer[2]->values->buffer[index * 2];
texa->buffer[i * 2 + 1] = md->vertex_arrays->buffer[2]->values->buffer[index * 2 + 1];
}
u32_array_t *inda = u32_array_create(md->index_array->length);
for (i32 i = 0; i < inda->length; ++i) {
inda->buffer[i] = i;
}
mesh_data_t *raw = GC_ALLOC_INIT(mesh_data_t, {.name = md->name,
.vertex_arrays = any_array_create_from_raw(
(void *[]){
GC_ALLOC_INIT(vertex_array_t, {.values = posa, .attrib = "pos", .data = "short4norm"}),
GC_ALLOC_INIT(vertex_array_t, {.values = nora, .attrib = "nor", .data = "short2norm"}),
GC_ALLOC_INIT(vertex_array_t, {.values = texa, .attrib = "tex", .data = "short2norm"}),
},
3),
.index_array = inda,
.scale_pos = 1.0,
.scale_tex = 1.0});
mesh_object_set_data(object, mesh_data_create(raw));
}
if (objects->length > 1) {
f32 ex = 0.0, ey = 0.0, ez = 0.0;
for (i32 o = 0; o < objects->length; ++o) {
vec4_t aabb = mesh_data_calculate_aabb(objects->buffer[o]->data);
ex = fmaxf(ex, aabb.x);
ey = fmaxf(ey, aabb.y);
ez = fmaxf(ez, aabb.z);
}
f32 r = math_sqrt(ex * ex + ey * ey + ez * ez);
mesh_object_t *main_object = context_main_object();
main_object->base->transform->loc = (vec4_t){0, 0, 0, 1.0};
main_object->base->transform->scale = (vec4_t){2.0 / r, 2.0 / r, 2.0 / r, 1.0};
transform_build_matrix(main_object->base->transform);
}
}
void sculpt_init() {
sculpt_init_meshes();
if (g_context->merged_object != NULL) {
util_mesh_merge(NULL);
}
make_material_parse_paint_material(true);
make_material_parse_mesh_material();
if (any_map_get(render_path_render_targets, "gbuffer0_undo") != NULL) {
return;
}
{
render_target_t *t = render_target_create();
t->name = "gbuffer0_undo";
t->width = 0;
t->height = 0;
t->format = "RGBA64";
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
{
render_target_t *t = render_target_create();
t->name = "gbufferD_undo";
t->width = 0;
t->height = 0;
t->format = "R32";
t->scale = render_path_base_get_super_sampling();
render_path_create_render_target(t);
}
{
// Holds the previous-frame sculpt state during a stroke
render_target_t *t = render_target_create();
t->name = "texpaint_sculpt_ref";
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
render_path_create_render_target(t);
}
render_path_load_shader("Scene/copy_pass/copyR32_pass");
for (i32 i = 0; i < history_undo_layers->length; ++i) {
char *ext = string("_undo%s", i32_to_string(i));
slot_layer_t *ul = history_undo_layers->buffer[i];
render_target_t *t = render_target_create();
t->name = string("texpaint_sculpt%s", ext);
t->width = config_get_texture_res_x();
t->height = config_get_texture_res_y();
t->format = "RGBA128";
ul->texpaint_sculpt = render_path_create_render_target(t)->_image;
}
}
void sculpt_layers_create_sculpt_layer() {
slot_layer_t *l = layers_new_layer(true, -1, NULL);
l->name = string("Sculpt %d", l->id + 1);
sculpt_init_meshes();
sculpt_init_sculpt_texture(l);
sculpt_init();
}
void render_path_sculpt_displace_pass(char *texpaint_sculpt) {
// Snapshot the current state so the displacement pass accumulates on top of the previous one
render_path_set_target("texpaint_sculpt_ref", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target(texpaint_sculpt, "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA128_pass");
render_path_set_target("texpaint_blend1", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("texpaint_blend0", "tex");
render_path_draw_shader("Scene/copy_pass/copyR8_pass");
string_array_t *additional = any_array_create_from_raw(
(void *[]){
"texpaint_blend0",
},
1);
render_path_set_target(texpaint_sculpt, additional, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("gbufferD_undo", "gbufferD");
if (g_context->xray || g_config->brush_angle_reject) {
render_path_bind_target("gbuffer0", "gbuffer0");
}
render_path_bind_target("texpaint_blend1", "paintmask");
render_path_bind_target("gbuffer0_undo", "gbuffer0_undo");
material_context_t *material_context = NULL;
shader_context_t *shader_context = NULL;
material_data_t *mat = g_project->_->paint_objects->buffer[0]->material;
for (i32 j = 0; j < mat->contexts->length; ++j) {
if (string_equals(mat->contexts->buffer[j]->name, "paint")) {
shader_context = shader_data_get_context(mat->_->shader, "paint");
material_context = mat->contexts->buffer[j];
break;
}
}
gpu_set_pipeline(shader_context->_->pipe);
uniforms_set_context_consts(shader_context, _render_path_bind_params);
uniforms_set_obj_consts(shader_context, g_project->_->paint_objects->buffer[0]->base);
uniforms_set_material_consts(shader_context, material_context);
gpu_set_vertex_buffer(const_data_screen_aligned_vb);
gpu_set_index_buffer(const_data_screen_aligned_ib);
gpu_draw();
render_path_end();
}
void render_path_sculpt_commands() {
if (g_context->pdirty <= 0) {
return;
}
i32 tid = g_context->layer->id;
char *texpaint_sculpt = string("texpaint_sculpt%s", i32_to_string(tid));
if (g_context->tool == TOOL_TYPE_PARTICLE) {
// Accumulate one displacement pass per active particle impact
for (i32 pi = 0; pi < 32; ++pi) {
if (g_context->particles[pi].timer == NULL || g_context->particles[pi].hit_x == 0) {
continue;
}
g_context->particle_index = pi;
g_context->particle_hit_x = g_context->particles[pi].hit_x;
g_context->particle_hit_y = g_context->particles[pi].hit_y;
g_context->particle_hit_z = g_context->particles[pi].hit_z;
g_context->last_particle_hit_x = g_context->particles[pi].hit_last_x;
g_context->last_particle_hit_y = g_context->particles[pi].hit_last_y;
g_context->last_particle_hit_z = g_context->particles[pi].hit_last_z;
render_path_sculpt_displace_pass(texpaint_sculpt);
}
return;
}
render_path_sculpt_displace_pass(texpaint_sculpt);
}
void render_path_sculpt_snapshot_gbuffer() {
// Freeze the current surface (depth + packed normal) as the reference the displacement pass reads
render_path_set_target("gbuffer0_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("gbuffer0", "tex");
render_path_draw_shader("Scene/copy_pass/copyRGBA64_pass");
render_path_set_target("gbufferD_undo", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
render_path_bind_target("main", "tex");
render_path_draw_shader("Scene/copy_pass/copyR32_pass");
}
void render_path_sculpt_begin() {
if (!render_path_paint_paint_enabled()) {
return;
}
render_path_paint_push_undo_last = history_push_undo;
if (history_push_undo && history_undo_layers != NULL) {
history_paint();
render_path_sculpt_snapshot_gbuffer();
}
sculpt_push_undo = false;
}
void slot_layer_apply_sculpt(slot_layer_t *raw) {
if (raw->texpaint_sculpt == NULL) {
return;
}
mesh_object_t_array_t *objects = g_project->_->paint_objects;
buffer_t *pixels = gpu_get_texture_pixels(raw->texpaint_sculpt);
// Each object occupies a contiguous slice of the shared sculpt grid
f32 max_abs = 1.0;
i32 offset = 0;
for (i32 o = 0; o < objects->length; ++o) {
mesh_data_t *g = objects->buffer[o]->data;
i32 nv = math_floor(g->vertex_arrays->buffer[0]->values->length / 4.0);
for (i32 i = 0; i < nv; ++i) {
i32 t = i + offset;
f32 x = math_abs(buffer_get_f32(pixels, t * 16));
f32 y = math_abs(buffer_get_f32(pixels, t * 16 + 4));
f32 z = math_abs(buffer_get_f32(pixels, t * 16 + 8));
if (x > max_abs)
max_abs = x;
if (y > max_abs)
max_abs = y;
if (z > max_abs)
max_abs = z;
}
offset += g->index_array->length;
}
offset = 0;
for (i32 o = 0; o < objects->length; ++o) {
mesh_object_t *ob = objects->buffer[o];
mesh_data_t *g = ob->data;
i16_array_t *va0 = g->vertex_arrays->buffer[0]->values;
i32 nv = math_floor(va0->length / 4.0);
if (max_abs > 1.0) {
ob->base->transform->scale_world = g->scale_pos = g->scale_pos * max_abs;
transform_build_matrix(ob->base->transform);
}
for (i32 i = 0; i < nv; ++i) {
i32 t = i + offset;
va0->buffer[i * 4] = math_floor(buffer_get_f32(pixels, t * 16) / max_abs * 32767.0);
va0->buffer[i * 4 + 1] = math_floor(buffer_get_f32(pixels, t * 16 + 4) / max_abs * 32767.0);
va0->buffer[i * 4 + 2] = math_floor(buffer_get_f32(pixels, t * 16 + 8) / max_abs * 32767.0);
}
mesh_data_build_vertices(g->_->vertex_buffer, g->vertex_arrays);
offset += g->index_array->length;
}
util_mesh_calc_normals(true);
render_path_raytrace_ready = false;
slot_layer_delete(raw);
}