forked from bartvdbraak/blender
Cycles microdisplacement: Improved automatic bump mapping
Object coordinates can now be used in the displacement shader and will give correct results, where as before bump mapping was calculated from the displace positions and resulted in incorrect shading. This works by evaluating the shader in two parts, first bump then surface, and setting the shader state to match what it would be if the surface was undisplaced for the bump shader evaluation. Currently only `P` is set as if undisplaced, but other shader variables could be set as well, such as `I` or `time`. Since these aren't set to anything meaningful for displacement I left them out of this patch, we can decide what to do with them separately. Reviewed By: brecht Differential Revision: https://developer.blender.org/D2156
This commit is contained in:
parent
62aecbdac1
commit
e7ea1ae78c
@ -113,6 +113,7 @@ set(SRC_SVM_HEADERS
|
||||
svm/svm.h
|
||||
svm/svm_attribute.h
|
||||
svm/svm_blackbody.h
|
||||
svm/svm_bump.h
|
||||
svm/svm_camera.h
|
||||
svm/svm_closure.h
|
||||
svm/svm_convert.h
|
||||
|
@ -714,20 +714,21 @@ enum ShaderDataFlag {
|
||||
SD_VOLUME_MIS = (1 << 19), /* use multiple importance sampling */
|
||||
SD_VOLUME_CUBIC = (1 << 20), /* use cubic interpolation for voxels */
|
||||
SD_HAS_BUMP = (1 << 21), /* has data connected to the displacement input */
|
||||
SD_HAS_DISPLACEMENT = (1 << 22), /* has true displacement */
|
||||
|
||||
SD_SHADER_FLAGS = (SD_USE_MIS|SD_HAS_TRANSPARENT_SHADOW|SD_HAS_VOLUME|
|
||||
SD_HAS_ONLY_VOLUME|SD_HETEROGENEOUS_VOLUME|
|
||||
SD_HAS_BSSRDF_BUMP|SD_VOLUME_EQUIANGULAR|SD_VOLUME_MIS|
|
||||
SD_VOLUME_CUBIC|SD_HAS_BUMP),
|
||||
SD_VOLUME_CUBIC|SD_HAS_BUMP|SD_HAS_DISPLACEMENT),
|
||||
|
||||
/* object flags */
|
||||
SD_HOLDOUT_MASK = (1 << 22), /* holdout for camera rays */
|
||||
SD_OBJECT_MOTION = (1 << 23), /* has object motion blur */
|
||||
SD_TRANSFORM_APPLIED = (1 << 24), /* vertices have transform applied */
|
||||
SD_NEGATIVE_SCALE_APPLIED = (1 << 25), /* vertices have negative scale applied */
|
||||
SD_OBJECT_HAS_VOLUME = (1 << 26), /* object has a volume shader */
|
||||
SD_OBJECT_INTERSECTS_VOLUME = (1 << 27), /* object intersects AABB of an object with volume shader */
|
||||
SD_OBJECT_HAS_VERTEX_MOTION = (1 << 28), /* has position for motion vertices */
|
||||
SD_HOLDOUT_MASK = (1 << 23), /* holdout for camera rays */
|
||||
SD_OBJECT_MOTION = (1 << 24), /* has object motion blur */
|
||||
SD_TRANSFORM_APPLIED = (1 << 25), /* vertices have transform applied */
|
||||
SD_NEGATIVE_SCALE_APPLIED = (1 << 26), /* vertices have negative scale applied */
|
||||
SD_OBJECT_HAS_VOLUME = (1 << 27), /* object has a volume shader */
|
||||
SD_OBJECT_INTERSECTS_VOLUME = (1 << 28), /* object intersects AABB of an object with volume shader */
|
||||
SD_OBJECT_HAS_VERTEX_MOTION = (1 << 29), /* has position for motion vertices */
|
||||
|
||||
SD_OBJECT_FLAGS = (SD_HOLDOUT_MASK|SD_OBJECT_MOTION|SD_TRANSFORM_APPLIED|
|
||||
SD_NEGATIVE_SCALE_APPLIED|SD_OBJECT_HAS_VOLUME|
|
||||
|
@ -54,6 +54,7 @@ struct OSLGlobals {
|
||||
vector<OSL::ShaderGroupRef> surface_state;
|
||||
vector<OSL::ShaderGroupRef> volume_state;
|
||||
vector<OSL::ShaderGroupRef> displacement_state;
|
||||
vector<OSL::ShaderGroupRef> bump_state;
|
||||
OSL::ShaderGroupRef background_state;
|
||||
|
||||
/* attributes */
|
||||
|
@ -93,6 +93,7 @@ ustring OSLRenderServices::u_geom_numpolyvertices("geom:numpolyvertices");
|
||||
ustring OSLRenderServices::u_geom_trianglevertices("geom:trianglevertices");
|
||||
ustring OSLRenderServices::u_geom_polyvertices("geom:polyvertices");
|
||||
ustring OSLRenderServices::u_geom_name("geom:name");
|
||||
ustring OSLRenderServices::u_geom_undisplaced("geom:undisplaced");
|
||||
ustring OSLRenderServices::u_is_smooth("geom:is_smooth");
|
||||
#ifdef __HAIR__
|
||||
ustring OSLRenderServices::u_is_curve("geom:is_curve");
|
||||
|
@ -158,6 +158,7 @@ public:
|
||||
static ustring u_geom_trianglevertices;
|
||||
static ustring u_geom_polyvertices;
|
||||
static ustring u_geom_name;
|
||||
static ustring u_geom_undisplaced;
|
||||
static ustring u_is_smooth;
|
||||
static ustring u_is_curve;
|
||||
static ustring u_curve_thickness;
|
||||
|
@ -184,6 +184,47 @@ void OSLShader::eval_surface(KernelGlobals *kg, ShaderData *sd, PathState *state
|
||||
OSL::ShadingContext *octx = tdata->context[(int)ctx];
|
||||
int shader = sd->shader & SHADER_MASK;
|
||||
|
||||
/* automatic bump shader */
|
||||
if(kg->osl->bump_state[shader]) {
|
||||
/* save state */
|
||||
float3 P = sd->P;
|
||||
float3 dPdx = sd->dP.dx;
|
||||
float3 dPdy = sd->dP.dy;
|
||||
|
||||
/* set state as if undisplaced */
|
||||
if(sd->flag & SD_HAS_DISPLACEMENT) {
|
||||
float data[9];
|
||||
bool found = kg->osl->services->get_attribute(sd, true, OSLRenderServices::u_empty, TypeDesc::TypeVector,
|
||||
OSLRenderServices::u_geom_undisplaced, data);
|
||||
assert(found);
|
||||
|
||||
memcpy(&sd->P, data, sizeof(float)*3);
|
||||
memcpy(&sd->dP.dx, data+3, sizeof(float)*3);
|
||||
memcpy(&sd->dP.dy, data+6, sizeof(float)*3);
|
||||
|
||||
object_position_transform(kg, sd, &sd->P);
|
||||
object_dir_transform(kg, sd, &sd->dP.dx);
|
||||
object_dir_transform(kg, sd, &sd->dP.dy);
|
||||
|
||||
globals->P = TO_VEC3(sd->P);
|
||||
globals->dPdx = TO_VEC3(sd->dP.dx);
|
||||
globals->dPdy = TO_VEC3(sd->dP.dy);
|
||||
}
|
||||
|
||||
/* execute bump shader */
|
||||
ss->execute(octx, *(kg->osl->bump_state[shader]), *globals);
|
||||
|
||||
/* reset state */
|
||||
sd->P = P;
|
||||
sd->dP.dx = dPdx;
|
||||
sd->dP.dy = dPdy;
|
||||
|
||||
globals->P = TO_VEC3(P);
|
||||
globals->dPdx = TO_VEC3(dPdx);
|
||||
globals->dPdy = TO_VEC3(dPdy);
|
||||
}
|
||||
|
||||
/* surface shader */
|
||||
if(kg->osl->surface_state[shader]) {
|
||||
ss->execute(octx, *(kg->osl->surface_state[shader]), *globals);
|
||||
}
|
||||
|
@ -181,6 +181,7 @@ CCL_NAMESPACE_END
|
||||
#include "svm_brick.h"
|
||||
#include "svm_vector_transform.h"
|
||||
#include "svm_voxel.h"
|
||||
#include "svm_bump.h"
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
@ -294,6 +295,12 @@ ccl_device_noinline void svm_eval_nodes(KernelGlobals *kg, ShaderData *sd, ccl_a
|
||||
case NODE_CLOSURE_SET_NORMAL:
|
||||
svm_node_set_normal(kg, sd, stack, node.y, node.z);
|
||||
break;
|
||||
case NODE_ENTER_BUMP_EVAL:
|
||||
svm_node_enter_bump_eval(kg, sd, stack, node.y);
|
||||
break;
|
||||
case NODE_LEAVE_BUMP_EVAL:
|
||||
svm_node_leave_bump_eval(kg, sd, stack, node.y);
|
||||
break;
|
||||
# endif /* NODES_FEATURE(NODE_FEATURE_BUMP) */
|
||||
case NODE_HSV:
|
||||
svm_node_hsv(kg, sd, stack, node, &offset);
|
||||
|
54
intern/cycles/kernel/svm/svm_bump.h
Normal file
54
intern/cycles/kernel/svm/svm_bump.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2011-2016 Blender Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
CCL_NAMESPACE_BEGIN
|
||||
|
||||
/* Bump Eval Nodes */
|
||||
|
||||
ccl_device void svm_node_enter_bump_eval(KernelGlobals *kg, ShaderData *sd, float *stack, uint offset)
|
||||
{
|
||||
/* save state */
|
||||
stack_store_float3(stack, offset+0, ccl_fetch(sd, P));
|
||||
stack_store_float3(stack, offset+3, ccl_fetch(sd, dP).dx);
|
||||
stack_store_float3(stack, offset+6, ccl_fetch(sd, dP).dy);
|
||||
|
||||
/* set state as if undisplaced */
|
||||
const AttributeDescriptor desc = find_attribute(kg, sd, ATTR_STD_POSITION_UNDISPLACED);
|
||||
|
||||
if(desc.offset != ATTR_STD_NOT_FOUND) {
|
||||
float3 P, dPdx, dPdy;
|
||||
P = primitive_attribute_float3(kg, sd, desc, &dPdx, &dPdy);
|
||||
|
||||
object_position_transform(kg, sd, &P);
|
||||
object_dir_transform(kg, sd, &dPdx);
|
||||
object_dir_transform(kg, sd, &dPdy);
|
||||
|
||||
ccl_fetch(sd, P) = P;
|
||||
ccl_fetch(sd, dP).dx = dPdx;
|
||||
ccl_fetch(sd, dP).dy = dPdy;
|
||||
}
|
||||
}
|
||||
|
||||
ccl_device void svm_node_leave_bump_eval(KernelGlobals *kg, ShaderData *sd, float *stack, uint offset)
|
||||
{
|
||||
/* restore state */
|
||||
ccl_fetch(sd, P) = stack_load_float3(stack, offset+0);
|
||||
ccl_fetch(sd, dP).dx = stack_load_float3(stack, offset+3);
|
||||
ccl_fetch(sd, dP).dy = stack_load_float3(stack, offset+6);
|
||||
}
|
||||
|
||||
CCL_NAMESPACE_END
|
||||
|
@ -26,6 +26,8 @@ CCL_NAMESPACE_BEGIN
|
||||
/* SVM stack offsets with this value indicate that it's not on the stack */
|
||||
#define SVM_STACK_INVALID 255
|
||||
|
||||
#define SVM_BUMP_EVAL_STATE_SIZE 9
|
||||
|
||||
/* Nodes */
|
||||
|
||||
/* Known frequencies of used nodes, used for selective nodes compilation
|
||||
@ -127,6 +129,8 @@ typedef enum ShaderNodeType {
|
||||
NODE_HAIR_INFO,
|
||||
NODE_UVMAP,
|
||||
NODE_TEX_VOXEL,
|
||||
NODE_ENTER_BUMP_EVAL,
|
||||
NODE_LEAVE_BUMP_EVAL,
|
||||
} ShaderNodeType;
|
||||
|
||||
typedef enum NodeAttributeType {
|
||||
@ -374,7 +378,8 @@ typedef enum NodeTexVoxelSpace {
|
||||
typedef enum ShaderType {
|
||||
SHADER_TYPE_SURFACE,
|
||||
SHADER_TYPE_VOLUME,
|
||||
SHADER_TYPE_DISPLACEMENT
|
||||
SHADER_TYPE_DISPLACEMENT,
|
||||
SHADER_TYPE_BUMP,
|
||||
} ShaderType;
|
||||
|
||||
/* Closure */
|
||||
|
@ -856,27 +856,8 @@ void ShaderGraph::bump_from_displacement()
|
||||
/* connect the bump out to the set normal in: */
|
||||
connect(bump->output("Normal"), set_normal->input("Direction"));
|
||||
|
||||
/* connect bump output to normal input nodes that aren't set yet. actually
|
||||
* this will only set the normal input to the geometry node that we created
|
||||
* and connected to all other normal inputs already. */
|
||||
foreach(ShaderNode *node, nodes) {
|
||||
/* Don't connect normal to the bump node we're coming from,
|
||||
* otherwise it'll be a cycle in graph.
|
||||
*/
|
||||
if(node == bump) {
|
||||
continue;
|
||||
}
|
||||
foreach(ShaderInput *input, node->inputs) {
|
||||
if(!input->link && (input->flags() & SocketType::LINK_NORMAL))
|
||||
connect(set_normal->output("Normal"), input);
|
||||
}
|
||||
}
|
||||
|
||||
/* for displacement bump, clear the normal input in case the above loop
|
||||
* connected the setnormal out to the bump normalin */
|
||||
ShaderInput *bump_normal_in = bump->input("Normal");
|
||||
if(bump_normal_in)
|
||||
bump_normal_in->link = NULL;
|
||||
/* connect to output node */
|
||||
connect(set_normal->output("Normal"), output()->input("Normal"));
|
||||
|
||||
/* finally, add the copied nodes to the graph. we can't do this earlier
|
||||
* because we would create dependency cycles in the above loop */
|
||||
|
@ -609,7 +609,7 @@ bool OSLCompiler::node_skip_input(ShaderNode *node, ShaderInput *input)
|
||||
return true;
|
||||
if(input->name() == "Displacement" && current_type != SHADER_TYPE_DISPLACEMENT)
|
||||
return true;
|
||||
if(input->name() == "Normal")
|
||||
if(input->name() == "Normal" && current_type != SHADER_TYPE_BUMP)
|
||||
return true;
|
||||
}
|
||||
else if(node->special_type == SHADER_SPECIAL_TYPE_BUMP) {
|
||||
@ -684,6 +684,8 @@ void OSLCompiler::add(ShaderNode *node, const char *name, bool isfilepath)
|
||||
ss->Shader("surface", name, id(node).c_str());
|
||||
else if(current_type == SHADER_TYPE_DISPLACEMENT)
|
||||
ss->Shader("displacement", name, id(node).c_str());
|
||||
else if(current_type == SHADER_TYPE_BUMP)
|
||||
ss->Shader("displacement", name, id(node).c_str());
|
||||
else
|
||||
assert(0);
|
||||
|
||||
@ -1055,6 +1057,12 @@ OSL::ShaderGroupRef OSLCompiler::compile_type(Shader *shader, ShaderGraph *graph
|
||||
generate_nodes(dependencies);
|
||||
output->compile(*this);
|
||||
}
|
||||
else if(type == SHADER_TYPE_BUMP) {
|
||||
/* generate bump shader */
|
||||
find_dependencies(dependencies, output->input("Normal"));
|
||||
generate_nodes(dependencies);
|
||||
output->compile(*this);
|
||||
}
|
||||
else if(type == SHADER_TYPE_VOLUME) {
|
||||
/* generate volume shader */
|
||||
find_dependencies(dependencies, output->input("Volume"));
|
||||
@ -1116,10 +1124,10 @@ void OSLCompiler::compile(Scene *scene, OSLGlobals *og, Shader *shader)
|
||||
if(shader->used && graph && output->input("Surface")->link) {
|
||||
shader->osl_surface_ref = compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
|
||||
|
||||
if(shader->graph_bump)
|
||||
shader->osl_surface_bump_ref = compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE);
|
||||
if(shader->graph_bump && shader->displacement_method != DISPLACE_TRUE)
|
||||
shader->osl_surface_bump_ref = compile_type(shader, shader->graph_bump, SHADER_TYPE_BUMP);
|
||||
else
|
||||
shader->osl_surface_bump_ref = shader->osl_surface_ref;
|
||||
shader->osl_surface_bump_ref = OSL::ShaderGroupRef();
|
||||
|
||||
shader->has_surface = true;
|
||||
}
|
||||
@ -1146,15 +1154,10 @@ void OSLCompiler::compile(Scene *scene, OSLGlobals *og, Shader *shader)
|
||||
}
|
||||
|
||||
/* push state to array for lookup */
|
||||
if(shader->displacement_method == DISPLACE_TRUE || !shader->graph_bump) {
|
||||
og->surface_state.push_back(shader->osl_surface_ref);
|
||||
}
|
||||
else {
|
||||
og->surface_state.push_back(shader->osl_surface_bump_ref);
|
||||
}
|
||||
|
||||
og->surface_state.push_back(shader->osl_surface_ref);
|
||||
og->volume_state.push_back(shader->osl_volume_ref);
|
||||
og->displacement_state.push_back(shader->osl_displacement_ref);
|
||||
og->bump_state.push_back(shader->osl_surface_bump_ref);
|
||||
}
|
||||
|
||||
#else
|
||||
|
@ -407,6 +407,8 @@ void ShaderManager::device_update_common(Device *device,
|
||||
flag |= SD_VOLUME_CUBIC;
|
||||
if(shader->graph_bump)
|
||||
flag |= SD_HAS_BUMP;
|
||||
if(shader->displacement_method != DISPLACE_BUMP)
|
||||
flag |= SD_HAS_DISPLACEMENT;
|
||||
|
||||
/* shader with bump mapping */
|
||||
if(shader->displacement_method != DISPLACE_TRUE && shader->graph_bump)
|
||||
|
@ -146,9 +146,8 @@ int SVMCompiler::stack_size(SocketType::Type type)
|
||||
return size;
|
||||
}
|
||||
|
||||
int SVMCompiler::stack_find_offset(SocketType::Type type)
|
||||
int SVMCompiler::stack_find_offset(int size)
|
||||
{
|
||||
int size = stack_size(type);
|
||||
int offset = -1;
|
||||
|
||||
/* find free space in stack & mark as used */
|
||||
@ -175,6 +174,11 @@ int SVMCompiler::stack_find_offset(SocketType::Type type)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int SVMCompiler::stack_find_offset(SocketType::Type type)
|
||||
{
|
||||
return stack_find_offset(stack_size(type));
|
||||
}
|
||||
|
||||
void SVMCompiler::stack_clear_offset(SocketType::Type type, int offset)
|
||||
{
|
||||
int size = stack_size(type);
|
||||
@ -647,6 +651,9 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
|
||||
case SHADER_TYPE_DISPLACEMENT:
|
||||
clin = node->input("Displacement");
|
||||
break;
|
||||
case SHADER_TYPE_BUMP:
|
||||
clin = node->input("Normal");
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
@ -663,6 +670,13 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
|
||||
output->stack_offset = SVM_STACK_INVALID;
|
||||
}
|
||||
|
||||
/* for the bump shader we need add a node to store the shader state */
|
||||
int bump_state_offset = SVM_STACK_INVALID;
|
||||
if(type == SHADER_TYPE_BUMP) {
|
||||
bump_state_offset = stack_find_offset(SVM_BUMP_EVAL_STATE_SIZE);
|
||||
add_node(NODE_ENTER_BUMP_EVAL, bump_state_offset);
|
||||
}
|
||||
|
||||
if(shader->used) {
|
||||
if(clin->link) {
|
||||
bool generate = false;
|
||||
@ -680,6 +694,9 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
|
||||
generate = true;
|
||||
shader->has_displacement = true;
|
||||
break;
|
||||
case SHADER_TYPE_BUMP: /* generate bump shader */
|
||||
generate = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -696,13 +713,21 @@ void SVMCompiler::compile_type(Shader *shader, ShaderGraph *graph, ShaderType ty
|
||||
node->compile(*this);
|
||||
}
|
||||
|
||||
/* add node to restore state after bump shader has finished */
|
||||
if(type == SHADER_TYPE_BUMP) {
|
||||
add_node(NODE_LEAVE_BUMP_EVAL, bump_state_offset);
|
||||
}
|
||||
|
||||
/* if compile failed, generate empty shader */
|
||||
if(compile_failed) {
|
||||
svm_nodes.clear();
|
||||
compile_failed = false;
|
||||
}
|
||||
|
||||
add_node(NODE_END, 0, 0, 0);
|
||||
/* for bump shaders we fall thru to the surface shader, but if this is any other kind of shader it ends here */
|
||||
if(type != SHADER_TYPE_BUMP) {
|
||||
add_node(NODE_END, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SVMCompiler::compile(Scene *scene,
|
||||
@ -752,17 +777,22 @@ void SVMCompiler::compile(Scene *scene,
|
||||
shader->has_object_dependency = false;
|
||||
shader->has_integrator_dependency = false;
|
||||
|
||||
/* generate surface shader */
|
||||
if(shader->displacement_method == DISPLACE_TRUE || !shader->graph_bump) {
|
||||
scoped_timer timer((summary != NULL)? &summary->time_generate_surface: NULL);
|
||||
compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
|
||||
/* generate bump shader */
|
||||
if(shader->displacement_method != DISPLACE_TRUE && shader->graph_bump) {
|
||||
scoped_timer timer((summary != NULL)? &summary->time_generate_bump: NULL);
|
||||
compile_type(shader, shader->graph_bump, SHADER_TYPE_BUMP);
|
||||
global_svm_nodes[index].y = global_svm_nodes.size();
|
||||
global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
|
||||
}
|
||||
else {
|
||||
scoped_timer timer((summary != NULL)? &summary->time_generate_bump: NULL);
|
||||
compile_type(shader, shader->graph_bump, SHADER_TYPE_SURFACE);
|
||||
global_svm_nodes[index].y = global_svm_nodes.size();
|
||||
|
||||
/* generate surface shader */
|
||||
{
|
||||
scoped_timer timer((summary != NULL)? &summary->time_generate_surface: NULL);
|
||||
compile_type(shader, shader->graph, SHADER_TYPE_SURFACE);
|
||||
/* only set jump offset if there's no bump shader, as the bump shader will fall thru to this one if it exists */
|
||||
if(shader->displacement_method == DISPLACE_TRUE || !shader->graph_bump) {
|
||||
global_svm_nodes[index].y = global_svm_nodes.size();
|
||||
}
|
||||
global_svm_nodes.insert(global_svm_nodes.end(), svm_nodes.begin(), svm_nodes.end());
|
||||
}
|
||||
|
||||
|
@ -99,6 +99,7 @@ public:
|
||||
int stack_assign(ShaderInput *input);
|
||||
int stack_assign_if_linked(ShaderInput *input);
|
||||
int stack_assign_if_linked(ShaderOutput *output);
|
||||
int stack_find_offset(int size);
|
||||
int stack_find_offset(SocketType::Type type);
|
||||
void stack_clear_offset(SocketType::Type type, int offset);
|
||||
void stack_link(ShaderInput *input, ShaderOutput *output);
|
||||
|
Loading…
Reference in New Issue
Block a user